fix: refresh status output
This commit is contained in:
@@ -46,6 +46,7 @@
|
|||||||
- Auto-reply: add `/reasoning on|off` to expose model reasoning blocks (italic).
|
- Auto-reply: add `/reasoning on|off` to expose model reasoning blocks (italic).
|
||||||
- Auto-reply: place reasoning blocks before the final reply text when appended.
|
- Auto-reply: place reasoning blocks before the final reply text when appended.
|
||||||
- Auto-reply: flag error payloads and improve Bun socket error messaging. Thanks @emanuelst for PR #331.
|
- Auto-reply: flag error payloads and improve Bun socket error messaging. Thanks @emanuelst for PR #331.
|
||||||
|
- Auto-reply: refresh `/status` output with build info, compact context, and queue depth.
|
||||||
- Commands: add `/stop` to the registry and route native aborts to the active chat session. Thanks @nachoiacovino for PR #295.
|
- Commands: add `/stop` to the registry and route native aborts to the active chat session. Thanks @nachoiacovino for PR #295.
|
||||||
- Commands: unify native + text chat commands behind `commands.*` config (Discord/Slack/Telegram). Thanks @thewilloftheshadow for PR #275.
|
- Commands: unify native + text chat commands behind `commands.*` config (Discord/Slack/Telegram). Thanks @thewilloftheshadow for PR #275.
|
||||||
- Auto-reply: treat steer during compaction as a follow-up, queued until compaction completes.
|
- Auto-reply: treat steer during compaction as a follow-up, queued until compaction completes.
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ describe("trigger handling", () => {
|
|||||||
makeCfg(home),
|
makeCfg(home),
|
||||||
);
|
);
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||||
expect(text).toContain("Status");
|
expect(text).toContain("ClawdBot");
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ import { enqueueSystemEvent } from "../../infra/system-events.js";
|
|||||||
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
||||||
import { resolveSendPolicy } from "../../sessions/send-policy.js";
|
import { resolveSendPolicy } from "../../sessions/send-policy.js";
|
||||||
import { normalizeE164 } from "../../utils.js";
|
import { normalizeE164 } from "../../utils.js";
|
||||||
import { resolveHeartbeatSeconds } from "../../web/reconnect.js";
|
|
||||||
import { getWebAuthAgeMs, webAuthExists } from "../../web/session.js";
|
|
||||||
import { resolveCommandAuthorization } from "../command-auth.js";
|
import { resolveCommandAuthorization } from "../command-auth.js";
|
||||||
import { shouldHandleTextCommands } from "../commands-registry.js";
|
import { shouldHandleTextCommands } from "../commands-registry.js";
|
||||||
import {
|
import {
|
||||||
@@ -51,6 +49,7 @@ 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";
|
||||||
import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
|
import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
|
||||||
|
import { getFollowupQueueDepth, resolveQueueSettings } from "./queue.js";
|
||||||
import { incrementCompactionCount } from "./session-updates.js";
|
import { incrementCompactionCount } from "./session-updates.js";
|
||||||
|
|
||||||
function resolveSessionEntryForKey(
|
function resolveSessionEntryForKey(
|
||||||
@@ -384,9 +383,18 @@ export async function handleCommands(params: {
|
|||||||
);
|
);
|
||||||
return { shouldContinue: false };
|
return { shouldContinue: false };
|
||||||
}
|
}
|
||||||
const webLinked = await webAuthExists();
|
const queueSettings = resolveQueueSettings({
|
||||||
const webAuthAgeMs = getWebAuthAgeMs();
|
cfg,
|
||||||
const heartbeatSeconds = resolveHeartbeatSeconds(cfg, undefined);
|
provider: command.provider,
|
||||||
|
sessionEntry,
|
||||||
|
});
|
||||||
|
const queueKey = sessionKey ?? sessionEntry?.sessionId;
|
||||||
|
const queueDepth = queueKey ? getFollowupQueueDepth(queueKey) : 0;
|
||||||
|
const queueOverrides = Boolean(
|
||||||
|
sessionEntry?.queueDebounceMs ??
|
||||||
|
sessionEntry?.queueCap ??
|
||||||
|
sessionEntry?.queueDrop,
|
||||||
|
);
|
||||||
const groupActivation = isGroup
|
const groupActivation = isGroup
|
||||||
? (normalizeGroupActivation(sessionEntry?.groupActivation) ??
|
? (normalizeGroupActivation(sessionEntry?.groupActivation) ??
|
||||||
defaultGroupActivation())
|
defaultGroupActivation())
|
||||||
@@ -403,11 +411,9 @@ export async function handleCommands(params: {
|
|||||||
verboseDefault: cfg.agent?.verboseDefault,
|
verboseDefault: cfg.agent?.verboseDefault,
|
||||||
elevatedDefault: cfg.agent?.elevatedDefault,
|
elevatedDefault: cfg.agent?.elevatedDefault,
|
||||||
},
|
},
|
||||||
workspaceDir,
|
|
||||||
sessionEntry,
|
sessionEntry,
|
||||||
sessionKey,
|
sessionKey,
|
||||||
sessionScope,
|
sessionScope,
|
||||||
storePath,
|
|
||||||
groupActivation,
|
groupActivation,
|
||||||
resolvedThink:
|
resolvedThink:
|
||||||
resolvedThinkLevel ?? (await resolveDefaultThinkingLevel()),
|
resolvedThinkLevel ?? (await resolveDefaultThinkingLevel()),
|
||||||
@@ -415,9 +421,15 @@ export async function handleCommands(params: {
|
|||||||
resolvedReasoning: resolvedReasoningLevel,
|
resolvedReasoning: resolvedReasoningLevel,
|
||||||
resolvedElevated: resolvedElevatedLevel,
|
resolvedElevated: resolvedElevatedLevel,
|
||||||
modelAuth: resolveModelAuthLabel(provider, cfg),
|
modelAuth: resolveModelAuthLabel(provider, cfg),
|
||||||
webLinked,
|
queue: {
|
||||||
webAuthAgeMs,
|
mode: queueSettings.mode,
|
||||||
heartbeatSeconds,
|
depth: queueDepth,
|
||||||
|
debounceMs: queueSettings.debounceMs,
|
||||||
|
cap: queueSettings.cap,
|
||||||
|
dropPolicy: queueSettings.dropPolicy,
|
||||||
|
showDetails: queueOverrides,
|
||||||
|
},
|
||||||
|
includeTranscriptUsage: false,
|
||||||
});
|
});
|
||||||
return { shouldContinue: false, reply: { text: statusText } };
|
return { shouldContinue: false, reply: { text: statusText } };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -586,3 +586,11 @@ export function resolveQueueSettings(params: {
|
|||||||
dropPolicy: dropRaw,
|
dropPolicy: dropRaw,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFollowupQueueDepth(key: string): number {
|
||||||
|
const cleaned = key.trim();
|
||||||
|
if (!cleaned) return 0;
|
||||||
|
const queue = FOLLOWUP_QUEUES.get(cleaned);
|
||||||
|
if (!queue) return 0;
|
||||||
|
return queue.items.length;
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,27 +26,23 @@ describe("buildStatusMessage", () => {
|
|||||||
},
|
},
|
||||||
sessionKey: "agent:main:main",
|
sessionKey: "agent:main:main",
|
||||||
sessionScope: "per-sender",
|
sessionScope: "per-sender",
|
||||||
storePath: "/tmp/sessions.json",
|
|
||||||
resolvedThink: "medium",
|
resolvedThink: "medium",
|
||||||
resolvedVerbose: "off",
|
resolvedVerbose: "off",
|
||||||
|
queue: { mode: "collect", depth: 0 },
|
||||||
now: 10 * 60_000, // 10 minutes later
|
now: 10 * 60_000, // 10 minutes later
|
||||||
webLinked: true,
|
|
||||||
webAuthAgeMs: 5 * 60_000,
|
|
||||||
heartbeatSeconds: 45,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(text).toContain("⚙️ Status");
|
expect(text).toContain("🦞 ClawdBot");
|
||||||
expect(text).toContain("Agent: embedded pi");
|
expect(text).toContain("🧠 Model:");
|
||||||
expect(text).toContain("Runtime: direct");
|
expect(text).toContain("Runtime: direct");
|
||||||
expect(text).toContain("Context: 16k/32k (50%)");
|
expect(text).toContain("Context: 16k/32k (50%)");
|
||||||
|
expect(text).toContain("🧹 Compactions: 2");
|
||||||
expect(text).toContain("Session: agent:main:main");
|
expect(text).toContain("Session: agent:main:main");
|
||||||
expect(text).toContain("compactions 2");
|
expect(text).toContain("updated 10m ago");
|
||||||
expect(text).toContain("Web: linked");
|
expect(text).toContain("Think: medium");
|
||||||
expect(text).toContain("heartbeat 45s");
|
expect(text).toContain("Verbose: off");
|
||||||
expect(text).toContain("thinking=medium");
|
expect(text).toContain("Elevated: on");
|
||||||
expect(text).toContain("verbose=off");
|
expect(text).toContain("Queue: collect");
|
||||||
expect(text).not.toContain("Shortcuts:");
|
|
||||||
expect(text).not.toContain("set with");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles missing agent config gracefully", () => {
|
it("handles missing agent config gracefully", () => {
|
||||||
@@ -56,9 +52,9 @@ describe("buildStatusMessage", () => {
|
|||||||
webLinked: false,
|
webLinked: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(text).toContain("Agent: embedded pi");
|
expect(text).toContain("🧠 Model:");
|
||||||
expect(text).toContain("Context:");
|
expect(text).toContain("Context:");
|
||||||
expect(text).toContain("Web: not linked");
|
expect(text).toContain("Queue:");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes group activation for group sessions", () => {
|
it("includes group activation for group sessions", () => {
|
||||||
@@ -72,10 +68,31 @@ describe("buildStatusMessage", () => {
|
|||||||
},
|
},
|
||||||
sessionKey: "agent:main:whatsapp:group:123@g.us",
|
sessionKey: "agent:main:whatsapp:group:123@g.us",
|
||||||
sessionScope: "per-sender",
|
sessionScope: "per-sender",
|
||||||
webLinked: true,
|
queue: { mode: "collect", depth: 0 },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(text).toContain("Group activation: always");
|
expect(text).toContain("Activation: always");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows queue details when overridden", () => {
|
||||||
|
const text = buildStatusMessage({
|
||||||
|
agent: {},
|
||||||
|
sessionEntry: { sessionId: "q1", updatedAt: 0 },
|
||||||
|
sessionKey: "agent:main:main",
|
||||||
|
sessionScope: "per-sender",
|
||||||
|
queue: {
|
||||||
|
mode: "collect",
|
||||||
|
depth: 3,
|
||||||
|
debounceMs: 2000,
|
||||||
|
cap: 5,
|
||||||
|
dropPolicy: "old",
|
||||||
|
showDetails: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(text).toContain(
|
||||||
|
"Queue: collect (depth 3 · debounce 2s · cap 5 · drop old)",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prefers cached prompt tokens from the session log", async () => {
|
it("prefers cached prompt tokens from the session log", async () => {
|
||||||
@@ -88,14 +105,6 @@ describe("buildStatusMessage", () => {
|
|||||||
"./status.js"
|
"./status.js"
|
||||||
);
|
);
|
||||||
|
|
||||||
const storePath = path.join(
|
|
||||||
dir,
|
|
||||||
".clawdbot",
|
|
||||||
"agents",
|
|
||||||
"main",
|
|
||||||
"sessions",
|
|
||||||
"sessions.json",
|
|
||||||
);
|
|
||||||
const sessionId = "sess-1";
|
const sessionId = "sess-1";
|
||||||
const logPath = path.join(
|
const logPath = path.join(
|
||||||
dir,
|
dir,
|
||||||
@@ -141,8 +150,8 @@ describe("buildStatusMessage", () => {
|
|||||||
},
|
},
|
||||||
sessionKey: "agent:main:main",
|
sessionKey: "agent:main:main",
|
||||||
sessionScope: "per-sender",
|
sessionScope: "per-sender",
|
||||||
storePath,
|
queue: { mode: "collect", depth: 0 },
|
||||||
webLinked: true,
|
includeTranscriptUsage: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(text).toContain("Context: 1.0k/32k");
|
expect(text).toContain("Context: 1.0k/32k");
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
import { lookupContextTokens } from "../agents/context.js";
|
import { lookupContextTokens } from "../agents/context.js";
|
||||||
import {
|
import {
|
||||||
@@ -19,7 +20,7 @@ import {
|
|||||||
type SessionEntry,
|
type SessionEntry,
|
||||||
type SessionScope,
|
type SessionScope,
|
||||||
} from "../config/sessions.js";
|
} from "../config/sessions.js";
|
||||||
import { shortenHomePath } from "../utils.js";
|
import { VERSION } from "../version.js";
|
||||||
import type {
|
import type {
|
||||||
ElevatedLevel,
|
ElevatedLevel,
|
||||||
ReasoningLevel,
|
ReasoningLevel,
|
||||||
@@ -29,23 +30,29 @@ import type {
|
|||||||
|
|
||||||
type AgentConfig = NonNullable<ClawdbotConfig["agent"]>;
|
type AgentConfig = NonNullable<ClawdbotConfig["agent"]>;
|
||||||
|
|
||||||
|
type QueueStatus = {
|
||||||
|
mode?: string;
|
||||||
|
depth?: number;
|
||||||
|
debounceMs?: number;
|
||||||
|
cap?: number;
|
||||||
|
dropPolicy?: string;
|
||||||
|
showDetails?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type StatusArgs = {
|
type StatusArgs = {
|
||||||
agent: AgentConfig;
|
agent: AgentConfig;
|
||||||
workspaceDir?: string;
|
|
||||||
sessionEntry?: SessionEntry;
|
sessionEntry?: SessionEntry;
|
||||||
sessionKey?: string;
|
sessionKey?: string;
|
||||||
sessionScope?: SessionScope;
|
sessionScope?: SessionScope;
|
||||||
storePath?: string;
|
|
||||||
groupActivation?: "mention" | "always";
|
groupActivation?: "mention" | "always";
|
||||||
resolvedThink?: ThinkLevel;
|
resolvedThink?: ThinkLevel;
|
||||||
resolvedVerbose?: VerboseLevel;
|
resolvedVerbose?: VerboseLevel;
|
||||||
resolvedReasoning?: ReasoningLevel;
|
resolvedReasoning?: ReasoningLevel;
|
||||||
resolvedElevated?: ElevatedLevel;
|
resolvedElevated?: ElevatedLevel;
|
||||||
modelAuth?: string;
|
modelAuth?: string;
|
||||||
|
queue?: QueueStatus;
|
||||||
|
includeTranscriptUsage?: boolean;
|
||||||
now?: number;
|
now?: number;
|
||||||
webLinked?: boolean;
|
|
||||||
webAuthAgeMs?: number | null;
|
|
||||||
heartbeatSeconds?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatAge = (ms?: number | null) => {
|
const formatAge = (ms?: number | null) => {
|
||||||
@@ -84,6 +91,97 @@ export const formatContextUsageShort = (
|
|||||||
contextTokens: number | null | undefined,
|
contextTokens: number | null | undefined,
|
||||||
) => `Context ${formatTokens(total, contextTokens ?? null)}`;
|
) => `Context ${formatTokens(total, contextTokens ?? null)}`;
|
||||||
|
|
||||||
|
const formatCommit = (value?: string | null) => {
|
||||||
|
if (!value) return null;
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return null;
|
||||||
|
return trimmed.length > 7 ? trimmed.slice(0, 7) : trimmed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveGitHead = (startDir: string) => {
|
||||||
|
let current = startDir;
|
||||||
|
for (let i = 0; i < 12; i += 1) {
|
||||||
|
const gitPath = path.join(current, ".git");
|
||||||
|
try {
|
||||||
|
const stat = fs.statSync(gitPath);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
return path.join(gitPath, "HEAD");
|
||||||
|
}
|
||||||
|
if (stat.isFile()) {
|
||||||
|
const raw = fs.readFileSync(gitPath, "utf-8");
|
||||||
|
const match = raw.match(/gitdir:\s*(.+)/i);
|
||||||
|
if (match?.[1]) {
|
||||||
|
const resolved = path.resolve(current, match[1].trim());
|
||||||
|
return path.join(resolved, "HEAD");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore missing .git at this level
|
||||||
|
}
|
||||||
|
const parent = path.dirname(current);
|
||||||
|
if (parent === current) break;
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
let cachedCommit: string | null | undefined;
|
||||||
|
const resolveCommitHash = () => {
|
||||||
|
if (cachedCommit !== undefined) return cachedCommit;
|
||||||
|
const envCommit =
|
||||||
|
process.env.GIT_COMMIT?.trim() || process.env.GIT_SHA?.trim();
|
||||||
|
const normalized = formatCommit(envCommit);
|
||||||
|
if (normalized) {
|
||||||
|
cachedCommit = normalized;
|
||||||
|
return cachedCommit;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const headPath = resolveGitHead(process.cwd());
|
||||||
|
if (!headPath) {
|
||||||
|
cachedCommit = null;
|
||||||
|
return cachedCommit;
|
||||||
|
}
|
||||||
|
const head = fs.readFileSync(headPath, "utf-8").trim();
|
||||||
|
if (!head) {
|
||||||
|
cachedCommit = null;
|
||||||
|
return cachedCommit;
|
||||||
|
}
|
||||||
|
if (head.startsWith("ref:")) {
|
||||||
|
const ref = head.replace(/^ref:\s*/i, "").trim();
|
||||||
|
const refPath = path.resolve(path.dirname(headPath), ref);
|
||||||
|
const refHash = fs.readFileSync(refPath, "utf-8").trim();
|
||||||
|
cachedCommit = formatCommit(refHash);
|
||||||
|
return cachedCommit;
|
||||||
|
}
|
||||||
|
cachedCommit = formatCommit(head);
|
||||||
|
return cachedCommit;
|
||||||
|
} catch {
|
||||||
|
cachedCommit = null;
|
||||||
|
return cachedCommit;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatQueueDetails = (queue?: QueueStatus) => {
|
||||||
|
if (!queue) return "";
|
||||||
|
const depth = typeof queue.depth === "number" ? `depth ${queue.depth}` : null;
|
||||||
|
if (!queue.showDetails) {
|
||||||
|
return depth ? ` (${depth})` : "";
|
||||||
|
}
|
||||||
|
const detailParts: string[] = [];
|
||||||
|
if (depth) detailParts.push(depth);
|
||||||
|
if (typeof queue.debounceMs === "number") {
|
||||||
|
const ms = Math.max(0, Math.round(queue.debounceMs));
|
||||||
|
const label =
|
||||||
|
ms >= 1000
|
||||||
|
? `${ms % 1000 === 0 ? ms / 1000 : (ms / 1000).toFixed(1)}s`
|
||||||
|
: `${ms}ms`;
|
||||||
|
detailParts.push(`debounce ${label}`);
|
||||||
|
}
|
||||||
|
if (typeof queue.cap === "number") detailParts.push(`cap ${queue.cap}`);
|
||||||
|
if (queue.dropPolicy) detailParts.push(`drop ${queue.dropPolicy}`);
|
||||||
|
return detailParts.length ? ` (${detailParts.join(" · ")})` : "";
|
||||||
|
};
|
||||||
|
|
||||||
const readUsageFromSessionLog = (
|
const readUsageFromSessionLog = (
|
||||||
sessionId?: string,
|
sessionId?: string,
|
||||||
):
|
):
|
||||||
@@ -164,15 +262,17 @@ export function buildStatusMessage(args: StatusArgs): string {
|
|||||||
|
|
||||||
// Prefer prompt-size tokens from the session transcript when it looks larger
|
// Prefer prompt-size tokens from the session transcript when it looks larger
|
||||||
// (cached prompt tokens are often missing from agent meta/store).
|
// (cached prompt tokens are often missing from agent meta/store).
|
||||||
const logUsage = readUsageFromSessionLog(entry?.sessionId);
|
if (args.includeTranscriptUsage) {
|
||||||
if (logUsage) {
|
const logUsage = readUsageFromSessionLog(entry?.sessionId);
|
||||||
const candidate = logUsage.promptTokens || logUsage.total;
|
if (logUsage) {
|
||||||
if (!totalTokens || totalTokens === 0 || candidate > totalTokens) {
|
const candidate = logUsage.promptTokens || logUsage.total;
|
||||||
totalTokens = candidate;
|
if (!totalTokens || totalTokens === 0 || candidate > totalTokens) {
|
||||||
}
|
totalTokens = candidate;
|
||||||
if (!model) model = logUsage.model ?? model;
|
}
|
||||||
if (!contextTokens && logUsage.model) {
|
if (!model) model = logUsage.model ?? model;
|
||||||
contextTokens = lookupContextTokens(logUsage.model) ?? contextTokens;
|
if (!contextTokens && logUsage.model) {
|
||||||
|
contextTokens = lookupContextTokens(logUsage.model) ?? contextTokens;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,8 +288,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
|||||||
|
|
||||||
const runtime = (() => {
|
const runtime = (() => {
|
||||||
const sandboxMode = args.agent?.sandbox?.mode ?? "off";
|
const sandboxMode = args.agent?.sandbox?.mode ?? "off";
|
||||||
if (sandboxMode === "off")
|
if (sandboxMode === "off") return { label: "direct" };
|
||||||
return { line: "Runtime: direct", sandboxed: false };
|
|
||||||
const sessionScope = args.sessionScope ?? "per-sender";
|
const sessionScope = args.sessionScope ?? "per-sender";
|
||||||
const mainKey = resolveMainSessionKey({
|
const mainKey = resolveMainSessionKey({
|
||||||
session: { scope: sessionScope },
|
session: { scope: sessionScope },
|
||||||
@@ -199,35 +298,17 @@ export function buildStatusMessage(args: StatusArgs): string {
|
|||||||
? sandboxMode === "all" || sessionKey !== mainKey.trim()
|
? sandboxMode === "all" || sessionKey !== mainKey.trim()
|
||||||
: false;
|
: false;
|
||||||
const runtime = sandboxed ? "docker" : sessionKey ? "direct" : "unknown";
|
const runtime = sandboxed ? "docker" : sessionKey ? "direct" : "unknown";
|
||||||
const suffix = sandboxed ? ` • elevated ${elevatedLevel}` : "";
|
|
||||||
return {
|
return {
|
||||||
line: `Runtime: ${runtime} (sandbox ${sandboxMode})${suffix}`,
|
label: `${runtime}/${sandboxMode}`,
|
||||||
sandboxed,
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const webLine = (() => {
|
const updatedAt = entry?.updatedAt;
|
||||||
if (args.webLinked === false) {
|
|
||||||
return "Web: not linked — run `clawdbot login` to scan the QR.";
|
|
||||||
}
|
|
||||||
const authAge = formatAge(args.webAuthAgeMs);
|
|
||||||
const heartbeat =
|
|
||||||
typeof args.heartbeatSeconds === "number"
|
|
||||||
? ` • heartbeat ${args.heartbeatSeconds}s`
|
|
||||||
: "";
|
|
||||||
return `Web: linked • auth refreshed ${authAge}${heartbeat}`;
|
|
||||||
})();
|
|
||||||
|
|
||||||
const sessionLine = [
|
const sessionLine = [
|
||||||
`Session: ${args.sessionKey ?? "unknown"}`,
|
`Session: ${args.sessionKey ?? "unknown"}`,
|
||||||
`scope ${args.sessionScope ?? "per-sender"}`,
|
typeof updatedAt === "number"
|
||||||
entry?.updatedAt
|
? `updated ${formatAge(now - updatedAt)}`
|
||||||
? `updated ${formatAge(now - entry.updatedAt)}`
|
|
||||||
: "no activity",
|
: "no activity",
|
||||||
typeof entry?.compactionCount === "number"
|
|
||||||
? `compactions ${entry.compactionCount}`
|
|
||||||
: undefined,
|
|
||||||
args.storePath ? `store ${shortenHomePath(args.storePath)}` : undefined,
|
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" • ");
|
.join(" • ");
|
||||||
@@ -238,39 +319,42 @@ export function buildStatusMessage(args: StatusArgs): string {
|
|||||||
Boolean(args.sessionKey?.includes(":group:")) ||
|
Boolean(args.sessionKey?.includes(":group:")) ||
|
||||||
Boolean(args.sessionKey?.includes(":channel:")) ||
|
Boolean(args.sessionKey?.includes(":channel:")) ||
|
||||||
Boolean(args.sessionKey?.startsWith("group:"));
|
Boolean(args.sessionKey?.startsWith("group:"));
|
||||||
const groupActivationLine = isGroupSession
|
const groupActivationValue = isGroupSession
|
||||||
? `Group activation: ${args.groupActivation ?? entry?.groupActivation ?? "mention"}`
|
? (args.groupActivation ?? entry?.groupActivation ?? "mention")
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const contextLine = `Context: ${formatTokens(
|
const contextLine = [
|
||||||
totalTokens,
|
`Context: ${formatTokens(totalTokens, contextTokens ?? null)}`,
|
||||||
contextTokens ?? null,
|
`🧹 Compactions: ${entry?.compactionCount ?? 0}`,
|
||||||
)}${entry?.abortedLastRun ? " • last run aborted" : ""}`;
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" · ");
|
||||||
|
|
||||||
const optionsLine = runtime.sandboxed
|
const queueMode = args.queue?.mode ?? "unknown";
|
||||||
? `Options: thinking=${thinkLevel} | verbose=${verboseLevel} | reasoning=${reasoningLevel} | elevated=${elevatedLevel}`
|
const queueDetails = formatQueueDetails(args.queue);
|
||||||
: `Options: thinking=${thinkLevel} | verbose=${verboseLevel} | reasoning=${reasoningLevel}`;
|
const optionParts = [
|
||||||
|
`Runtime: ${runtime.label}`,
|
||||||
|
`Think: ${thinkLevel}`,
|
||||||
|
`Verbose: ${verboseLevel}`,
|
||||||
|
reasoningLevel !== "off" ? `Reasoning: ${reasoningLevel}` : null,
|
||||||
|
`Elevated: ${elevatedLevel}`,
|
||||||
|
groupActivationValue ? `👥 Activation: ${groupActivationValue}` : null,
|
||||||
|
`🪢 Queue: ${queueMode}${queueDetails}`,
|
||||||
|
];
|
||||||
|
const optionsLine = optionParts.filter(Boolean).join(" · ");
|
||||||
|
|
||||||
const modelLabel = model ? `${provider}/${model}` : "unknown";
|
const modelLabel = model ? `${provider}/${model}` : "unknown";
|
||||||
|
const authLabel = args.modelAuth ? ` · 🔑 ${args.modelAuth}` : "";
|
||||||
const agentLine = `Agent: embedded pi • ${modelLabel}`;
|
const modelLine = `🧠 Model: ${modelLabel}${authLabel}`;
|
||||||
const authLine = args.modelAuth ? `Model auth: ${args.modelAuth}` : undefined;
|
const commit = resolveCommitHash();
|
||||||
|
const versionLine = `🦞 ClawdBot ${VERSION}${commit ? ` (${commit})` : ""}`;
|
||||||
const workspaceLine = args.workspaceDir
|
|
||||||
? `Workspace: ${shortenHomePath(args.workspaceDir)}`
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"⚙️ Status",
|
versionLine,
|
||||||
webLine,
|
modelLine,
|
||||||
agentLine,
|
`📚 ${contextLine}`,
|
||||||
authLine,
|
`🧵 ${sessionLine}`,
|
||||||
runtime.line,
|
`⚙️ ${optionsLine}`,
|
||||||
workspaceLine,
|
|
||||||
contextLine,
|
|
||||||
sessionLine,
|
|
||||||
groupActivationLine,
|
|
||||||
optionsLine,
|
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user