switchroom 0.18.24 → 0.18.25
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/cli/switchroom.js +59 -11
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +26 -0
- package/telegram-plugin/dist/gateway/gateway.js +1489 -829
- package/telegram-plugin/dist/server.js +26 -0
- package/telegram-plugin/gateway/callback-query-handlers.ts +7 -0
- package/telegram-plugin/gateway/gateway.ts +314 -3
- package/telegram-plugin/gateway/model-command.ts +188 -56
- package/telegram-plugin/gateway/redelivery-decision.ts +139 -0
- package/telegram-plugin/gateway/vault-grant-inbound-builders.ts +42 -1
- package/telegram-plugin/history.ts +118 -0
- package/telegram-plugin/registry/turns-schema.ts +89 -1
- package/telegram-plugin/session-tail.ts +185 -0
- package/telegram-plugin/subagent-watcher.ts +45 -0
- package/telegram-plugin/tests/crash-redelivery-resume-exclusion.test.ts +133 -0
- package/telegram-plugin/tests/crash-redelivery-wiring.test.ts +72 -0
- package/telegram-plugin/tests/history.test.ts +91 -0
- package/telegram-plugin/tests/model-command.test.ts +189 -12
- package/telegram-plugin/tests/redelivery-decision.test.ts +84 -0
- package/telegram-plugin/tests/registry-turns.test.ts +51 -0
- package/telegram-plugin/tests/session-model-source.test.ts +11 -0
- package/telegram-plugin/tests/session-tail.test.ts +145 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +50 -0
- package/telegram-plugin/tests/tool-activity-summary.test.ts +109 -0
- package/telegram-plugin/tests/trailing-answer-projector.test.ts +124 -0
- package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +125 -0
- package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +306 -0
- package/telegram-plugin/tool-activity-summary.ts +54 -3
- package/telegram-plugin/worker-activity-feed.ts +104 -0
- package/vendor/hindsight-memory/scripts/backfill_transcripts.py +762 -0
- package/vendor/hindsight-memory/scripts/drain_pending.py +13 -1
- package/vendor/hindsight-memory/scripts/lib/client.py +14 -4
- package/vendor/hindsight-memory/scripts/lib/config.py +8 -0
- package/vendor/hindsight-memory/scripts/lib/pacing.py +102 -0
- package/vendor/hindsight-memory/scripts/lib/watermark.py +213 -0
- package/vendor/hindsight-memory/scripts/reconcile_tail.py +344 -0
- package/vendor/hindsight-memory/scripts/retain.py +299 -143
- package/vendor/hindsight-memory/scripts/session_start.py +14 -0
- package/vendor/hindsight-memory/scripts/tests/test_backfill.py +362 -0
- package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +350 -0
- package/vendor/hindsight-memory/tests/test_hooks.py +8 -2
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.18.
|
|
2123
|
+
var VERSION = "0.18.25", COMMIT_SHA = "01803cff";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -15458,18 +15458,32 @@ async function injectSlashCommand(agentName, command, opts = {}) {
|
|
|
15458
15458
|
const socket = opts.socketName ?? defaultSocketName(agentName);
|
|
15459
15459
|
const session = opts.sessionName ?? agentName;
|
|
15460
15460
|
const settleMs = opts.settleMs ?? 2000;
|
|
15461
|
-
const
|
|
15461
|
+
const signalMode = !!(opts.successPattern || opts.errorPattern);
|
|
15462
|
+
const timeoutMs = opts.timeoutMs ?? (signalMode ? 8000 : 5000);
|
|
15462
15463
|
return withPaneLock(`${socket}:${session}`, () => injectSlashCommandWith(makeTmuxRunner(tmuxBin), {
|
|
15463
15464
|
socket,
|
|
15464
15465
|
session,
|
|
15465
15466
|
command: command.trim(),
|
|
15466
15467
|
settleMs,
|
|
15467
15468
|
timeoutMs,
|
|
15468
|
-
precondition: opts.precondition
|
|
15469
|
+
precondition: opts.precondition,
|
|
15470
|
+
successPattern: opts.successPattern,
|
|
15471
|
+
errorPattern: opts.errorPattern,
|
|
15472
|
+
settleBeforeSendMs: opts.settleBeforeSendMs
|
|
15469
15473
|
}));
|
|
15470
15474
|
}
|
|
15471
15475
|
async function injectSlashCommandWith(runner, args) {
|
|
15472
|
-
const {
|
|
15476
|
+
const {
|
|
15477
|
+
socket,
|
|
15478
|
+
session,
|
|
15479
|
+
command,
|
|
15480
|
+
settleMs,
|
|
15481
|
+
timeoutMs,
|
|
15482
|
+
precondition,
|
|
15483
|
+
successPattern,
|
|
15484
|
+
errorPattern,
|
|
15485
|
+
settleBeforeSendMs
|
|
15486
|
+
} = args;
|
|
15473
15487
|
let bareVerb;
|
|
15474
15488
|
try {
|
|
15475
15489
|
bareVerb = validateInjectCommand(command);
|
|
@@ -15510,6 +15524,17 @@ async function injectSlashCommandWith(runner, args) {
|
|
|
15510
15524
|
errorMessage: "inject precondition returned false at write time; send aborted " + "(no keys sent)."
|
|
15511
15525
|
};
|
|
15512
15526
|
}
|
|
15527
|
+
if (settleBeforeSendMs && settleBeforeSendMs > 0) {
|
|
15528
|
+
const settleStart = Date.now();
|
|
15529
|
+
let prevSettle = runner.capture(socket, session) ?? "";
|
|
15530
|
+
while (Date.now() - settleStart < settleBeforeSendMs) {
|
|
15531
|
+
await sleep(POLL_INTERVAL_MS);
|
|
15532
|
+
const cur = runner.capture(socket, session) ?? "";
|
|
15533
|
+
if (cur === prevSettle)
|
|
15534
|
+
break;
|
|
15535
|
+
prevSettle = cur;
|
|
15536
|
+
}
|
|
15537
|
+
}
|
|
15513
15538
|
const before = runner.capture(socket, session) ?? "";
|
|
15514
15539
|
try {
|
|
15515
15540
|
runner.send(socket, session, ["send-keys", "-l", command]);
|
|
@@ -15528,9 +15553,23 @@ async function injectSlashCommandWith(runner, args) {
|
|
|
15528
15553
|
const start = Date.now();
|
|
15529
15554
|
let last = before;
|
|
15530
15555
|
let stableSince = null;
|
|
15556
|
+
const signalMode = !!(successPattern || errorPattern);
|
|
15531
15557
|
while (Date.now() - start < timeoutMs) {
|
|
15532
15558
|
await sleep(POLL_INTERVAL_MS);
|
|
15533
15559
|
const cur = runner.capture(socket, session) ?? "";
|
|
15560
|
+
if (signalMode) {
|
|
15561
|
+
last = cur;
|
|
15562
|
+
if (cur !== before) {
|
|
15563
|
+
const { output: region } = diffPane(before, cur, command);
|
|
15564
|
+
const regionLines = region.split(`
|
|
15565
|
+
`).map((l) => l.trim());
|
|
15566
|
+
if (errorPattern && regionLines.some((l) => errorPattern.test(l)))
|
|
15567
|
+
break;
|
|
15568
|
+
if (successPattern && regionLines.some((l) => successPattern.test(l)))
|
|
15569
|
+
break;
|
|
15570
|
+
}
|
|
15571
|
+
continue;
|
|
15572
|
+
}
|
|
15534
15573
|
if (cur === last && cur !== before) {
|
|
15535
15574
|
if (stableSince === null) {
|
|
15536
15575
|
stableSince = Date.now();
|
|
@@ -63219,8 +63258,9 @@ function isoFromTs(ts) {
|
|
|
63219
63258
|
return null;
|
|
63220
63259
|
return new Date(ts * 1000).toISOString();
|
|
63221
63260
|
}
|
|
63222
|
-
function detectTurnFindings(agent, turns) {
|
|
63261
|
+
function detectTurnFindings(agent, turns, opts = {}) {
|
|
63223
63262
|
const findings = [];
|
|
63263
|
+
const silentNoopFloorTs = opts.silentNoopFloorTs ?? SILENT_NOOP_FLOOR_TS;
|
|
63224
63264
|
for (const t of turns) {
|
|
63225
63265
|
const tid = t.turn_id ?? "?";
|
|
63226
63266
|
const st = t.status;
|
|
@@ -63254,7 +63294,7 @@ function detectTurnFindings(agent, turns) {
|
|
|
63254
63294
|
ts
|
|
63255
63295
|
});
|
|
63256
63296
|
}
|
|
63257
|
-
if (st === "complete" && tl === 0 && !synthetic) {
|
|
63297
|
+
if (st === "complete" && tl === 0 && !synthetic && t.ts != null && t.ts >= silentNoopFloorTs) {
|
|
63258
63298
|
findings.push({
|
|
63259
63299
|
signal: "silent-no-op-candidate",
|
|
63260
63300
|
agent,
|
|
@@ -63302,20 +63342,20 @@ function extractTs(line) {
|
|
|
63302
63342
|
const m = line.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?/);
|
|
63303
63343
|
return m ? m[0] : null;
|
|
63304
63344
|
}
|
|
63305
|
-
function scanAgent(agent, turnsText, gatewayText) {
|
|
63345
|
+
function scanAgent(agent, turnsText, gatewayText, opts = {}) {
|
|
63306
63346
|
const turns = parseTurns(turnsText);
|
|
63307
63347
|
const status_mix = {};
|
|
63308
63348
|
for (const t of turns) {
|
|
63309
63349
|
const st = t.status ?? "unknown";
|
|
63310
63350
|
status_mix[st] = (status_mix[st] ?? 0) + 1;
|
|
63311
63351
|
}
|
|
63312
|
-
const turnFindings = detectTurnFindings(agent, turns);
|
|
63352
|
+
const turnFindings = detectTurnFindings(agent, turns, opts);
|
|
63313
63353
|
const { findings: gwFindings, gw_hits } = detectGatewayFindings(agent, gatewayText);
|
|
63314
63354
|
const findings = [...turnFindings, ...gwFindings];
|
|
63315
63355
|
const escalate = turnFindings.some((f) => f.signal === "killed-incomplete-turn" || f.signal === "hang-long-stalled" || f.signal === "silent-no-op-candidate" || f.signal === "send-failed-delivery") || gw_hits["duplicate-delivery-represent"] > 0 || gw_hits["reply-delivery-failure"] > 0;
|
|
63316
63356
|
return { agent, turns: turns.length, status_mix, findings, gw_hits, escalate };
|
|
63317
63357
|
}
|
|
63318
|
-
var HANG_MS = 360000, HANG_MAXTOOLS = 2, GATEWAY_SIGNATURES;
|
|
63358
|
+
var HANG_MS = 360000, HANG_MAXTOOLS = 2, SILENT_NOOP_FLOOR_TS = 1783900800, GATEWAY_SIGNATURES;
|
|
63319
63359
|
var init_detect = __esm(() => {
|
|
63320
63360
|
GATEWAY_SIGNATURES = {
|
|
63321
63361
|
"duplicate-delivery-represent": /represent duplicate-send/,
|
|
@@ -63664,7 +63704,9 @@ function runScan(opts = {}) {
|
|
|
63664
63704
|
continue;
|
|
63665
63705
|
}
|
|
63666
63706
|
try {
|
|
63667
|
-
const res = scanAgent(agent, turnsText, gwText
|
|
63707
|
+
const res = scanAgent(agent, turnsText, gwText, {
|
|
63708
|
+
silentNoopFloorTs: opts.silentNoopFloorTs
|
|
63709
|
+
});
|
|
63668
63710
|
perAgent.push(res);
|
|
63669
63711
|
findings.push(...res.findings);
|
|
63670
63712
|
scanned++;
|
|
@@ -78672,12 +78714,16 @@ var PHASE2_MIGRATIONS = [
|
|
|
78672
78714
|
var PHASE3_MIGRATIONS = [
|
|
78673
78715
|
`ALTER TABLE turns ADD COLUMN resumed_at INTEGER`
|
|
78674
78716
|
];
|
|
78717
|
+
var PHASE4_MIGRATIONS = [
|
|
78718
|
+
`ALTER TABLE turns ADD COLUMN session_id TEXT`,
|
|
78719
|
+
`ALTER TABLE turns ADD COLUMN answer_redelivered_at INTEGER`
|
|
78720
|
+
];
|
|
78675
78721
|
function applySchema(db) {
|
|
78676
78722
|
db.exec("PRAGMA journal_mode = WAL");
|
|
78677
78723
|
db.exec("PRAGMA synchronous = NORMAL");
|
|
78678
78724
|
db.exec("PRAGMA busy_timeout = 5000");
|
|
78679
78725
|
db.exec(SCHEMA_SQL);
|
|
78680
|
-
for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS, ...PHASE3_MIGRATIONS]) {
|
|
78726
|
+
for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS, ...PHASE3_MIGRATIONS, ...PHASE4_MIGRATIONS]) {
|
|
78681
78727
|
try {
|
|
78682
78728
|
db.exec(sql);
|
|
78683
78729
|
} catch (err) {
|
|
@@ -78720,6 +78766,8 @@ function mapRow(row) {
|
|
|
78720
78766
|
tool_call_count: row.tool_call_count,
|
|
78721
78767
|
interrupt_reason: row.interrupt_reason,
|
|
78722
78768
|
resumed_at: row.resumed_at,
|
|
78769
|
+
session_id: row.session_id ?? null,
|
|
78770
|
+
answer_redelivered_at: row.answer_redelivered_at ?? null,
|
|
78723
78771
|
created_at: row.created_at,
|
|
78724
78772
|
updated_at: row.updated_at
|
|
78725
78773
|
};
|
|
@@ -26605,7 +26605,7 @@ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:f
|
|
|
26605
26605
|
import { dirname as dirname4, join as join7 } from "node:path";
|
|
26606
26606
|
|
|
26607
26607
|
// src/build-info.ts
|
|
26608
|
-
var VERSION = "0.18.
|
|
26608
|
+
var VERSION = "0.18.25";
|
|
26609
26609
|
|
|
26610
26610
|
// src/cli/resolve-version.ts
|
|
26611
26611
|
function readPackageVersion() {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
3
|
"//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
|
|
4
|
-
"version": "0.18.
|
|
4
|
+
"version": "0.18.25",
|
|
5
5
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -23277,6 +23277,13 @@ function projectAssistantTextBlocks(content, make) {
|
|
|
23277
23277
|
});
|
|
23278
23278
|
return out;
|
|
23279
23279
|
}
|
|
23280
|
+
function sumUsageTokens(usage) {
|
|
23281
|
+
if (usage == null || typeof usage !== "object")
|
|
23282
|
+
return 0;
|
|
23283
|
+
const u = usage;
|
|
23284
|
+
const n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
23285
|
+
return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens);
|
|
23286
|
+
}
|
|
23280
23287
|
function assistantLineCarriesAnswerSurface(content) {
|
|
23281
23288
|
if (!Array.isArray(content))
|
|
23282
23289
|
return false;
|
|
@@ -23332,6 +23339,15 @@ function projectTranscriptLine(line) {
|
|
|
23332
23339
|
if (typeof mainModel === "string" && !isModelSentinel(mainModel)) {
|
|
23333
23340
|
events.push({ kind: "model", model: mainModel });
|
|
23334
23341
|
}
|
|
23342
|
+
const mainUsageTotal = sumUsageTokens(message?.usage);
|
|
23343
|
+
if (mainUsageTotal > 0) {
|
|
23344
|
+
const mainMsgId = message?.id;
|
|
23345
|
+
events.push({
|
|
23346
|
+
kind: "usage",
|
|
23347
|
+
messageId: typeof mainMsgId === "string" ? mainMsgId : null,
|
|
23348
|
+
totalTokens: mainUsageTotal
|
|
23349
|
+
});
|
|
23350
|
+
}
|
|
23335
23351
|
const textEvents = projectAssistantTextBlocks(content, (text, blockIndex, lastInMessage) => ({ kind: "text", text, blockIndex, lastInMessage }));
|
|
23336
23352
|
content.forEach((c, i) => {
|
|
23337
23353
|
const ct = c.type;
|
|
@@ -23438,6 +23454,16 @@ function projectSubagentLine(line, agentId, state) {
|
|
|
23438
23454
|
if (typeof subModel === "string" && !isModelSentinel(subModel)) {
|
|
23439
23455
|
events.push({ kind: "sub_agent_model", agentId, model: subModel });
|
|
23440
23456
|
}
|
|
23457
|
+
const subUsageTotal = sumUsageTokens(message?.usage);
|
|
23458
|
+
if (subUsageTotal > 0) {
|
|
23459
|
+
const subMsgId = message?.id;
|
|
23460
|
+
events.push({
|
|
23461
|
+
kind: "sub_agent_usage",
|
|
23462
|
+
agentId,
|
|
23463
|
+
messageId: typeof subMsgId === "string" ? subMsgId : null,
|
|
23464
|
+
totalTokens: subUsageTotal
|
|
23465
|
+
});
|
|
23466
|
+
}
|
|
23441
23467
|
const textEvents = projectAssistantTextBlocks(content, (text, blockIndex, lastInMessage) => ({
|
|
23442
23468
|
kind: "sub_agent_text",
|
|
23443
23469
|
agentId,
|