Add /restart WhatsApp command to restart warelay
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
- **Pi/Tau stability:** RPC replies buffered until the assistant turn finishes; parsers return consistent `texts[]`; web auto-replies keep a warm Tau RPC process to avoid cold starts.
|
||||
- **Claude prompt flow:** One-time `sessionIntro` with per-message `/think:high` bodyPrefix; system prompt always sent on first turn even with `sendSystemOnce`.
|
||||
- **Heartbeat UX:** Backpressure skips reply heartbeats while other commands run; skips don’t refresh session `updatedAt`; web/Twilio heartbeats normalize array payloads and optional `heartbeatCommand`.
|
||||
- **Control via WhatsApp:** Send `/restart` to restart the warelay launchd service (`com.steipete.warelay`) from your allowed numbers.
|
||||
- **Tau completion signal:** RPC now resolves on Tau’s `agent_end` (or process exit) so late assistant messages aren’t truncated; 5-minute hard cap only as a failsafe.
|
||||
|
||||
### Reliability & UX
|
||||
|
||||
@@ -16,6 +16,7 @@ import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
import type { TwilioRequester } from "../twilio/types.js";
|
||||
import { sendTypingIndicator } from "../twilio/typing.js";
|
||||
import { triggerWarelayRestart } from "../infra/restart.js";
|
||||
import { chunkText } from "./chunk.js";
|
||||
import { runCommandReply } from "./command-reply.js";
|
||||
import {
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
} from "./templating.js";
|
||||
import { isAudio, transcribeInboundAudio } from "./transcription.js";
|
||||
import type { GetReplyOptions, ReplyPayload } from "./types.js";
|
||||
import { triggerWarelayRestart } from "../infra/restart.js";
|
||||
|
||||
export type { GetReplyOptions, ReplyPayload } from "./types.js";
|
||||
|
||||
@@ -347,6 +349,11 @@ export async function getReplyFromConfig(
|
||||
const to = (ctx.To ?? "").replace(/^whatsapp:/, "");
|
||||
const isSamePhone = from && to && from === to;
|
||||
const abortKey = sessionKey ?? (from || undefined) ?? (to || undefined);
|
||||
const rawBodyNormalized = (
|
||||
sessionCtx.BodyStripped ?? sessionCtx.Body ?? ""
|
||||
)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
if (!sessionEntry && abortKey) {
|
||||
abortedLastRun = ABORT_MEMORY.get(abortKey) ?? false;
|
||||
@@ -366,6 +373,18 @@ export async function getReplyFromConfig(
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
rawBodyNormalized === "/restart" ||
|
||||
rawBodyNormalized === "restart" ||
|
||||
rawBodyNormalized.startsWith("/restart ")
|
||||
) {
|
||||
triggerWarelayRestart();
|
||||
cleanupTyping();
|
||||
return {
|
||||
text: "Restarting warelay via launchctl; give me a few seconds to come back online.",
|
||||
};
|
||||
}
|
||||
|
||||
const abortRequested =
|
||||
reply?.mode === "command" &&
|
||||
isAbortTrigger((sessionCtx.BodyStripped ?? sessionCtx.Body ?? "").trim());
|
||||
|
||||
14
src/infra/restart.ts
Normal file
14
src/infra/restart.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
const DEFAULT_LAUNCHD_LABEL = "com.steipete.warelay";
|
||||
|
||||
export function triggerWarelayRestart(): void {
|
||||
const label = process.env.WARELAY_LAUNCHD_LABEL || DEFAULT_LAUNCHD_LABEL;
|
||||
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
||||
const target = uid !== undefined ? `gui/${uid}/${label}` : label;
|
||||
const child = spawn("launchctl", ["kickstart", "-k", target], {
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
});
|
||||
child.unref();
|
||||
}
|
||||
Reference in New Issue
Block a user