feat(auto-reply): greet on bare /new
This commit is contained in:
@@ -145,7 +145,7 @@ Example:
|
|||||||
|
|
||||||
- Session files: `~/.clawdis/sessions/{{SessionId}}.jsonl`
|
- Session files: `~/.clawdis/sessions/{{SessionId}}.jsonl`
|
||||||
- Session metadata (token usage, last route, etc): `~/.clawdis/sessions/sessions.json` (legacy: `~/.clawdis/sessions.json`)
|
- Session metadata (token usage, last route, etc): `~/.clawdis/sessions/sessions.json` (legacy: `~/.clawdis/sessions.json`)
|
||||||
- `/new` starts a fresh session for that chat (configurable via `resetTriggers`)
|
- `/new` starts a fresh session for that chat (configurable via `resetTriggers`). If sent alone, the agent replies with a short hello to confirm the reset.
|
||||||
|
|
||||||
## Heartbeats (proactive mode)
|
## Heartbeats (proactive mode)
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ All session state is **owned by the gateway** (the “master” Clawdis). UI cli
|
|||||||
|
|
||||||
## Lifecyle
|
## Lifecyle
|
||||||
- Idle expiry: `inbound.session.idleMinutes` (default 60). After the timeout a new `sessionId` is minted on the next message.
|
- Idle expiry: `inbound.session.idleMinutes` (default 60). After the timeout a new `sessionId` is minted on the next message.
|
||||||
- Reset triggers: exact `/new` (plus any extras in `resetTriggers`) start a fresh session id and pass the remainder of the message through.
|
- Reset triggers: exact `/new` (plus any extras in `resetTriggers`) start a fresh session id and pass the remainder of the message through. If `/new` is sent alone, Clawdis runs a short “hello” greeting turn to confirm the reset.
|
||||||
- Manual reset: delete specific keys from the store or remove the JSONL transcript; the next message recreates them.
|
- Manual reset: delete specific keys from the store or remove the JSONL transcript; the next message recreates them.
|
||||||
|
|
||||||
## Configuration (optional rename example)
|
## Configuration (optional rename example)
|
||||||
|
|||||||
@@ -98,8 +98,16 @@ describe("trigger handling", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("acknowledges a bare /new without treating it as empty", async () => {
|
it("runs a greeting prompt for a bare /new", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||||
|
payloads: [{ text: "hello" }],
|
||||||
|
meta: {
|
||||||
|
durationMs: 1,
|
||||||
|
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const res = await getReplyFromConfig(
|
const res = await getReplyFromConfig(
|
||||||
{
|
{
|
||||||
Body: "/new",
|
Body: "/new",
|
||||||
@@ -119,8 +127,11 @@ describe("trigger handling", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||||
expect(text).toMatch(/fresh session/i);
|
expect(text).toBe("hello");
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||||
|
const prompt =
|
||||||
|
vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||||
|
expect(prompt).toContain("A new session was started via /new");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ const ABORT_TRIGGERS = new Set(["stop", "esc", "abort", "wait", "exit"]);
|
|||||||
const ABORT_MEMORY = new Map<string, boolean>();
|
const ABORT_MEMORY = new Map<string, boolean>();
|
||||||
const SYSTEM_MARK = "⚙️";
|
const SYSTEM_MARK = "⚙️";
|
||||||
|
|
||||||
|
const BARE_SESSION_RESET_PROMPT =
|
||||||
|
"A new session was started via /new. Say hi briefly and ask what the user wants to do next.";
|
||||||
|
|
||||||
export function extractThinkDirective(body?: string): {
|
export function extractThinkDirective(body?: string): {
|
||||||
cleaned: string;
|
cleaned: string;
|
||||||
thinkLevel?: ThinkLevel;
|
thinkLevel?: ThinkLevel;
|
||||||
@@ -580,20 +583,18 @@ export async function getReplyFromConfig(
|
|||||||
})()
|
})()
|
||||||
: "";
|
: "";
|
||||||
const baseBody = sessionCtx.BodyStripped ?? sessionCtx.Body ?? "";
|
const baseBody = sessionCtx.BodyStripped ?? sessionCtx.Body ?? "";
|
||||||
const baseBodyTrimmed = baseBody.trim();
|
|
||||||
const rawBodyTrimmed = (ctx.Body ?? "").trim();
|
const rawBodyTrimmed = (ctx.Body ?? "").trim();
|
||||||
|
const baseBodyTrimmedRaw = baseBody.trim();
|
||||||
const isBareSessionReset =
|
const isBareSessionReset =
|
||||||
isNewSession && baseBodyTrimmed.length === 0 && rawBodyTrimmed.length > 0;
|
isNewSession &&
|
||||||
|
baseBodyTrimmedRaw.length === 0 &&
|
||||||
|
rawBodyTrimmed.length > 0;
|
||||||
|
const baseBodyFinal = isBareSessionReset ? BARE_SESSION_RESET_PROMPT : baseBody;
|
||||||
|
const baseBodyTrimmed = baseBodyFinal.trim();
|
||||||
// Bail early if the cleaned body is empty to avoid sending blank prompts to the agent.
|
// Bail early if the cleaned body is empty to avoid sending blank prompts to the agent.
|
||||||
// This can happen if an inbound platform delivers an empty text message or we strip everything out.
|
// This can happen if an inbound platform delivers an empty text message or we strip everything out.
|
||||||
if (!baseBodyTrimmed) {
|
if (!baseBodyTrimmed) {
|
||||||
await onReplyStart();
|
await onReplyStart();
|
||||||
if (isBareSessionReset) {
|
|
||||||
cleanupTyping();
|
|
||||||
return {
|
|
||||||
text: "Started a fresh session. Send a new message to continue.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
logVerbose("Inbound body empty after normalization; skipping agent run");
|
logVerbose("Inbound body empty after normalization; skipping agent run");
|
||||||
cleanupTyping();
|
cleanupTyping();
|
||||||
return {
|
return {
|
||||||
@@ -603,7 +604,7 @@ export async function getReplyFromConfig(
|
|||||||
const abortedHint = abortedLastRun
|
const abortedHint = abortedLastRun
|
||||||
? "Note: The previous agent run was aborted by the user. Resume carefully or ask for clarification."
|
? "Note: The previous agent run was aborted by the user. Resume carefully or ask for clarification."
|
||||||
: "";
|
: "";
|
||||||
let prefixedBodyBase = baseBody;
|
let prefixedBodyBase = baseBodyFinal;
|
||||||
if (groupIntro) {
|
if (groupIntro) {
|
||||||
prefixedBodyBase = `${groupIntro}\n\n${prefixedBodyBase}`;
|
prefixedBodyBase = `${groupIntro}\n\n${prefixedBodyBase}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user