switchroom 0.14.53 → 0.14.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +1 -0
- package/dist/auth-broker/index.js +1 -0
- package/dist/cli/notion-write-pretool.mjs +1 -0
- package/dist/cli/switchroom.js +6 -2
- package/dist/host-control/main.js +1 -0
- package/dist/vault/approvals/kernel-server.js +1 -0
- package/dist/vault/broker/server.js +1 -0
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +37 -14
- package/telegram-plugin/gateway/gateway.ts +48 -13
- package/telegram-plugin/tests/tool-activity-summary.test.ts +36 -0
- package/telegram-plugin/tool-activity-summary.ts +9 -4
|
@@ -11049,6 +11049,7 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11049
11049
|
rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
|
|
11050
11050
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI — " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events — stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
11051
11051
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
|
|
11052
|
+
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message — Reading X, Searching the web for Y, …) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) — no post-then-delete. Per-agent " + "override; cascades defaults → profile → agent (per-key)."),
|
|
11052
11053
|
hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
|
|
11053
11054
|
orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
|
|
11054
11055
|
cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
|
|
@@ -11049,6 +11049,7 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11049
11049
|
rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
|
|
11050
11050
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI — " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events — stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
11051
11051
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
|
|
11052
|
+
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message — Reading X, Searching the web for Y, …) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) — no post-then-delete. Per-agent " + "override; cascades defaults → profile → agent (per-key)."),
|
|
11052
11053
|
hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
|
|
11053
11054
|
orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
|
|
11054
11055
|
cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
|
|
@@ -11797,6 +11797,7 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11797
11797
|
rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
|
|
11798
11798
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI \u2014 " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events \u2014 stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
11799
11799
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
|
|
11800
|
+
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message \u2014 Reading X, Searching the web for Y, \u2026) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) \u2014 no post-then-delete. Per-agent " + "override; cascades defaults \u2192 profile \u2192 agent (per-key)."),
|
|
11800
11801
|
hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
|
|
11801
11802
|
orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
|
|
11802
11803
|
cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -13613,6 +13613,7 @@ var init_schema = __esm(() => {
|
|
|
13613
13613
|
rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
|
|
13614
13614
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI \u2014 " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events \u2014 stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
13615
13615
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
|
|
13616
|
+
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message \u2014 Reading X, Searching the web for Y, \u2026) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) \u2014 no post-then-delete. Per-agent " + "override; cascades defaults \u2192 profile \u2192 agent (per-key)."),
|
|
13616
13617
|
hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
|
|
13617
13618
|
orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
|
|
13618
13619
|
cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
|
|
@@ -49462,8 +49463,8 @@ var {
|
|
|
49462
49463
|
} = import__.default;
|
|
49463
49464
|
|
|
49464
49465
|
// src/build-info.ts
|
|
49465
|
-
var VERSION = "0.14.
|
|
49466
|
-
var COMMIT_SHA = "
|
|
49466
|
+
var VERSION = "0.14.54";
|
|
49467
|
+
var COMMIT_SHA = "b6f1a934";
|
|
49467
49468
|
|
|
49468
49469
|
// src/cli/agent.ts
|
|
49469
49470
|
init_source();
|
|
@@ -50868,6 +50869,9 @@ function channelsToEnv(agent) {
|
|
|
50868
50869
|
if (tg.edit_budget_threshold !== undefined) {
|
|
50869
50870
|
out.SWITCHROOM_TG_EDIT_BUDGET_THRESHOLD = String(tg.edit_budget_threshold);
|
|
50870
50871
|
}
|
|
50872
|
+
if (tg.clear_status_on_completion !== undefined) {
|
|
50873
|
+
out.SWITCHROOM_TG_CLEAR_STATUS_ON_COMPLETION = tg.clear_status_on_completion ? "1" : "0";
|
|
50874
|
+
}
|
|
50871
50875
|
return out;
|
|
50872
50876
|
}
|
|
50873
50877
|
function buildRepoEnvVars(_agentName, agentDir, agent) {
|
|
@@ -13784,6 +13784,7 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
13784
13784
|
rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
|
|
13785
13785
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI — " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events — stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
13786
13786
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
|
|
13787
|
+
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message — Reading X, Searching the web for Y, …) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) — no post-then-delete. Per-agent " + "override; cascades defaults → profile → agent (per-key)."),
|
|
13787
13788
|
hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
|
|
13788
13789
|
orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
|
|
13789
13790
|
cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
|
|
@@ -11370,6 +11370,7 @@ var init_schema = __esm(() => {
|
|
|
11370
11370
|
rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
|
|
11371
11371
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI — " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events — stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
11372
11372
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
|
|
11373
|
+
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message — Reading X, Searching the web for Y, …) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) — no post-then-delete. Per-agent " + "override; cascades defaults → profile → agent (per-key)."),
|
|
11373
11374
|
hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
|
|
11374
11375
|
orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
|
|
11375
11376
|
cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
|
|
@@ -11370,6 +11370,7 @@ var init_schema = __esm(() => {
|
|
|
11370
11370
|
rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
|
|
11371
11371
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI — " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events — stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
11372
11372
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
|
|
11373
|
+
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message — Reading X, Searching the web for Y, …) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) — no post-then-delete. Per-agent " + "override; cascades defaults → profile → agent (per-key)."),
|
|
11373
11374
|
hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
|
|
11374
11375
|
orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
|
|
11375
11376
|
cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
|
package/package.json
CHANGED
|
@@ -23779,6 +23779,7 @@ var init_schema = __esm(() => {
|
|
|
23779
23779
|
rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
|
|
23780
23780
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI \u2014 " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events \u2014 stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
23781
23781
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
|
|
23782
|
+
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message \u2014 Reading X, Searching the web for Y, \u2026) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) \u2014 no post-then-delete. Per-agent " + "override; cascades defaults \u2192 profile \u2192 agent (per-key)."),
|
|
23782
23783
|
hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
|
|
23783
23784
|
orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
|
|
23784
23785
|
cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
|
|
@@ -32728,7 +32729,7 @@ var MIRROR_MAX_LINES = 6;
|
|
|
32728
32729
|
function escapeFeedHtml(s) {
|
|
32729
32730
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
32730
32731
|
}
|
|
32731
|
-
function renderActivityFeed(lines) {
|
|
32732
|
+
function renderActivityFeed(lines, final = false) {
|
|
32732
32733
|
if (lines.length === 0)
|
|
32733
32734
|
return null;
|
|
32734
32735
|
const shown = lines.slice(-MIRROR_MAX_LINES);
|
|
@@ -32739,7 +32740,7 @@ function renderActivityFeed(lines) {
|
|
|
32739
32740
|
const lastIdx = shown.length - 1;
|
|
32740
32741
|
shown.forEach((l, i) => {
|
|
32741
32742
|
const esc = escapeFeedHtml(l);
|
|
32742
|
-
out.push(i === lastIdx ? `<b>\u2192 ${esc}</b>` : `<i>\u2713 ${esc}</i>`);
|
|
32743
|
+
out.push(i === lastIdx && !final ? `<b>\u2192 ${esc}</b>` : `<i>\u2713 ${esc}</i>`);
|
|
32743
32744
|
});
|
|
32744
32745
|
return out.join(`
|
|
32745
32746
|
`);
|
|
@@ -32747,10 +32748,10 @@ function renderActivityFeed(lines) {
|
|
|
32747
32748
|
var NESTED_MAX_LINES = 4;
|
|
32748
32749
|
var NESTED_LINE_MAX = 90;
|
|
32749
32750
|
var NESTED_PREFIX = " \u21b3 ";
|
|
32750
|
-
function renderActivityFeedWithNested(lines, childLines) {
|
|
32751
|
+
function renderActivityFeedWithNested(lines, childLines, final = false) {
|
|
32751
32752
|
const children = childLines.map((s) => s.trim()).filter((s) => s.length > 0);
|
|
32752
32753
|
if (children.length === 0)
|
|
32753
|
-
return renderActivityFeed(lines);
|
|
32754
|
+
return renderActivityFeed(lines, final);
|
|
32754
32755
|
const out = [];
|
|
32755
32756
|
const shownParent = lines.slice(-MIRROR_MAX_LINES);
|
|
32756
32757
|
const hiddenParent = lines.length - shownParent.length;
|
|
@@ -32766,7 +32767,7 @@ function renderActivityFeedWithNested(lines, childLines) {
|
|
|
32766
32767
|
shownChild.forEach((l, i) => {
|
|
32767
32768
|
const t = l.length > NESTED_LINE_MAX ? l.slice(0, NESTED_LINE_MAX - 1) + "\u2026" : l;
|
|
32768
32769
|
const esc = escapeFeedHtml(t);
|
|
32769
|
-
out.push(i === lastChildIdx ? `${NESTED_PREFIX}<b>\u2192 ${esc}</b>` : `${NESTED_PREFIX}<i>${esc}</i>`);
|
|
32770
|
+
out.push(i === lastChildIdx && !final ? `${NESTED_PREFIX}<b>\u2192 ${esc}</b>` : `${NESTED_PREFIX}<i>${esc}</i>`);
|
|
32770
32771
|
});
|
|
32771
32772
|
return out.length > 0 ? out.join(`
|
|
32772
32773
|
`) : null;
|
|
@@ -52166,10 +52167,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
52166
52167
|
}
|
|
52167
52168
|
|
|
52168
52169
|
// ../src/build-info.ts
|
|
52169
|
-
var VERSION = "0.14.
|
|
52170
|
-
var COMMIT_SHA = "
|
|
52171
|
-
var COMMIT_DATE = "2026-06-
|
|
52172
|
-
var LATEST_PR =
|
|
52170
|
+
var VERSION = "0.14.54";
|
|
52171
|
+
var COMMIT_SHA = "b6f1a934";
|
|
52172
|
+
var COMMIT_DATE = "2026-06-03T20:22:35Z";
|
|
52173
|
+
var LATEST_PR = 2133;
|
|
52173
52174
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
52174
52175
|
|
|
52175
52176
|
// gateway/boot-version.ts
|
|
@@ -54317,6 +54318,13 @@ var STREAM_THROTTLE_MS_OVERRIDE = (() => {
|
|
|
54317
54318
|
})();
|
|
54318
54319
|
var TURN_FLUSH_SAFETY_ENABLED = isTurnFlushSafetyEnabled();
|
|
54319
54320
|
var ANSWER_STREAM_VISIBLE_ENABLED = parseVisibleAnswerStreamEnabled(process.env.SWITCHROOM_VISIBLE_ANSWER_STREAM);
|
|
54321
|
+
var CLEAR_STATUS_ON_COMPLETION = (() => {
|
|
54322
|
+
const raw = process.env.SWITCHROOM_TG_CLEAR_STATUS_ON_COMPLETION;
|
|
54323
|
+
if (raw == null)
|
|
54324
|
+
return false;
|
|
54325
|
+
const v = raw.trim().toLowerCase();
|
|
54326
|
+
return v === "1" || v === "true" || v === "on" || v === "yes";
|
|
54327
|
+
})();
|
|
54320
54328
|
var progressDriver = null;
|
|
54321
54329
|
var unpinProgressCardForChat = null;
|
|
54322
54330
|
var getPinnedProgressCardMessageId = null;
|
|
@@ -56874,12 +56882,12 @@ function closeProgressLane(chatId, threadId) {
|
|
|
56874
56882
|
}
|
|
56875
56883
|
}
|
|
56876
56884
|
var FOREGROUND_SUBAGENT_ACCUM_MAX = 12;
|
|
56877
|
-
function composeTurnActivity(turn) {
|
|
56885
|
+
function composeTurnActivity(turn, final = false) {
|
|
56878
56886
|
const childLines = [];
|
|
56879
56887
|
for (const narrative of turn.foregroundSubAgents.values()) {
|
|
56880
56888
|
childLines.push(...narrative);
|
|
56881
56889
|
}
|
|
56882
|
-
return renderActivityFeedWithNested(turn.mirrorLines, childLines);
|
|
56890
|
+
return renderActivityFeedWithNested(turn.mirrorLines, childLines, final);
|
|
56883
56891
|
}
|
|
56884
56892
|
async function drainActivitySummary(turn) {
|
|
56885
56893
|
try {
|
|
@@ -56923,13 +56931,28 @@ function clearActivitySummary(turn) {
|
|
|
56923
56931
|
const thread = turn.sessionThreadId;
|
|
56924
56932
|
const inFlight = turn.activityInFlight ?? Promise.resolve();
|
|
56925
56933
|
inFlight.then(async () => {
|
|
56926
|
-
if (turn.activityMessageId
|
|
56927
|
-
|
|
56928
|
-
|
|
56934
|
+
if (turn.activityMessageId == null)
|
|
56935
|
+
return;
|
|
56936
|
+
const id = turn.activityMessageId;
|
|
56937
|
+
turn.activityMessageId = null;
|
|
56938
|
+
if (CLEAR_STATUS_ON_COMPLETION) {
|
|
56929
56939
|
try {
|
|
56930
56940
|
await robustApiCall(() => bot.api.deleteMessage(chat, id), { chat_id: chat, ...thread != null ? { threadId: thread } : {}, verb: "activity-summary.delete" });
|
|
56931
56941
|
} catch (err) {
|
|
56932
56942
|
process.stderr.write(`telegram gateway: activity-summary delete failed: ${err}
|
|
56943
|
+
`);
|
|
56944
|
+
}
|
|
56945
|
+
return;
|
|
56946
|
+
}
|
|
56947
|
+
const finalHtml = composeTurnActivity(turn, true);
|
|
56948
|
+
if (finalHtml == null)
|
|
56949
|
+
return;
|
|
56950
|
+
try {
|
|
56951
|
+
await robustApiCall(() => bot.api.editMessageText(chat, id, finalHtml, { parse_mode: "HTML" }), { chat_id: chat, ...thread != null ? { threadId: thread } : {}, verb: "activity-summary.finalize" });
|
|
56952
|
+
} catch (err) {
|
|
56953
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
56954
|
+
if (!msg.includes("message is not modified")) {
|
|
56955
|
+
process.stderr.write(`telegram gateway: activity-summary finalize failed: ${msg}
|
|
56933
56956
|
`);
|
|
56934
56957
|
}
|
|
56935
56958
|
}
|
|
@@ -3767,6 +3767,19 @@ const ANSWER_STREAM_VISIBLE_ENABLED = parseVisibleAnswerStreamEnabled(
|
|
|
3767
3767
|
process.env.SWITCHROOM_VISIBLE_ANSWER_STREAM,
|
|
3768
3768
|
)
|
|
3769
3769
|
|
|
3770
|
+
// Whether to DELETE the activity/status feed when the final answer lands.
|
|
3771
|
+
// Default OFF (2026-06-04, operator request): the status message stays in the
|
|
3772
|
+
// chat as a record (finalized to an all-done render) instead of being deleted
|
|
3773
|
+
// — no post-then-delete flicker. Opt IN per agent via
|
|
3774
|
+
// channels.telegram.clear_status_on_completion: true (→ this env). See the
|
|
3775
|
+
// clearActivitySummary doc-comment.
|
|
3776
|
+
const CLEAR_STATUS_ON_COMPLETION = (() => {
|
|
3777
|
+
const raw = process.env.SWITCHROOM_TG_CLEAR_STATUS_ON_COMPLETION
|
|
3778
|
+
if (raw == null) return false
|
|
3779
|
+
const v = raw.trim().toLowerCase()
|
|
3780
|
+
return v === '1' || v === 'true' || v === 'on' || v === 'yes'
|
|
3781
|
+
})()
|
|
3782
|
+
|
|
3770
3783
|
// Activity feed. The gateway streams a live "what it's doing" tool-activity
|
|
3771
3784
|
// feed for every turn. The PreToolUse sidecar emits a `tool_label` per tool
|
|
3772
3785
|
// call (flush-independent, so it stays real-time on fast/clustered-tool
|
|
@@ -8015,12 +8028,12 @@ const FOREGROUND_SUBAGENT_ACCUM_MAX = 12
|
|
|
8015
8028
|
* order; the single-sub-agent common case nests precisely under its
|
|
8016
8029
|
* Delegating line.
|
|
8017
8030
|
*/
|
|
8018
|
-
function composeTurnActivity(turn: CurrentTurn): string | null {
|
|
8031
|
+
function composeTurnActivity(turn: CurrentTurn, final = false): string | null {
|
|
8019
8032
|
const childLines: string[] = []
|
|
8020
8033
|
for (const narrative of turn.foregroundSubAgents.values()) {
|
|
8021
8034
|
childLines.push(...narrative)
|
|
8022
8035
|
}
|
|
8023
|
-
return renderActivityFeedWithNested(turn.mirrorLines, childLines)
|
|
8036
|
+
return renderActivityFeedWithNested(turn.mirrorLines, childLines, final)
|
|
8024
8037
|
}
|
|
8025
8038
|
|
|
8026
8039
|
/**
|
|
@@ -8094,24 +8107,30 @@ async function drainActivitySummary(turn: CurrentTurn): Promise<void> {
|
|
|
8094
8107
|
}
|
|
8095
8108
|
|
|
8096
8109
|
/**
|
|
8097
|
-
*
|
|
8098
|
-
*
|
|
8099
|
-
*
|
|
8100
|
-
*
|
|
8101
|
-
*
|
|
8110
|
+
* Reconcile the activity summary when the model's reply tool takes over as the
|
|
8111
|
+
* authoritative surface. Awaits any in-flight render so we don't race a stale
|
|
8112
|
+
* write, then EITHER:
|
|
8113
|
+
* - FINALIZE (default, CLEAR_STATUS_ON_COMPLETION=false): edit the message to
|
|
8114
|
+
* a terminal all-done render (no "→ in-progress" line) and stop tracking it
|
|
8115
|
+
* — the status stays in the chat as a record beside the reply. No delete.
|
|
8116
|
+
* - DELETE (CLEAR_STATUS_ON_COMPLETION=true, opt-in via
|
|
8117
|
+
* channels.telegram.clear_status_on_completion): remove the message so only
|
|
8118
|
+
* the reply remains (the pre-2026-06 behaviour).
|
|
8119
|
+
* Idempotent + best-effort — failures stderr-log but don't block.
|
|
8102
8120
|
*
|
|
8103
|
-
* Called on the first reply (hand-off
|
|
8104
|
-
*
|
|
8105
|
-
*
|
|
8121
|
+
* Called on the first reply (hand-off) and again at turn_end (no-reply safety
|
|
8122
|
+
* net); finalize edits are idempotent (a 'message is not modified' on the
|
|
8123
|
+
* second call is swallowed).
|
|
8106
8124
|
*/
|
|
8107
8125
|
function clearActivitySummary(turn: CurrentTurn): void {
|
|
8108
8126
|
const chat = turn.sessionChatId
|
|
8109
8127
|
const thread = turn.sessionThreadId
|
|
8110
8128
|
const inFlight = turn.activityInFlight ?? Promise.resolve()
|
|
8111
8129
|
void inFlight.then(async () => {
|
|
8112
|
-
if (turn.activityMessageId
|
|
8113
|
-
|
|
8114
|
-
|
|
8130
|
+
if (turn.activityMessageId == null) return
|
|
8131
|
+
const id = turn.activityMessageId
|
|
8132
|
+
turn.activityMessageId = null
|
|
8133
|
+
if (CLEAR_STATUS_ON_COMPLETION) {
|
|
8115
8134
|
try {
|
|
8116
8135
|
await robustApiCall(
|
|
8117
8136
|
() => bot.api.deleteMessage(chat, id),
|
|
@@ -8120,6 +8139,22 @@ function clearActivitySummary(turn: CurrentTurn): void {
|
|
|
8120
8139
|
} catch (err) {
|
|
8121
8140
|
process.stderr.write(`telegram gateway: activity-summary delete failed: ${err}\n`)
|
|
8122
8141
|
}
|
|
8142
|
+
return
|
|
8143
|
+
}
|
|
8144
|
+
// Default: leave the status message as a record, edited to a terminal
|
|
8145
|
+
// all-done state so it doesn't freeze on a misleading "→ in-progress" line.
|
|
8146
|
+
const finalHtml = composeTurnActivity(turn, true)
|
|
8147
|
+
if (finalHtml == null) return
|
|
8148
|
+
try {
|
|
8149
|
+
await robustApiCall(
|
|
8150
|
+
() => bot.api.editMessageText(chat, id, finalHtml, { parse_mode: 'HTML' }),
|
|
8151
|
+
{ chat_id: chat, ...(thread != null ? { threadId: thread } : {}), verb: 'activity-summary.finalize' },
|
|
8152
|
+
)
|
|
8153
|
+
} catch (err) {
|
|
8154
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
8155
|
+
if (!msg.includes('message is not modified')) {
|
|
8156
|
+
process.stderr.write(`telegram gateway: activity-summary finalize failed: ${msg}\n`)
|
|
8157
|
+
}
|
|
8123
8158
|
}
|
|
8124
8159
|
})
|
|
8125
8160
|
}
|
|
@@ -126,6 +126,26 @@ describe("appendActivityLine + renderActivityFeed — accumulating activity feed
|
|
|
126
126
|
it("renderActivityFeed returns null on empty", () => {
|
|
127
127
|
expect(renderActivityFeed([])).toBeNull();
|
|
128
128
|
});
|
|
129
|
+
|
|
130
|
+
// final=true: the persisted "status stays" terminal render — the feed is
|
|
131
|
+
// left in the chat when clear_status_on_completion=false, so the newest line
|
|
132
|
+
// must read done (✓), not a frozen "→ in-progress".
|
|
133
|
+
it("final=true renders the newest line as done (✓), not in-progress (→)", () => {
|
|
134
|
+
const lines = ["Reading a.ts", "Searching memory", "Running a command"];
|
|
135
|
+
const out = renderActivityFeed(lines, true)!;
|
|
136
|
+
expect(out).toBe(
|
|
137
|
+
"<i>✓ Reading a.ts</i>\n<i>✓ Searching memory</i>\n<i>✓ Running a command</i>",
|
|
138
|
+
);
|
|
139
|
+
expect(out).not.toContain("→"); // no in-progress arrow anywhere
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("final=true on a single line is also done (✓)", () => {
|
|
143
|
+
expect(renderActivityFeed(["Reading a.ts"], true)).toBe("<i>✓ Reading a.ts</i>");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("final defaults false (live render keeps the → in-progress newest line)", () => {
|
|
147
|
+
expect(renderActivityFeed(["Reading a.ts"])).toBe("<b>→ Reading a.ts</b>");
|
|
148
|
+
});
|
|
129
149
|
});
|
|
130
150
|
|
|
131
151
|
describe("appendActivityLabel — precomputed label feed (tool_label path)", () => {
|
|
@@ -186,4 +206,20 @@ describe("renderActivityFeedWithNested — foreground sub-agent nesting (Model A
|
|
|
186
206
|
const out = renderActivityFeedWithNested(["Delegating: x"], ["touch <a> & <b>"])!;
|
|
187
207
|
expect(out).toContain(" ↳ <b>→ touch <a> & <b></b>");
|
|
188
208
|
});
|
|
209
|
+
|
|
210
|
+
it("final=true: the nested newest step renders done (✓), not in-progress (→)", () => {
|
|
211
|
+
const out = renderActivityFeedWithNested(
|
|
212
|
+
["Delegating: x"],
|
|
213
|
+
["Reading schema.ts", "Looking for foreign keys"],
|
|
214
|
+
true,
|
|
215
|
+
)!;
|
|
216
|
+
expect(out).toContain(" ↳ <i>Looking for foreign keys</i>"); // newest now italic done
|
|
217
|
+
expect(out).not.toContain("→"); // no in-progress arrow in the finalized feed
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("final=true with no children delegates to the finalized flat render", () => {
|
|
221
|
+
expect(renderActivityFeedWithNested(["Reading a.ts"], [], true)).toBe(
|
|
222
|
+
"<i>✓ Reading a.ts</i>",
|
|
223
|
+
);
|
|
224
|
+
});
|
|
189
225
|
});
|
|
@@ -200,7 +200,7 @@ function escapeFeedHtml(s: string): string {
|
|
|
200
200
|
* `✓ +N earlier…` header when the turn ran longer. Returns null when empty.
|
|
201
201
|
* Callers send the result verbatim — do NOT re-escape or re-wrap it.
|
|
202
202
|
*/
|
|
203
|
-
export function renderActivityFeed(lines: string[]): string | null {
|
|
203
|
+
export function renderActivityFeed(lines: string[], final = false): string | null {
|
|
204
204
|
if (lines.length === 0) return null;
|
|
205
205
|
const shown = lines.slice(-MIRROR_MAX_LINES);
|
|
206
206
|
const hidden = lines.length - shown.length;
|
|
@@ -208,10 +208,12 @@ export function renderActivityFeed(lines: string[]): string | null {
|
|
|
208
208
|
if (hidden > 0) out.push(`<i>✓ +${hidden} earlier…</i>`);
|
|
209
209
|
const lastIdx = shown.length - 1;
|
|
210
210
|
// Newest line = in-progress step (bold, →); earlier = done (italic, ✓).
|
|
211
|
+
// `final` (turn complete, feed left as a record): ALL lines render done (✓)
|
|
212
|
+
// so the persisted message doesn't freeze on a misleading "→ in-progress".
|
|
211
213
|
// Returns ready Telegram HTML — callers must NOT re-escape or re-wrap it.
|
|
212
214
|
shown.forEach((l, i) => {
|
|
213
215
|
const esc = escapeFeedHtml(l);
|
|
214
|
-
out.push(i === lastIdx ? `<b>→ ${esc}</b>` : `<i>✓ ${esc}</i>`);
|
|
216
|
+
out.push(i === lastIdx && !final ? `<b>→ ${esc}</b>` : `<i>✓ ${esc}</i>`);
|
|
215
217
|
});
|
|
216
218
|
return out.join("\n");
|
|
217
219
|
}
|
|
@@ -245,9 +247,10 @@ const NESTED_PREFIX = " ↳ ";
|
|
|
245
247
|
export function renderActivityFeedWithNested(
|
|
246
248
|
lines: string[],
|
|
247
249
|
childLines: string[],
|
|
250
|
+
final = false,
|
|
248
251
|
): string | null {
|
|
249
252
|
const children = childLines.map((s) => s.trim()).filter((s) => s.length > 0);
|
|
250
|
-
if (children.length === 0) return renderActivityFeed(lines);
|
|
253
|
+
if (children.length === 0) return renderActivityFeed(lines, final);
|
|
251
254
|
|
|
252
255
|
const out: string[] = [];
|
|
253
256
|
const shownParent = lines.slice(-MIRROR_MAX_LINES);
|
|
@@ -259,11 +262,13 @@ export function renderActivityFeedWithNested(
|
|
|
259
262
|
const hiddenChild = children.length - shownChild.length;
|
|
260
263
|
if (hiddenChild > 0) out.push(`${NESTED_PREFIX}<i>+${hiddenChild} earlier…</i>`);
|
|
261
264
|
const lastChildIdx = shownChild.length - 1;
|
|
265
|
+
// `final`: the nested newest step also renders done (✓) so the left-behind
|
|
266
|
+
// feed reads as completed, not stuck on a "→ in-progress" child step.
|
|
262
267
|
shownChild.forEach((l, i) => {
|
|
263
268
|
const t = l.length > NESTED_LINE_MAX ? l.slice(0, NESTED_LINE_MAX - 1) + "…" : l;
|
|
264
269
|
const esc = escapeFeedHtml(t);
|
|
265
270
|
out.push(
|
|
266
|
-
i === lastChildIdx
|
|
271
|
+
i === lastChildIdx && !final
|
|
267
272
|
? `${NESTED_PREFIX}<b>→ ${esc}</b>`
|
|
268
273
|
: `${NESTED_PREFIX}<i>${esc}</i>`,
|
|
269
274
|
);
|