fix: status runtime + help
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
- Auth: add OpenAI Codex OAuth support and migrate legacy oauth.json into auth.json.
|
- Auth: add OpenAI Codex OAuth support and migrate legacy oauth.json into auth.json.
|
||||||
- Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding.
|
- Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding.
|
||||||
- Sandbox: copy inbound media into sandbox workspaces so agent tools can read attachments.
|
- Sandbox: copy inbound media into sandbox workspaces so agent tools can read attachments.
|
||||||
|
- Status: show runtime (docker/direct) and move shortcuts to `/help`.
|
||||||
|
|
||||||
### Maintenance
|
### Maintenance
|
||||||
- Deps: bump pi-* stack, Slack SDK, discord-api-types, file-type, zod, and Biome.
|
- Deps: bump pi-* stack, Slack SDK, discord-api-types, file-type, zod, and Biome.
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
const CONTROL_COMMAND_RE =
|
const CONTROL_COMMAND_RE =
|
||||||
/(?:^|\s)\/(?:status|thinking|think|t|verbose|v|elevated|elev|model|queue|activation|send|restart|reset|new)(?=$|\s|:)\b/i;
|
/(?:^|\s)\/(?:status|help|thinking|think|t|verbose|v|elevated|elev|model|queue|activation|send|restart|reset|new)(?=$|\s|:)\b/i;
|
||||||
|
|
||||||
const CONTROL_COMMAND_EXACT = new Set([
|
const CONTROL_COMMAND_EXACT = new Set([
|
||||||
|
"help",
|
||||||
|
"/help",
|
||||||
"status",
|
"status",
|
||||||
"/status",
|
"/status",
|
||||||
"restart",
|
"restart",
|
||||||
|
|||||||
@@ -126,6 +126,24 @@ describe("trigger handling", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns help without invoking the agent", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
const res = await getReplyFromConfig(
|
||||||
|
{
|
||||||
|
Body: "/help",
|
||||||
|
From: "+1002",
|
||||||
|
To: "+2000",
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
makeCfg(home),
|
||||||
|
);
|
||||||
|
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||||
|
expect(text).toContain("Help");
|
||||||
|
expect(text).toContain("Shortcuts");
|
||||||
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("allows owner to set send policy", async () => {
|
it("allows owner to set send policy", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const cfg = {
|
const cfg = {
|
||||||
|
|||||||
@@ -472,6 +472,7 @@ export async function getReplyFromConfig(
|
|||||||
defaultGroupActivation: () => defaultActivation,
|
defaultGroupActivation: () => defaultActivation,
|
||||||
resolvedThinkLevel,
|
resolvedThinkLevel,
|
||||||
resolvedVerboseLevel: resolvedVerboseLevel ?? "off",
|
resolvedVerboseLevel: resolvedVerboseLevel ?? "off",
|
||||||
|
resolvedElevatedLevel,
|
||||||
resolveDefaultThinkingLevel: modelState.resolveDefaultThinkingLevel,
|
resolveDefaultThinkingLevel: modelState.resolveDefaultThinkingLevel,
|
||||||
provider,
|
provider,
|
||||||
model,
|
model,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import {
|
|||||||
parseActivationCommand,
|
parseActivationCommand,
|
||||||
} from "../group-activation.js";
|
} from "../group-activation.js";
|
||||||
import { parseSendPolicyCommand } from "../send-policy.js";
|
import { parseSendPolicyCommand } from "../send-policy.js";
|
||||||
import { buildStatusMessage } from "../status.js";
|
import { buildHelpMessage, buildStatusMessage } from "../status.js";
|
||||||
import type { MsgContext } from "../templating.js";
|
import type { MsgContext } from "../templating.js";
|
||||||
import type { ThinkLevel, VerboseLevel } from "../thinking.js";
|
import type { ElevatedLevel, ThinkLevel, VerboseLevel } from "../thinking.js";
|
||||||
import type { ReplyPayload } from "../types.js";
|
import type { ReplyPayload } from "../types.js";
|
||||||
import { isAbortTrigger, setAbortMemory } from "./abort.js";
|
import { isAbortTrigger, setAbortMemory } from "./abort.js";
|
||||||
import type { InlineDirectives } from "./directive-handling.js";
|
import type { InlineDirectives } from "./directive-handling.js";
|
||||||
@@ -121,6 +121,7 @@ export async function handleCommands(params: {
|
|||||||
defaultGroupActivation: () => "always" | "mention";
|
defaultGroupActivation: () => "always" | "mention";
|
||||||
resolvedThinkLevel?: ThinkLevel;
|
resolvedThinkLevel?: ThinkLevel;
|
||||||
resolvedVerboseLevel: VerboseLevel;
|
resolvedVerboseLevel: VerboseLevel;
|
||||||
|
resolvedElevatedLevel?: ElevatedLevel;
|
||||||
resolveDefaultThinkingLevel: () => Promise<ThinkLevel | undefined>;
|
resolveDefaultThinkingLevel: () => Promise<ThinkLevel | undefined>;
|
||||||
provider: string;
|
provider: string;
|
||||||
model: string;
|
model: string;
|
||||||
@@ -143,6 +144,7 @@ export async function handleCommands(params: {
|
|||||||
defaultGroupActivation,
|
defaultGroupActivation,
|
||||||
resolvedThinkLevel,
|
resolvedThinkLevel,
|
||||||
resolvedVerboseLevel,
|
resolvedVerboseLevel,
|
||||||
|
resolvedElevatedLevel,
|
||||||
resolveDefaultThinkingLevel,
|
resolveDefaultThinkingLevel,
|
||||||
model,
|
model,
|
||||||
contextTokens,
|
contextTokens,
|
||||||
@@ -260,6 +262,20 @@ export async function handleCommands(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const helpRequested =
|
||||||
|
command.commandBodyNormalized === "/help" ||
|
||||||
|
command.commandBodyNormalized === "help" ||
|
||||||
|
/(?:^|\s)\/help(?=$|\s|:)\b/i.test(command.commandBodyNormalized);
|
||||||
|
if (helpRequested) {
|
||||||
|
if (!command.isAuthorizedSender) {
|
||||||
|
logVerbose(
|
||||||
|
`Ignoring /help from unauthorized sender: ${command.senderE164 || "<unknown>"}`,
|
||||||
|
);
|
||||||
|
return { shouldContinue: false };
|
||||||
|
}
|
||||||
|
return { shouldContinue: false, reply: { text: buildHelpMessage() } };
|
||||||
|
}
|
||||||
|
|
||||||
const statusRequested =
|
const statusRequested =
|
||||||
directives.hasStatusDirective ||
|
directives.hasStatusDirective ||
|
||||||
command.commandBodyNormalized === "/status" ||
|
command.commandBodyNormalized === "/status" ||
|
||||||
@@ -281,10 +297,12 @@ export async function handleCommands(params: {
|
|||||||
: undefined;
|
: undefined;
|
||||||
const statusText = buildStatusMessage({
|
const statusText = buildStatusMessage({
|
||||||
agent: {
|
agent: {
|
||||||
|
...(cfg.agent ?? {}),
|
||||||
model,
|
model,
|
||||||
contextTokens,
|
contextTokens,
|
||||||
thinkingDefault: cfg.agent?.thinkingDefault,
|
thinkingDefault: cfg.agent?.thinkingDefault,
|
||||||
verboseDefault: cfg.agent?.verboseDefault,
|
verboseDefault: cfg.agent?.verboseDefault,
|
||||||
|
elevatedDefault: cfg.agent?.elevatedDefault,
|
||||||
},
|
},
|
||||||
workspaceDir,
|
workspaceDir,
|
||||||
sessionEntry,
|
sessionEntry,
|
||||||
@@ -295,6 +313,7 @@ export async function handleCommands(params: {
|
|||||||
resolvedThink:
|
resolvedThink:
|
||||||
resolvedThinkLevel ?? (await resolveDefaultThinkingLevel()),
|
resolvedThinkLevel ?? (await resolveDefaultThinkingLevel()),
|
||||||
resolvedVerbose: resolvedVerboseLevel,
|
resolvedVerbose: resolvedVerboseLevel,
|
||||||
|
resolvedElevated: resolvedElevatedLevel,
|
||||||
webLinked,
|
webLinked,
|
||||||
webAuthAgeMs,
|
webAuthAgeMs,
|
||||||
heartbeatSeconds,
|
heartbeatSeconds,
|
||||||
|
|||||||
@@ -36,12 +36,14 @@ describe("buildStatusMessage", () => {
|
|||||||
|
|
||||||
expect(text).toContain("⚙️ Status");
|
expect(text).toContain("⚙️ Status");
|
||||||
expect(text).toContain("Agent: embedded pi");
|
expect(text).toContain("Agent: embedded pi");
|
||||||
|
expect(text).toContain("Runtime: direct");
|
||||||
expect(text).toContain("Context: 16k/32k (50%)");
|
expect(text).toContain("Context: 16k/32k (50%)");
|
||||||
expect(text).toContain("Session: main");
|
expect(text).toContain("Session: main");
|
||||||
expect(text).toContain("Web: linked");
|
expect(text).toContain("Web: linked");
|
||||||
expect(text).toContain("heartbeat 45s");
|
expect(text).toContain("heartbeat 45s");
|
||||||
expect(text).toContain("thinking=medium");
|
expect(text).toContain("thinking=medium");
|
||||||
expect(text).toContain("verbose=off");
|
expect(text).toContain("verbose=off");
|
||||||
|
expect(text).not.toContain("Shortcuts:");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles missing agent config gracefully", () => {
|
it("handles missing agent config gracefully", () => {
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ import {
|
|||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import {
|
import {
|
||||||
resolveSessionTranscriptPath,
|
resolveSessionTranscriptPath,
|
||||||
|
resolveMainSessionKey,
|
||||||
type SessionEntry,
|
type SessionEntry,
|
||||||
type SessionScope,
|
type SessionScope,
|
||||||
} from "../config/sessions.js";
|
} from "../config/sessions.js";
|
||||||
import { shortenHomePath } from "../utils.js";
|
import { shortenHomePath } from "../utils.js";
|
||||||
import type { ThinkLevel, VerboseLevel } from "./thinking.js";
|
import type { ElevatedLevel, ThinkLevel, VerboseLevel } from "./thinking.js";
|
||||||
|
|
||||||
type AgentConfig = NonNullable<ClawdbotConfig["agent"]>;
|
type AgentConfig = NonNullable<ClawdbotConfig["agent"]>;
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ type StatusArgs = {
|
|||||||
groupActivation?: "mention" | "always";
|
groupActivation?: "mention" | "always";
|
||||||
resolvedThink?: ThinkLevel;
|
resolvedThink?: ThinkLevel;
|
||||||
resolvedVerbose?: VerboseLevel;
|
resolvedVerbose?: VerboseLevel;
|
||||||
|
resolvedElevated?: ElevatedLevel;
|
||||||
now?: number;
|
now?: number;
|
||||||
webLinked?: boolean;
|
webLinked?: boolean;
|
||||||
webAuthAgeMs?: number | null;
|
webAuthAgeMs?: number | null;
|
||||||
@@ -164,8 +166,29 @@ export function buildStatusMessage(args: StatusArgs): string {
|
|||||||
const verboseLevel =
|
const verboseLevel =
|
||||||
args.resolvedVerbose ?? args.agent?.verboseDefault ?? "off";
|
args.resolvedVerbose ?? args.agent?.verboseDefault ?? "off";
|
||||||
const elevatedLevel =
|
const elevatedLevel =
|
||||||
|
args.resolvedElevated ??
|
||||||
args.sessionEntry?.elevatedLevel ?? args.agent?.elevatedDefault ?? "on";
|
args.sessionEntry?.elevatedLevel ?? args.agent?.elevatedDefault ?? "on";
|
||||||
|
|
||||||
|
const runtime = (() => {
|
||||||
|
const sandboxMode = args.agent?.sandbox?.mode ?? "off";
|
||||||
|
if (sandboxMode === "off")
|
||||||
|
return { line: "Runtime: direct", sandboxed: false };
|
||||||
|
const sessionScope = args.sessionScope ?? "per-sender";
|
||||||
|
const mainKey = resolveMainSessionKey({
|
||||||
|
session: { scope: sessionScope },
|
||||||
|
});
|
||||||
|
const sessionKey = args.sessionKey?.trim();
|
||||||
|
const sandboxed = sessionKey
|
||||||
|
? sandboxMode === "all" || sessionKey !== mainKey.trim()
|
||||||
|
: false;
|
||||||
|
const runtime = sandboxed ? "docker" : sessionKey ? "direct" : "unknown";
|
||||||
|
const suffix = sandboxed ? ` • elevated ${elevatedLevel}` : "";
|
||||||
|
return {
|
||||||
|
line: `Runtime: ${runtime} (sandbox ${sandboxMode})${suffix}`,
|
||||||
|
sandboxed,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
const webLine = (() => {
|
const webLine = (() => {
|
||||||
if (args.webLinked === false) {
|
if (args.webLinked === false) {
|
||||||
return "Web: not linked — run `clawdbot login` to scan the QR.";
|
return "Web: not linked — run `clawdbot login` to scan the QR.";
|
||||||
@@ -204,7 +227,9 @@ export function buildStatusMessage(args: StatusArgs): string {
|
|||||||
contextTokens ?? null,
|
contextTokens ?? null,
|
||||||
)}${entry?.abortedLastRun ? " • last run aborted" : ""}`;
|
)}${entry?.abortedLastRun ? " • last run aborted" : ""}`;
|
||||||
|
|
||||||
const optionsLine = `Options: thinking=${thinkLevel} | verbose=${verboseLevel} | elevated=${elevatedLevel} (set with /think <level>, /verbose on|off, /elevated on|off, /model <id>)`;
|
const optionsLine = runtime.sandboxed
|
||||||
|
? `Options: thinking=${thinkLevel} | verbose=${verboseLevel} | elevated=${elevatedLevel} (set with /think <level>, /verbose on|off, /elevated on|off, /model <id>)`
|
||||||
|
: `Options: thinking=${thinkLevel} | verbose=${verboseLevel} (set with /think <level>, /verbose on|off, /model <id>)`;
|
||||||
|
|
||||||
const modelLabel = model ? `${provider}/${model}` : "unknown";
|
const modelLabel = model ? `${provider}/${model}` : "unknown";
|
||||||
|
|
||||||
@@ -214,17 +239,21 @@ export function buildStatusMessage(args: StatusArgs): string {
|
|||||||
? `Workspace: ${shortenHomePath(args.workspaceDir)}`
|
? `Workspace: ${shortenHomePath(args.workspaceDir)}`
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const helpersLine = "Shortcuts: /new reset | /restart relink";
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"⚙️ Status",
|
"⚙️ Status",
|
||||||
webLine,
|
webLine,
|
||||||
agentLine,
|
agentLine,
|
||||||
|
runtime.line,
|
||||||
workspaceLine,
|
workspaceLine,
|
||||||
contextLine,
|
contextLine,
|
||||||
sessionLine,
|
sessionLine,
|
||||||
groupActivationLine,
|
groupActivationLine,
|
||||||
optionsLine,
|
optionsLine,
|
||||||
helpersLine,
|
]
|
||||||
].join("\n");
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildHelpMessage(): string {
|
||||||
|
return ["ℹ️ Help", "Shortcuts: /new reset | /restart relink"].join("\n");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user