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.
|
- **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`.
|
- **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`.
|
- **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.
|
- **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
|
### Reliability & UX
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { runCommandWithTimeout } from "../process/exec.js";
|
|||||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||||
import type { TwilioRequester } from "../twilio/types.js";
|
import type { TwilioRequester } from "../twilio/types.js";
|
||||||
import { sendTypingIndicator } from "../twilio/typing.js";
|
import { sendTypingIndicator } from "../twilio/typing.js";
|
||||||
|
import { triggerWarelayRestart } from "../infra/restart.js";
|
||||||
import { chunkText } from "./chunk.js";
|
import { chunkText } from "./chunk.js";
|
||||||
import { runCommandReply } from "./command-reply.js";
|
import { runCommandReply } from "./command-reply.js";
|
||||||
import {
|
import {
|
||||||
@@ -25,6 +26,7 @@ import {
|
|||||||
} from "./templating.js";
|
} from "./templating.js";
|
||||||
import { isAudio, transcribeInboundAudio } from "./transcription.js";
|
import { isAudio, transcribeInboundAudio } from "./transcription.js";
|
||||||
import type { GetReplyOptions, ReplyPayload } from "./types.js";
|
import type { GetReplyOptions, ReplyPayload } from "./types.js";
|
||||||
|
import { triggerWarelayRestart } from "../infra/restart.js";
|
||||||
|
|
||||||
export type { GetReplyOptions, ReplyPayload } from "./types.js";
|
export type { GetReplyOptions, ReplyPayload } from "./types.js";
|
||||||
|
|
||||||
@@ -347,6 +349,11 @@ export async function getReplyFromConfig(
|
|||||||
const to = (ctx.To ?? "").replace(/^whatsapp:/, "");
|
const to = (ctx.To ?? "").replace(/^whatsapp:/, "");
|
||||||
const isSamePhone = from && to && from === to;
|
const isSamePhone = from && to && from === to;
|
||||||
const abortKey = sessionKey ?? (from || undefined) ?? (to || undefined);
|
const abortKey = sessionKey ?? (from || undefined) ?? (to || undefined);
|
||||||
|
const rawBodyNormalized = (
|
||||||
|
sessionCtx.BodyStripped ?? sessionCtx.Body ?? ""
|
||||||
|
)
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
if (!sessionEntry && abortKey) {
|
if (!sessionEntry && abortKey) {
|
||||||
abortedLastRun = ABORT_MEMORY.get(abortKey) ?? false;
|
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 =
|
const abortRequested =
|
||||||
reply?.mode === "command" &&
|
reply?.mode === "command" &&
|
||||||
isAbortTrigger((sessionCtx.BodyStripped ?? sessionCtx.Body ?? "").trim());
|
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