switchroom 0.5.0 → 0.7.8
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/README.md +142 -121
- package/bin/autoaccept.exp +29 -6
- package/dist/agent-scheduler/index.js +12261 -0
- package/dist/cli/autoaccept-poll.js +10 -0
- package/dist/cli/switchroom.js +27250 -25324
- package/dist/vault/approvals/kernel-server.js +12709 -0
- package/dist/vault/broker/server.js +15724 -0
- package/package.json +4 -3
- package/profiles/_base/start.sh.hbs +133 -0
- package/profiles/_shared/telegram-style.md.hbs +3 -3
- package/profiles/default/CLAUDE.md +3 -3
- package/profiles/default/CLAUDE.md.hbs +2 -2
- package/profiles/default/workspace/CLAUDE.md.hbs +9 -0
- package/skills/docx/VENDORED.md +1 -1
- package/skills/mcp-builder/VENDORED.md +1 -1
- package/skills/pdf/VENDORED.md +1 -1
- package/skills/pptx/VENDORED.md +1 -1
- package/skills/skill-creator/VENDORED.md +1 -1
- package/skills/switchroom-architecture/SKILL.md +8 -7
- package/skills/switchroom-cli/SKILL.md +23 -15
- package/skills/switchroom-health/SKILL.md +7 -7
- package/skills/switchroom-install/SKILL.md +36 -39
- package/skills/switchroom-manage/SKILL.md +4 -4
- package/skills/switchroom-status/SKILL.md +1 -1
- package/skills/webapp-testing/VENDORED.md +1 -1
- package/skills/xlsx/VENDORED.md +1 -1
- package/telegram-plugin/admin-commands/dispatch.test.ts +119 -1
- package/telegram-plugin/admin-commands/index.ts +71 -0
- package/telegram-plugin/ask-user.ts +1 -0
- package/telegram-plugin/card-event-log.ts +138 -0
- package/telegram-plugin/dist/bridge/bridge.js +178 -31
- package/telegram-plugin/dist/foreman/foreman.js +6875 -6526
- package/telegram-plugin/dist/gateway/gateway.js +13862 -11834
- package/telegram-plugin/dist/server.js +202 -40
- package/telegram-plugin/fleet-state.ts +25 -10
- package/telegram-plugin/foreman/foreman.ts +38 -3
- package/telegram-plugin/gateway/approval-callback.ts +126 -0
- package/telegram-plugin/gateway/approval-card.test.ts +90 -0
- package/telegram-plugin/gateway/approval-card.ts +127 -0
- package/telegram-plugin/gateway/approvals-commands.ts +126 -0
- package/telegram-plugin/gateway/boot-card.ts +31 -6
- package/telegram-plugin/gateway/boot-probes.ts +503 -72
- package/telegram-plugin/gateway/gateway.ts +822 -94
- package/telegram-plugin/gateway/ipc-protocol.ts +34 -1
- package/telegram-plugin/gateway/ipc-server.ts +35 -0
- package/telegram-plugin/gateway/startup-mutex.ts +110 -2
- package/telegram-plugin/hooks/hooks.json +19 -0
- package/telegram-plugin/hooks/tool-label-pretool.mjs +216 -0
- package/telegram-plugin/hooks/tool-label-stop.mjs +63 -0
- package/telegram-plugin/package.json +4 -1
- package/telegram-plugin/plugin-logger.ts +20 -1
- package/telegram-plugin/progress-card-driver.ts +202 -13
- package/telegram-plugin/progress-card.ts +2 -2
- package/telegram-plugin/quota-check.ts +1 -0
- package/telegram-plugin/registry/subagents-schema.ts +37 -0
- package/telegram-plugin/registry/subagents.test.ts +64 -0
- package/telegram-plugin/session-tail.ts +58 -5
- package/telegram-plugin/shared/bot-runtime.ts +48 -2
- package/telegram-plugin/subagent-watcher.ts +139 -7
- package/telegram-plugin/tests/_progress-card-harness.ts +4 -0
- package/telegram-plugin/tests/bg-agent-progress-card-757.test.ts +201 -0
- package/telegram-plugin/tests/boot-card-probe-target.test.ts +10 -34
- package/telegram-plugin/tests/boot-card-render.test.ts +6 -5
- package/telegram-plugin/tests/boot-probes.test.ts +558 -0
- package/telegram-plugin/tests/card-event-log.test.ts +145 -0
- package/telegram-plugin/tests/gateway-startup-mutex.test.ts +102 -0
- package/telegram-plugin/tests/ipc-server-validate-inject-inbound.test.ts +134 -0
- package/telegram-plugin/tests/progress-card-delay-842.test.ts +160 -0
- package/telegram-plugin/tests/quota-check.test.ts +37 -1
- package/telegram-plugin/tests/subagent-registry-bugs.test.ts +5 -0
- package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +104 -1
- package/telegram-plugin/tests/subagent-watcher.test.ts +5 -0
- package/telegram-plugin/tests/tool-label-sidecar.test.ts +114 -0
- package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +5 -3
- package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +10 -0
- package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +58 -14
- package/telegram-plugin/tests/welcome-text.test.ts +57 -0
- package/telegram-plugin/tool-label-sidecar.ts +140 -0
- package/telegram-plugin/tool-labels.ts +55 -0
- package/telegram-plugin/two-zone-card.ts +27 -7
- package/telegram-plugin/uat/SETUP.md +160 -0
- package/telegram-plugin/uat/assertions.ts +140 -0
- package/telegram-plugin/uat/driver.ts +174 -0
- package/telegram-plugin/uat/harness.ts +161 -0
- package/telegram-plugin/uat/login.ts +134 -0
- package/telegram-plugin/uat/port-allocator.ts +71 -0
- package/telegram-plugin/uat/scenarios/smoke-clerk-reply.test.ts +61 -0
- package/telegram-plugin/welcome-text.ts +44 -2
- package/bin/bridge-watchdog.sh +0 -967
|
@@ -16931,7 +16931,7 @@ var init_zod = __esm(() => {
|
|
|
16931
16931
|
});
|
|
16932
16932
|
|
|
16933
16933
|
// plugin-logger.ts
|
|
16934
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2, renameSync as renameSync3, statSync as statSync2 } from "fs";
|
|
16934
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2, renameSync as renameSync3, statSync as statSync2, existsSync as existsSync2 } from "fs";
|
|
16935
16935
|
import { homedir as homedir2 } from "os";
|
|
16936
16936
|
import { dirname as dirname2, join as join2 } from "path";
|
|
16937
16937
|
function resolveLogPath2(env = process.env) {
|
|
@@ -16950,6 +16950,14 @@ function rotateIfNeeded2(path) {
|
|
|
16950
16950
|
const st = statSync2(path);
|
|
16951
16951
|
if (st.size < ROTATE_AT_BYTES2)
|
|
16952
16952
|
return;
|
|
16953
|
+
for (let i = ROTATION_BACKUPS2 - 1;i >= 1; i--) {
|
|
16954
|
+
const src = `${path}.${i}`;
|
|
16955
|
+
const dst = `${path}.${i + 1}`;
|
|
16956
|
+
try {
|
|
16957
|
+
if (existsSync2(src))
|
|
16958
|
+
renameSync3(src, dst);
|
|
16959
|
+
} catch {}
|
|
16960
|
+
}
|
|
16953
16961
|
const backup = `${path}.1`;
|
|
16954
16962
|
renameSync3(path, backup);
|
|
16955
16963
|
} catch {}
|
|
@@ -16981,11 +16989,14 @@ function installPluginLogger2(env = process.env) {
|
|
|
16981
16989
|
};
|
|
16982
16990
|
return activeHandle2;
|
|
16983
16991
|
}
|
|
16984
|
-
var DEFAULT_LOG_PATH2, ROTATE_AT_BYTES2, activeHandle2 = null;
|
|
16992
|
+
var DEFAULT_LOG_PATH2, ROTATE_AT_BYTES2, ROTATION_BACKUPS2 = 5, activeHandle2 = null;
|
|
16985
16993
|
var init_plugin_logger = __esm(() => {
|
|
16986
16994
|
DEFAULT_LOG_PATH2 = join2(homedir2(), ".switchroom", "logs", "telegram-plugin.log");
|
|
16987
|
-
ROTATE_AT_BYTES2 =
|
|
16995
|
+
ROTATE_AT_BYTES2 = 50 * 1024 * 1024;
|
|
16988
16996
|
});
|
|
16997
|
+
|
|
16998
|
+
// tool-labels.ts
|
|
16999
|
+
var init_tool_labels = () => {};
|
|
16989
17000
|
// tool-error-filter.ts
|
|
16990
17001
|
var init_tool_error_filter = () => {};
|
|
16991
17002
|
|
|
@@ -16995,6 +17006,7 @@ var init_fleet_state = () => {};
|
|
|
16995
17006
|
// two-zone-card.ts
|
|
16996
17007
|
var init_two_zone_card = __esm(() => {
|
|
16997
17008
|
init_fleet_state();
|
|
17009
|
+
init_tool_labels();
|
|
16998
17010
|
});
|
|
16999
17011
|
|
|
17000
17012
|
// progress-card.ts
|
|
@@ -17003,6 +17015,7 @@ function isMultiAgentEnabled(env = process.env) {
|
|
|
17003
17015
|
}
|
|
17004
17016
|
var STUCK_THRESHOLD_MS;
|
|
17005
17017
|
var init_progress_card = __esm(() => {
|
|
17018
|
+
init_tool_labels();
|
|
17006
17019
|
init_tool_error_filter();
|
|
17007
17020
|
init_two_zone_card();
|
|
17008
17021
|
STUCK_THRESHOLD_MS = 2 * 60000;
|
|
@@ -17076,26 +17089,117 @@ var init_operator_events = __esm(() => {
|
|
|
17076
17089
|
cooldownMap = new Map;
|
|
17077
17090
|
});
|
|
17078
17091
|
|
|
17092
|
+
// tool-label-sidecar.ts
|
|
17093
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
|
|
17094
|
+
import { join as join3 } from "node:path";
|
|
17095
|
+
function createToolLabelSidecar(opts) {
|
|
17096
|
+
const path = join3(opts.stateDir, `tool-labels-${opts.sessionId}.jsonl`);
|
|
17097
|
+
const labels = new Map;
|
|
17098
|
+
const subscribers = new Set;
|
|
17099
|
+
let offset = 0;
|
|
17100
|
+
let stopped = false;
|
|
17101
|
+
const sched = opts.scheduler ?? {
|
|
17102
|
+
setInterval: (cb, ms) => setInterval(cb, ms),
|
|
17103
|
+
clearInterval: (h) => clearInterval(h)
|
|
17104
|
+
};
|
|
17105
|
+
function ingestSuffix(text) {
|
|
17106
|
+
if (!text)
|
|
17107
|
+
return;
|
|
17108
|
+
const lines = text.split(`
|
|
17109
|
+
`);
|
|
17110
|
+
for (const raw of lines) {
|
|
17111
|
+
const line = raw.trim();
|
|
17112
|
+
if (!line)
|
|
17113
|
+
continue;
|
|
17114
|
+
let row = null;
|
|
17115
|
+
try {
|
|
17116
|
+
row = JSON.parse(line);
|
|
17117
|
+
} catch {
|
|
17118
|
+
continue;
|
|
17119
|
+
}
|
|
17120
|
+
if (!row || typeof row.tool_use_id !== "string" || typeof row.label !== "string")
|
|
17121
|
+
continue;
|
|
17122
|
+
if (labels.has(row.tool_use_id))
|
|
17123
|
+
continue;
|
|
17124
|
+
labels.set(row.tool_use_id, row.label);
|
|
17125
|
+
for (const cb of subscribers) {
|
|
17126
|
+
try {
|
|
17127
|
+
cb(row.tool_use_id, row.label);
|
|
17128
|
+
} catch {}
|
|
17129
|
+
}
|
|
17130
|
+
}
|
|
17131
|
+
}
|
|
17132
|
+
function poll() {
|
|
17133
|
+
if (stopped)
|
|
17134
|
+
return;
|
|
17135
|
+
if (!existsSync3(path))
|
|
17136
|
+
return;
|
|
17137
|
+
let size = 0;
|
|
17138
|
+
try {
|
|
17139
|
+
size = statSync3(path).size;
|
|
17140
|
+
} catch {
|
|
17141
|
+
return;
|
|
17142
|
+
}
|
|
17143
|
+
if (size <= offset) {
|
|
17144
|
+
if (size < offset)
|
|
17145
|
+
offset = 0;
|
|
17146
|
+
else
|
|
17147
|
+
return;
|
|
17148
|
+
}
|
|
17149
|
+
let text = "";
|
|
17150
|
+
try {
|
|
17151
|
+
const buf = readFileSync2(path);
|
|
17152
|
+
text = buf.subarray(offset).toString("utf8");
|
|
17153
|
+
offset = buf.length;
|
|
17154
|
+
} catch {
|
|
17155
|
+
return;
|
|
17156
|
+
}
|
|
17157
|
+
ingestSuffix(text);
|
|
17158
|
+
}
|
|
17159
|
+
poll();
|
|
17160
|
+
const handle = sched.setInterval(poll, opts.pollMs ?? 250);
|
|
17161
|
+
return {
|
|
17162
|
+
getLabel(toolUseId) {
|
|
17163
|
+
return labels.get(toolUseId);
|
|
17164
|
+
},
|
|
17165
|
+
onLabel(cb) {
|
|
17166
|
+
subscribers.add(cb);
|
|
17167
|
+
return () => subscribers.delete(cb);
|
|
17168
|
+
},
|
|
17169
|
+
poll,
|
|
17170
|
+
stop() {
|
|
17171
|
+
if (stopped)
|
|
17172
|
+
return;
|
|
17173
|
+
stopped = true;
|
|
17174
|
+
try {
|
|
17175
|
+
sched.clearInterval(handle);
|
|
17176
|
+
} catch {}
|
|
17177
|
+
subscribers.clear();
|
|
17178
|
+
}
|
|
17179
|
+
};
|
|
17180
|
+
}
|
|
17181
|
+
var init_tool_label_sidecar = () => {};
|
|
17182
|
+
|
|
17079
17183
|
// session-tail.ts
|
|
17080
17184
|
import {
|
|
17081
17185
|
closeSync,
|
|
17082
|
-
existsSync as
|
|
17186
|
+
existsSync as existsSync4,
|
|
17083
17187
|
openSync,
|
|
17084
17188
|
readdirSync,
|
|
17085
17189
|
readSync,
|
|
17086
|
-
statSync as
|
|
17190
|
+
statSync as statSync4,
|
|
17087
17191
|
watch
|
|
17088
17192
|
} from "fs";
|
|
17089
17193
|
import { homedir as homedir3 } from "os";
|
|
17090
|
-
import { basename, join as
|
|
17194
|
+
import { basename, join as join4 } from "path";
|
|
17091
17195
|
function sanitizeCwdToProjectName(cwd) {
|
|
17092
17196
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
17093
17197
|
}
|
|
17094
|
-
function getProjectsDirForCwd(cwd = process.cwd(), claudeHome = process.env.CLAUDE_CONFIG_DIR ??
|
|
17095
|
-
return
|
|
17198
|
+
function getProjectsDirForCwd(cwd = process.cwd(), claudeHome = process.env.CLAUDE_CONFIG_DIR ?? join4(homedir3(), ".claude")) {
|
|
17199
|
+
return join4(claudeHome, "projects", sanitizeCwdToProjectName(cwd));
|
|
17096
17200
|
}
|
|
17097
17201
|
function findActiveSessionFile(projectsDir) {
|
|
17098
|
-
if (!
|
|
17202
|
+
if (!existsSync4(projectsDir))
|
|
17099
17203
|
return null;
|
|
17100
17204
|
let entries;
|
|
17101
17205
|
try {
|
|
@@ -17108,9 +17212,9 @@ function findActiveSessionFile(projectsDir) {
|
|
|
17108
17212
|
for (const e of entries) {
|
|
17109
17213
|
if (!e.endsWith(".jsonl"))
|
|
17110
17214
|
continue;
|
|
17111
|
-
const p =
|
|
17215
|
+
const p = join4(projectsDir, e);
|
|
17112
17216
|
try {
|
|
17113
|
-
const s =
|
|
17217
|
+
const s = statSync4(p);
|
|
17114
17218
|
if (s.mtimeMs > bestMtime) {
|
|
17115
17219
|
bestMtime = s.mtimeMs;
|
|
17116
17220
|
bestPath = p;
|
|
@@ -17335,13 +17439,53 @@ function extractDetailMessage(obj) {
|
|
|
17335
17439
|
}
|
|
17336
17440
|
function startSessionTail(config2) {
|
|
17337
17441
|
const cwd = config2.cwd ?? process.cwd();
|
|
17338
|
-
const claudeHome = config2.claudeHome ?? process.env.CLAUDE_CONFIG_DIR ??
|
|
17442
|
+
const claudeHome = config2.claudeHome ?? process.env.CLAUDE_CONFIG_DIR ?? join4(homedir3(), ".claude");
|
|
17339
17443
|
const projectsDir = getProjectsDirForCwd(cwd, claudeHome);
|
|
17340
17444
|
const rescanMs = config2.rescanIntervalMs ?? 500;
|
|
17341
17445
|
const log = config2.log;
|
|
17342
|
-
const
|
|
17446
|
+
const rawOnEvent = config2.onEvent;
|
|
17343
17447
|
const onOperatorEvent = config2.onOperatorEvent;
|
|
17344
17448
|
log?.(`session-tail: projectsDir=${projectsDir}`);
|
|
17449
|
+
const sidecars = new Map;
|
|
17450
|
+
const stateDirForSidecar = process.env.TELEGRAM_STATE_DIR ?? null;
|
|
17451
|
+
function sessionIdForFile(file) {
|
|
17452
|
+
if (!file)
|
|
17453
|
+
return null;
|
|
17454
|
+
const b = file.endsWith(".jsonl") ? basename(file, ".jsonl") : null;
|
|
17455
|
+
return b && b.length > 0 ? b : null;
|
|
17456
|
+
}
|
|
17457
|
+
function ensureSidecar(sessionId) {
|
|
17458
|
+
if (!stateDirForSidecar)
|
|
17459
|
+
return null;
|
|
17460
|
+
const existing = sidecars.get(sessionId);
|
|
17461
|
+
if (existing)
|
|
17462
|
+
return existing;
|
|
17463
|
+
try {
|
|
17464
|
+
const s = createToolLabelSidecar({ stateDir: stateDirForSidecar, sessionId });
|
|
17465
|
+
sidecars.set(sessionId, s);
|
|
17466
|
+
return s;
|
|
17467
|
+
} catch (err) {
|
|
17468
|
+
log?.(`session-tail: sidecar create failed: ${err.message}`);
|
|
17469
|
+
return null;
|
|
17470
|
+
}
|
|
17471
|
+
}
|
|
17472
|
+
function decorate(ev, sessionId) {
|
|
17473
|
+
if (!sessionId)
|
|
17474
|
+
return ev;
|
|
17475
|
+
if (ev.kind !== "tool_use" && ev.kind !== "sub_agent_tool_use")
|
|
17476
|
+
return ev;
|
|
17477
|
+
if (!ev.toolUseId)
|
|
17478
|
+
return ev;
|
|
17479
|
+
const s = ensureSidecar(sessionId);
|
|
17480
|
+
if (!s)
|
|
17481
|
+
return ev;
|
|
17482
|
+
s.poll();
|
|
17483
|
+
const label = s.getLabel(ev.toolUseId);
|
|
17484
|
+
if (!label)
|
|
17485
|
+
return ev;
|
|
17486
|
+
return { ...ev, precomputedLabel: label };
|
|
17487
|
+
}
|
|
17488
|
+
const onEvent = (ev) => rawOnEvent(ev);
|
|
17345
17489
|
let currentFile = null;
|
|
17346
17490
|
let cursor = 0;
|
|
17347
17491
|
let watcher = null;
|
|
@@ -17353,7 +17497,7 @@ function startSessionTail(config2) {
|
|
|
17353
17497
|
if (stopped || !currentFile)
|
|
17354
17498
|
return;
|
|
17355
17499
|
try {
|
|
17356
|
-
const stat =
|
|
17500
|
+
const stat = statSync4(currentFile);
|
|
17357
17501
|
if (stat.size < cursor) {
|
|
17358
17502
|
cursor = 0;
|
|
17359
17503
|
pendingPartial = "";
|
|
@@ -17378,9 +17522,10 @@ function startSessionTail(config2) {
|
|
|
17378
17522
|
if (!line)
|
|
17379
17523
|
continue;
|
|
17380
17524
|
const events = projectTranscriptLine(line);
|
|
17525
|
+
const sid = sessionIdForFile(currentFile);
|
|
17381
17526
|
for (const ev of events) {
|
|
17382
17527
|
try {
|
|
17383
|
-
onEvent(ev);
|
|
17528
|
+
onEvent(decorate(ev, sid));
|
|
17384
17529
|
} catch (err) {
|
|
17385
17530
|
log?.(`session-tail: onEvent threw: ${err.message}`);
|
|
17386
17531
|
}
|
|
@@ -17421,7 +17566,7 @@ function startSessionTail(config2) {
|
|
|
17421
17566
|
} else {
|
|
17422
17567
|
pendingPartial = "";
|
|
17423
17568
|
try {
|
|
17424
|
-
cursor =
|
|
17569
|
+
cursor = statSync4(file).size;
|
|
17425
17570
|
} catch {
|
|
17426
17571
|
cursor = 0;
|
|
17427
17572
|
}
|
|
@@ -17441,7 +17586,7 @@ function startSessionTail(config2) {
|
|
|
17441
17586
|
if (stopped)
|
|
17442
17587
|
return;
|
|
17443
17588
|
try {
|
|
17444
|
-
const stat =
|
|
17589
|
+
const stat = statSync4(t.file);
|
|
17445
17590
|
if (stat.size < t.cursor) {
|
|
17446
17591
|
t.cursor = 0;
|
|
17447
17592
|
t.pendingPartial = "";
|
|
@@ -17482,7 +17627,8 @@ function startSessionTail(config2) {
|
|
|
17482
17627
|
t.hasSeenTerminal = true;
|
|
17483
17628
|
}
|
|
17484
17629
|
try {
|
|
17485
|
-
|
|
17630
|
+
const subSid = sessionIdForFile(t.file);
|
|
17631
|
+
onEvent(decorate(ev, subSid));
|
|
17486
17632
|
} catch (err) {
|
|
17487
17633
|
log?.(`session-tail: sub onEvent threw: ${err.message}`);
|
|
17488
17634
|
}
|
|
@@ -17498,7 +17644,7 @@ function startSessionTail(config2) {
|
|
|
17498
17644
|
return;
|
|
17499
17645
|
let cursor2 = 0;
|
|
17500
17646
|
try {
|
|
17501
|
-
cursor2 =
|
|
17647
|
+
cursor2 = statSync4(file).size;
|
|
17502
17648
|
} catch {}
|
|
17503
17649
|
const t = {
|
|
17504
17650
|
agentId,
|
|
@@ -17553,8 +17699,8 @@ function startSessionTail(config2) {
|
|
|
17553
17699
|
if (!currentFile)
|
|
17554
17700
|
return;
|
|
17555
17701
|
const sessionId = basename(currentFile, ".jsonl");
|
|
17556
|
-
const subDir =
|
|
17557
|
-
if (!
|
|
17702
|
+
const subDir = join4(projectsDir, sessionId, "subagents");
|
|
17703
|
+
if (!existsSync4(subDir))
|
|
17558
17704
|
return;
|
|
17559
17705
|
let entries;
|
|
17560
17706
|
try {
|
|
@@ -17566,7 +17712,7 @@ function startSessionTail(config2) {
|
|
|
17566
17712
|
if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
|
|
17567
17713
|
continue;
|
|
17568
17714
|
const agentId = e.slice("agent-".length, -".jsonl".length);
|
|
17569
|
-
const file =
|
|
17715
|
+
const file = join4(subDir, e);
|
|
17570
17716
|
if (!subTails.has(file)) {
|
|
17571
17717
|
attachSub(file, agentId);
|
|
17572
17718
|
} else {
|
|
@@ -17607,6 +17753,12 @@ function startSessionTail(config2) {
|
|
|
17607
17753
|
}
|
|
17608
17754
|
}
|
|
17609
17755
|
subTails.clear();
|
|
17756
|
+
for (const s of sidecars.values()) {
|
|
17757
|
+
try {
|
|
17758
|
+
s.stop();
|
|
17759
|
+
} catch {}
|
|
17760
|
+
}
|
|
17761
|
+
sidecars.clear();
|
|
17610
17762
|
if (pollTimer) {
|
|
17611
17763
|
clearInterval(pollTimer);
|
|
17612
17764
|
pollTimer = null;
|
|
@@ -17621,6 +17773,7 @@ var MAX_JSONL_LINE_BYTES, MAX_ERROR_TEXT_CHARS = 500;
|
|
|
17621
17773
|
var init_session_tail = __esm(() => {
|
|
17622
17774
|
init_progress_card();
|
|
17623
17775
|
init_operator_events();
|
|
17776
|
+
init_tool_label_sidecar();
|
|
17624
17777
|
MAX_JSONL_LINE_BYTES = 2 * 1024 * 1024;
|
|
17625
17778
|
});
|
|
17626
17779
|
|
|
@@ -23180,7 +23333,7 @@ ${i3.join(`
|
|
|
23180
23333
|
});
|
|
23181
23334
|
|
|
23182
23335
|
// pty-tail.ts
|
|
23183
|
-
import { existsSync as
|
|
23336
|
+
import { existsSync as existsSync5, statSync as statSync5, watch as watch2, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
|
|
23184
23337
|
function debugDumpBufferOnMiss(buf) {
|
|
23185
23338
|
if (!PTY_DEBUG)
|
|
23186
23339
|
return;
|
|
@@ -23450,11 +23603,11 @@ function startPtyTail(config2) {
|
|
|
23450
23603
|
function readNew() {
|
|
23451
23604
|
if (stopped)
|
|
23452
23605
|
return;
|
|
23453
|
-
if (!
|
|
23606
|
+
if (!existsSync5(config2.logFile))
|
|
23454
23607
|
return;
|
|
23455
23608
|
let stat;
|
|
23456
23609
|
try {
|
|
23457
|
-
stat =
|
|
23610
|
+
stat = statSync5(config2.logFile);
|
|
23458
23611
|
} catch {
|
|
23459
23612
|
return;
|
|
23460
23613
|
}
|
|
@@ -23483,13 +23636,13 @@ function startPtyTail(config2) {
|
|
|
23483
23636
|
});
|
|
23484
23637
|
}
|
|
23485
23638
|
function attachWatcher() {
|
|
23486
|
-
if (!
|
|
23639
|
+
if (!existsSync5(config2.logFile))
|
|
23487
23640
|
return;
|
|
23488
23641
|
if (watcher)
|
|
23489
23642
|
return;
|
|
23490
23643
|
let size = 0;
|
|
23491
23644
|
try {
|
|
23492
|
-
size =
|
|
23645
|
+
size = statSync5(config2.logFile).size;
|
|
23493
23646
|
} catch {
|
|
23494
23647
|
return;
|
|
23495
23648
|
}
|
|
@@ -23815,7 +23968,7 @@ var init_ipc_client = () => {};
|
|
|
23815
23968
|
|
|
23816
23969
|
// bridge/bridge.ts
|
|
23817
23970
|
var exports_bridge = {};
|
|
23818
|
-
import { dirname as dirname3, join as
|
|
23971
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
23819
23972
|
import { homedir as homedir4 } from "os";
|
|
23820
23973
|
function onInbound(msg) {
|
|
23821
23974
|
mcp.notification({
|
|
@@ -23882,7 +24035,7 @@ async function main() {
|
|
|
23882
24035
|
onStatus,
|
|
23883
24036
|
log: (msg) => process.stderr.write(`telegram bridge: ipc: ${msg}
|
|
23884
24037
|
`),
|
|
23885
|
-
livenessFilePath:
|
|
24038
|
+
livenessFilePath: join5(STATE_DIR, ".bridge-alive")
|
|
23886
24039
|
});
|
|
23887
24040
|
if (ipc.isConnected()) {
|
|
23888
24041
|
process.stderr.write(`telegram bridge: connected to gateway at ${SOCKET_PATH}
|
|
@@ -23904,8 +24057,8 @@ var init_bridge = __esm(async () => {
|
|
|
23904
24057
|
init_pty_tail();
|
|
23905
24058
|
init_ipc_client();
|
|
23906
24059
|
installPluginLogger2();
|
|
23907
|
-
STATE_DIR = process.env.TELEGRAM_STATE_DIR ??
|
|
23908
|
-
SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ??
|
|
24060
|
+
STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join5(homedir4(), ".claude", "channels", "telegram");
|
|
24061
|
+
SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join5(STATE_DIR, "gateway.sock");
|
|
23909
24062
|
TOPIC_ID = process.env.TELEGRAM_TOPIC_ID ? Number(process.env.TELEGRAM_TOPIC_ID) : undefined;
|
|
23910
24063
|
AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME;
|
|
23911
24064
|
if (!AGENT_NAME) {
|
|
@@ -24334,7 +24487,7 @@ var init_bridge = __esm(async () => {
|
|
|
24334
24487
|
if (ptyTailEnabled) {
|
|
24335
24488
|
try {
|
|
24336
24489
|
const agentDir = process.env.CLAUDE_CONFIG_DIR ? dirname3(process.env.CLAUDE_CONFIG_DIR) : process.cwd();
|
|
24337
|
-
const serviceLogPath = process.env.SWITCHROOM_SERVICE_LOG_PATH ??
|
|
24490
|
+
const serviceLogPath = process.env.SWITCHROOM_SERVICE_LOG_PATH ?? join5(agentDir, "service.log");
|
|
24338
24491
|
ptyTailHandle = startPtyTail({
|
|
24339
24492
|
logFile: serviceLogPath,
|
|
24340
24493
|
log: (msg) => process.stderr.write(`telegram bridge: ${msg}
|
|
@@ -24383,16 +24536,17 @@ var init_bridge = __esm(async () => {
|
|
|
24383
24536
|
});
|
|
24384
24537
|
|
|
24385
24538
|
// server.ts
|
|
24386
|
-
import { existsSync as
|
|
24539
|
+
import { existsSync as existsSync6 } from "fs";
|
|
24387
24540
|
import { homedir as homedir5 } from "os";
|
|
24388
|
-
import { join as
|
|
24541
|
+
import { join as join6 } from "path";
|
|
24389
24542
|
|
|
24390
24543
|
// plugin-logger.ts
|
|
24391
|
-
import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
|
|
24544
|
+
import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
|
|
24392
24545
|
import { homedir } from "os";
|
|
24393
24546
|
import { dirname, join } from "path";
|
|
24394
24547
|
var DEFAULT_LOG_PATH = join(homedir(), ".switchroom", "logs", "telegram-plugin.log");
|
|
24395
|
-
var ROTATE_AT_BYTES =
|
|
24548
|
+
var ROTATE_AT_BYTES = 50 * 1024 * 1024;
|
|
24549
|
+
var ROTATION_BACKUPS = 5;
|
|
24396
24550
|
var activeHandle = null;
|
|
24397
24551
|
function resolveLogPath(env = process.env) {
|
|
24398
24552
|
const override = env.SWITCHROOM_TELEGRAM_LOG_PATH;
|
|
@@ -24410,6 +24564,14 @@ function rotateIfNeeded(path) {
|
|
|
24410
24564
|
const st = statSync(path);
|
|
24411
24565
|
if (st.size < ROTATE_AT_BYTES)
|
|
24412
24566
|
return;
|
|
24567
|
+
for (let i = ROTATION_BACKUPS - 1;i >= 1; i--) {
|
|
24568
|
+
const src = `${path}.${i}`;
|
|
24569
|
+
const dst = `${path}.${i + 1}`;
|
|
24570
|
+
try {
|
|
24571
|
+
if (existsSync(src))
|
|
24572
|
+
renameSync(src, dst);
|
|
24573
|
+
} catch {}
|
|
24574
|
+
}
|
|
24413
24575
|
const backup = `${path}.1`;
|
|
24414
24576
|
renameSync(path, backup);
|
|
24415
24577
|
} catch {}
|
|
@@ -24482,12 +24644,12 @@ function shouldFallBackToLegacy(input) {
|
|
|
24482
24644
|
// server.ts
|
|
24483
24645
|
installPluginLogger();
|
|
24484
24646
|
{
|
|
24485
|
-
const _stateDir = process.env.TELEGRAM_STATE_DIR ??
|
|
24486
|
-
const _gatewaySocket = process.env.SWITCHROOM_GATEWAY_SOCKET ??
|
|
24487
|
-
const _gatewayPidPath = process.env.SWITCHROOM_GATEWAY_PID_FILE ??
|
|
24647
|
+
const _stateDir = process.env.TELEGRAM_STATE_DIR ?? join6(homedir5(), ".claude", "channels", "telegram");
|
|
24648
|
+
const _gatewaySocket = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join6(_stateDir, "gateway.sock");
|
|
24649
|
+
const _gatewayPidPath = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join6(_stateDir, "gateway.pid.json");
|
|
24488
24650
|
let _gatewayLive = false;
|
|
24489
24651
|
async function probeSocketOnce() {
|
|
24490
|
-
if (!
|
|
24652
|
+
if (!existsSync6(_gatewaySocket))
|
|
24491
24653
|
return;
|
|
24492
24654
|
try {
|
|
24493
24655
|
await Bun.connect({
|
|
@@ -39,6 +39,13 @@ export interface FleetMember {
|
|
|
39
39
|
errorSeen: boolean
|
|
40
40
|
/** Snapshot of driver's currentTurnKey at sub_agent_started. Stable across turns. */
|
|
41
41
|
originatingTurnKey: string
|
|
42
|
+
/**
|
|
43
|
+
* True if this member was dispatched with `run_in_background: true`.
|
|
44
|
+
* Sticky — does NOT clear when status later promotes from background →
|
|
45
|
+
* running. Used by `hasLiveBackground` to keep the parent turn's
|
|
46
|
+
* PerChatState alive even after the member starts doing tool work.
|
|
47
|
+
*/
|
|
48
|
+
isBackgroundDispatch: boolean
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
export interface CreateFleetMemberArgs {
|
|
@@ -46,6 +53,7 @@ export interface CreateFleetMemberArgs {
|
|
|
46
53
|
role: string
|
|
47
54
|
startedAt: number
|
|
48
55
|
originatingTurnKey: string
|
|
56
|
+
isBackgroundDispatch?: boolean
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
export function createFleetMember(args: CreateFleetMemberArgs): FleetMember {
|
|
@@ -60,6 +68,7 @@ export function createFleetMember(args: CreateFleetMemberArgs): FleetMember {
|
|
|
60
68
|
terminalAt: null,
|
|
61
69
|
errorSeen: false,
|
|
62
70
|
originatingTurnKey: args.originatingTurnKey,
|
|
71
|
+
isBackgroundDispatch: args.isBackgroundDispatch ?? false,
|
|
63
72
|
}
|
|
64
73
|
}
|
|
65
74
|
|
|
@@ -69,10 +78,12 @@ export function applyToolUse(
|
|
|
69
78
|
input: Record<string, unknown> | undefined,
|
|
70
79
|
now: number,
|
|
71
80
|
): FleetMember {
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
81
|
+
// A live tool event proves the sub-agent is active — flip stuck or
|
|
82
|
+
// background back to running. Terminal statuses (done/failed/killed)
|
|
83
|
+
// are sticky and never reset here. Fixes #757: background members
|
|
84
|
+
// previously stayed ⏸ even while doing real work.
|
|
85
|
+
const status: FleetStatus =
|
|
86
|
+
member.status === 'stuck' || member.status === 'background' ? 'running' : member.status
|
|
76
87
|
return {
|
|
77
88
|
...member,
|
|
78
89
|
status,
|
|
@@ -126,15 +137,19 @@ export function markStuck(member: FleetMember, now: number, idleMs: number = 60_
|
|
|
126
137
|
}
|
|
127
138
|
|
|
128
139
|
/**
|
|
129
|
-
* P2 of #662 / fixes #64 — true if any fleet member
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
140
|
+
* P2 of #662 / fixes #64 — true if any fleet member was dispatched as a
|
|
141
|
+
* background worker AND has not yet reached terminal state. Used by the
|
|
142
|
+
* driver's dispose path to keep a PerChatState alive past parent turn_end
|
|
143
|
+
* while background sub-agents are still running, and by the v2 renderer's
|
|
144
|
+
* phase resolver to choose ⏸ Background vs ✅ Done.
|
|
145
|
+
*
|
|
146
|
+
* Uses `isBackgroundDispatch` (sticky) rather than current `status`, because
|
|
147
|
+
* background members promote to `running` once tool activity is observed
|
|
148
|
+
* (fixes #757 — card goes silent for active background workers).
|
|
134
149
|
*/
|
|
135
150
|
export function hasLiveBackground(fleet: ReadonlyMap<string, FleetMember>): boolean {
|
|
136
151
|
for (const m of fleet.values()) {
|
|
137
|
-
if (m.
|
|
152
|
+
if (m.isBackgroundDispatch && m.terminalAt == null) return true
|
|
138
153
|
}
|
|
139
154
|
return false
|
|
140
155
|
}
|
|
@@ -114,12 +114,47 @@ try {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// ─── Bot token ────────────────────────────────────────────────────────────
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
// Issue #758: when bot_token is a `vault:` ref and no .env was written,
|
|
118
|
+
// materialize it from the vault at startup (in-memory only).
|
|
119
|
+
//
|
|
120
|
+
// The outer try/catch is narrowed (post-#761 review) to ONLY catch
|
|
121
|
+
// ERR_MODULE_NOT_FOUND from the dynamic import. Other errors (e.g. throws
|
|
122
|
+
// from inside materializeBotToken that aren't BotTokenMaterializeError)
|
|
123
|
+
// must propagate so we don't mask real bugs behind the legacy "set in .env"
|
|
124
|
+
// hint.
|
|
125
|
+
type MaterializeMod = typeof import('../../src/telegram/materialize-bot-token.js')
|
|
126
|
+
let materializeMod: MaterializeMod | null = null
|
|
127
|
+
try {
|
|
128
|
+
materializeMod = await import('../../src/telegram/materialize-bot-token.js')
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const code = (err as NodeJS.ErrnoException | undefined)?.code
|
|
131
|
+
if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {
|
|
132
|
+
// Module missing — fall through with materializeMod=null.
|
|
133
|
+
} else {
|
|
134
|
+
throw err
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let TOKEN: string
|
|
139
|
+
if (materializeMod !== null) {
|
|
140
|
+
const { materializeBotToken, BotTokenMaterializeError } = materializeMod
|
|
141
|
+
try {
|
|
142
|
+
TOKEN = await materializeBotToken()
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if (err instanceof BotTokenMaterializeError) {
|
|
145
|
+
process.stderr.write(`foreman: ${err.message}\n`)
|
|
146
|
+
process.exit(1)
|
|
147
|
+
}
|
|
148
|
+
throw err
|
|
149
|
+
}
|
|
150
|
+
} else if (process.env.TELEGRAM_BOT_TOKEN) {
|
|
151
|
+
TOKEN = process.env.TELEGRAM_BOT_TOKEN
|
|
152
|
+
} else {
|
|
119
153
|
process.stderr.write(
|
|
120
154
|
`foreman: TELEGRAM_BOT_TOKEN required\n` +
|
|
121
155
|
` set in ${ENV_FILE}\n` +
|
|
122
|
-
` format: TELEGRAM_BOT_TOKEN=123456789:AAH...\n
|
|
156
|
+
` format: TELEGRAM_BOT_TOKEN=123456789:AAH...\n` +
|
|
157
|
+
` (token-materialization helper not found)\n`,
|
|
123
158
|
)
|
|
124
159
|
process.exit(1)
|
|
125
160
|
}
|