switchroom 0.19.40 → 0.19.41
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 +39 -8
- package/dist/host-control/main.js +72 -17
- package/package.json +1 -1
- package/telegram-plugin/bridge/bridge.ts +2 -2
- package/telegram-plugin/dist/bridge/bridge.js +2 -2
- package/telegram-plugin/dist/gateway/gateway.js +240 -46
- package/telegram-plugin/dist/server.js +2 -2
- package/telegram-plugin/gateway/checklist-fallback.ts +370 -0
- package/telegram-plugin/gateway/gateway.ts +69 -69
- package/telegram-plugin/gateway/turn-record-status.ts +80 -0
- package/telegram-plugin/tests/checklist-fallback.test.ts +317 -0
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +10 -6
- package/telegram-plugin/tests/turn-record-status.test.ts +62 -0
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.19.
|
|
2123
|
+
var VERSION = "0.19.41", COMMIT_SHA = "e77ee9c0";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -70125,6 +70125,9 @@ var init_server4 = __esm(() => {
|
|
|
70125
70125
|
];
|
|
70126
70126
|
});
|
|
70127
70127
|
|
|
70128
|
+
// telegram-plugin/gateway/turn-record-status.ts
|
|
70129
|
+
var ROUTE_FIELD_SHIP_TS = 1785456000;
|
|
70130
|
+
|
|
70128
70131
|
// src/fleet-health/detect.ts
|
|
70129
70132
|
function parseTurns(text) {
|
|
70130
70133
|
const out = [];
|
|
@@ -70160,12 +70163,14 @@ function isoFromTs(ts) {
|
|
|
70160
70163
|
function detectTurnFindings(agent, turns, opts = {}) {
|
|
70161
70164
|
const findings = [];
|
|
70162
70165
|
const silentNoopFloorTs = opts.silentNoopFloorTs ?? SILENT_NOOP_FLOOR_TS;
|
|
70166
|
+
const routeFieldShipTs = opts.routeFieldShipTs ?? ROUTE_FIELD_SHIP_TS;
|
|
70163
70167
|
for (const t of ownedTurns(agent, turns)) {
|
|
70164
70168
|
const tid = typeof t.turn_id === "string" ? t.turn_id : "?";
|
|
70165
70169
|
const st = t.status;
|
|
70166
70170
|
const tl = typeof t.tools === "number" ? t.tools : 0;
|
|
70167
70171
|
const dur = typeof t.duration_ms === "number" ? t.duration_ms : 0;
|
|
70168
70172
|
const synthetic = tid.includes("synthetic-");
|
|
70173
|
+
const route = typeof t.route === "string" ? t.route : undefined;
|
|
70169
70174
|
const ts = isoFromTs(t.ts);
|
|
70170
70175
|
if (st === "send_failed") {
|
|
70171
70176
|
findings.push({
|
|
@@ -70194,13 +70199,33 @@ function detectTurnFindings(agent, turns, opts = {}) {
|
|
|
70194
70199
|
});
|
|
70195
70200
|
}
|
|
70196
70201
|
if (st === "complete" && tl === 0 && !synthetic && t.ts != null && t.ts >= silentNoopFloorTs) {
|
|
70197
|
-
|
|
70198
|
-
|
|
70199
|
-
|
|
70200
|
-
|
|
70201
|
-
|
|
70202
|
-
|
|
70203
|
-
|
|
70202
|
+
if (route === "flush") {
|
|
70203
|
+
findings.push({
|
|
70204
|
+
signal: "flush-recovered-turn",
|
|
70205
|
+
agent,
|
|
70206
|
+
turn_id: tid,
|
|
70207
|
+
log_pointer: `turns.jsonl:${tid} tools=0 route=flush`,
|
|
70208
|
+
ts
|
|
70209
|
+
});
|
|
70210
|
+
} else if (route === "reply" || route === "stream") {} else if (route === "none") {
|
|
70211
|
+
findings.push({
|
|
70212
|
+
signal: "silent-no-op-candidate",
|
|
70213
|
+
agent,
|
|
70214
|
+
turn_id: tid,
|
|
70215
|
+
log_pointer: `turns.jsonl:${tid} tools=0 route=none`,
|
|
70216
|
+
ts
|
|
70217
|
+
});
|
|
70218
|
+
} else {
|
|
70219
|
+
if (t.ts >= routeFieldShipTs) {
|
|
70220
|
+
findings.push({
|
|
70221
|
+
signal: "silent-no-op-candidate",
|
|
70222
|
+
agent,
|
|
70223
|
+
turn_id: tid,
|
|
70224
|
+
log_pointer: `turns.jsonl:${tid} tools=0`,
|
|
70225
|
+
ts
|
|
70226
|
+
});
|
|
70227
|
+
}
|
|
70228
|
+
}
|
|
70204
70229
|
}
|
|
70205
70230
|
}
|
|
70206
70231
|
return findings;
|
|
@@ -70314,6 +70339,12 @@ var init_mapping = __esm(() => {
|
|
|
70314
70339
|
job_spec: "know-what-my-agent-is-doing",
|
|
70315
70340
|
signature: "silent-no-op:completed-zero-tools"
|
|
70316
70341
|
},
|
|
70342
|
+
"flush-recovered-turn": {
|
|
70343
|
+
failure_mode: "drift",
|
|
70344
|
+
severity: 1,
|
|
70345
|
+
job_spec: "know-what-my-agent-is-doing",
|
|
70346
|
+
signature: "flush-recovered:completed-zero-tools-route-flush"
|
|
70347
|
+
},
|
|
70317
70348
|
"duplicate-delivery-represent": {
|
|
70318
70349
|
failure_mode: "duplicate",
|
|
70319
70350
|
severity: 2,
|
|
@@ -21010,6 +21010,7 @@ function parsePendingRolloutMarker(raw) {
|
|
|
21010
21010
|
if (o.allow_downgrade !== undefined && typeof o.allow_downgrade !== "boolean") {
|
|
21011
21011
|
return null;
|
|
21012
21012
|
}
|
|
21013
|
+
const narrationIdValid = typeof o.narration_message_id === "number" && Number.isInteger(o.narration_message_id) && o.narration_message_id > 0;
|
|
21013
21014
|
return {
|
|
21014
21015
|
v: 1,
|
|
21015
21016
|
request_id: o.request_id,
|
|
@@ -21017,6 +21018,7 @@ function parsePendingRolloutMarker(raw) {
|
|
|
21017
21018
|
...o.agents !== undefined ? { agents: o.agents } : {},
|
|
21018
21019
|
...o.skip_web !== undefined ? { skip_web: o.skip_web } : {},
|
|
21019
21020
|
...o.allow_downgrade !== undefined ? { allow_downgrade: o.allow_downgrade } : {},
|
|
21021
|
+
...narrationIdValid ? { narration_message_id: o.narration_message_id } : {},
|
|
21020
21022
|
caller: caller.kind === "agent" ? { kind: "agent", name: caller.name } : { kind: "operator" },
|
|
21021
21023
|
created_at: o.created_at,
|
|
21022
21024
|
prior_hostd_version: o.prior_hostd_version
|
|
@@ -21320,7 +21322,7 @@ function allocateAgentUid(name) {
|
|
|
21320
21322
|
}
|
|
21321
21323
|
|
|
21322
21324
|
// src/build-info.ts
|
|
21323
|
-
var VERSION = "0.19.
|
|
21325
|
+
var VERSION = "0.19.41";
|
|
21324
21326
|
|
|
21325
21327
|
// src/setup/hindsight-recall-tunables.ts
|
|
21326
21328
|
var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
|
|
@@ -30786,6 +30788,27 @@ class HostdServer {
|
|
|
30786
30788
|
})
|
|
30787
30789
|
};
|
|
30788
30790
|
}
|
|
30791
|
+
persistNarrationMessageId(requestId, messageId) {
|
|
30792
|
+
try {
|
|
30793
|
+
if (!Number.isInteger(messageId) || messageId <= 0)
|
|
30794
|
+
return;
|
|
30795
|
+
const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
30796
|
+
if (!existsSync15(markerPath))
|
|
30797
|
+
return;
|
|
30798
|
+
const marker = parsePendingRolloutMarker(readFileSync11(markerPath, "utf8"));
|
|
30799
|
+
if (!marker)
|
|
30800
|
+
return;
|
|
30801
|
+
if (marker.request_id !== requestId)
|
|
30802
|
+
return;
|
|
30803
|
+
const updated = {
|
|
30804
|
+
...marker,
|
|
30805
|
+
narration_message_id: messageId
|
|
30806
|
+
};
|
|
30807
|
+
const tmp = `${markerPath}.tmp`;
|
|
30808
|
+
writeFileSync6(tmp, encodePendingRolloutMarker(updated), { mode: 384 });
|
|
30809
|
+
renameSync5(tmp, markerPath);
|
|
30810
|
+
} catch {}
|
|
30811
|
+
}
|
|
30789
30812
|
async resumePendingSelfBumpRollout() {
|
|
30790
30813
|
const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
30791
30814
|
if (!existsSync15(markerPath))
|
|
@@ -30854,6 +30877,9 @@ class HostdServer {
|
|
|
30854
30877
|
...marker.skip_web ? { skip_web: true } : {},
|
|
30855
30878
|
...marker.allow_downgrade ? { allow_downgrade: true } : {}
|
|
30856
30879
|
}, marker.request_id, caller, Date.now());
|
|
30880
|
+
if (marker.narration_message_id !== undefined && caller.kind === "agent") {
|
|
30881
|
+
this.rolloutNarrator?.seedPostedMessage?.(marker.request_id, caller.name, marker.narration_message_id);
|
|
30882
|
+
}
|
|
30857
30883
|
this.onRolloutPhase(entry, { phase: "self-bump-done", target: marker.pin });
|
|
30858
30884
|
}
|
|
30859
30885
|
async handleAgentStart(req, started) {
|
|
@@ -32672,28 +32698,49 @@ class LogTailRolloutNarrator {
|
|
|
32672
32698
|
}
|
|
32673
32699
|
}
|
|
32674
32700
|
}
|
|
32701
|
+
freshState(agentName, target) {
|
|
32702
|
+
return {
|
|
32703
|
+
agentName,
|
|
32704
|
+
lastAppliedSeq: -1,
|
|
32705
|
+
messageId: null,
|
|
32706
|
+
posting: false,
|
|
32707
|
+
postAttempts: 0,
|
|
32708
|
+
postFailed: false,
|
|
32709
|
+
render: { target },
|
|
32710
|
+
agents: [],
|
|
32711
|
+
singletons: initialSingletons(),
|
|
32712
|
+
frozen: false,
|
|
32713
|
+
timer: null,
|
|
32714
|
+
pendingEditAfterPost: false
|
|
32715
|
+
};
|
|
32716
|
+
}
|
|
32675
32717
|
ensureState(entry) {
|
|
32676
32718
|
let st = this.states.get(entry.request_id);
|
|
32677
32719
|
if (!st) {
|
|
32678
32720
|
const agentName = entry.caller.kind === "agent" ? entry.caller.name : "";
|
|
32679
|
-
st =
|
|
32680
|
-
agentName,
|
|
32681
|
-
lastAppliedSeq: -1,
|
|
32682
|
-
messageId: null,
|
|
32683
|
-
posting: false,
|
|
32684
|
-
postAttempts: 0,
|
|
32685
|
-
postFailed: false,
|
|
32686
|
-
render: { target: entry.pin ?? "" },
|
|
32687
|
-
agents: [],
|
|
32688
|
-
singletons: initialSingletons(),
|
|
32689
|
-
frozen: false,
|
|
32690
|
-
timer: null,
|
|
32691
|
-
pendingEditAfterPost: false
|
|
32692
|
-
};
|
|
32721
|
+
st = this.freshState(agentName, entry.pin ?? "");
|
|
32693
32722
|
this.states.set(entry.request_id, st);
|
|
32694
32723
|
}
|
|
32695
32724
|
return st;
|
|
32696
32725
|
}
|
|
32726
|
+
seedPostedMessage(requestId, agentName, messageId) {
|
|
32727
|
+
const existing = this.states.get(requestId);
|
|
32728
|
+
if (existing) {
|
|
32729
|
+
if (existing.messageId === null) {
|
|
32730
|
+
existing.messageId = messageId;
|
|
32731
|
+
existing.posting = false;
|
|
32732
|
+
existing.postFailed = false;
|
|
32733
|
+
if (existing.postAttempts < 1)
|
|
32734
|
+
existing.postAttempts = 1;
|
|
32735
|
+
existing.pendingEditAfterPost = false;
|
|
32736
|
+
}
|
|
32737
|
+
return;
|
|
32738
|
+
}
|
|
32739
|
+
const st = this.freshState(agentName, "");
|
|
32740
|
+
st.messageId = messageId;
|
|
32741
|
+
st.postAttempts = 1;
|
|
32742
|
+
this.states.set(requestId, st);
|
|
32743
|
+
}
|
|
32697
32744
|
scheduleRenderOrPost(requestId) {
|
|
32698
32745
|
const st = this.states.get(requestId);
|
|
32699
32746
|
if (!st)
|
|
@@ -32734,6 +32781,11 @@ class LogTailRolloutNarrator {
|
|
|
32734
32781
|
}
|
|
32735
32782
|
st.messageId = mid;
|
|
32736
32783
|
st.postFailed = false;
|
|
32784
|
+
try {
|
|
32785
|
+
this.opts.onMessageId?.(requestId, mid);
|
|
32786
|
+
} catch (e) {
|
|
32787
|
+
this.opts.log?.(`onMessageId sink threw (non-fatal): ${e.message}`);
|
|
32788
|
+
}
|
|
32737
32789
|
if (st.pendingEditAfterPost) {
|
|
32738
32790
|
st.pendingEditAfterPost = false;
|
|
32739
32791
|
this.flush(requestId, false);
|
|
@@ -33378,8 +33430,11 @@ async function main() {
|
|
|
33378
33430
|
resolveGatewaySocket,
|
|
33379
33431
|
log: (m) => process.stderr.write(`hostd: rollout-narration — ${m}
|
|
33380
33432
|
`)
|
|
33381
|
-
}), {
|
|
33382
|
-
|
|
33433
|
+
}), {
|
|
33434
|
+
log: (m) => process.stderr.write(`hostd: rollout-narration — ${m}
|
|
33435
|
+
`),
|
|
33436
|
+
onMessageId: (rid, mid) => server?.persistNarrationMessageId(rid, mid)
|
|
33437
|
+
});
|
|
33383
33438
|
const server = new HostdServer({
|
|
33384
33439
|
homeDir: homedir8(),
|
|
33385
33440
|
agentUids,
|
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.19.
|
|
4
|
+
"version": "0.19.41",
|
|
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": {
|
|
@@ -268,7 +268,7 @@ const TOOL_SCHEMAS = [
|
|
|
268
268
|
{
|
|
269
269
|
name: 'send_checklist',
|
|
270
270
|
description:
|
|
271
|
-
'Send a
|
|
271
|
+
'Send a checklist (task list) to a chat. Native interactive Telegram checklists require a Business-account connection, which most deployments do not have — in that normal case the checklist is sent as a formatted TEXT message (bold title + ✅/⬜ task lines) and the result carries degraded:"text": tasks are NOT tappable, and you tick them yourself via update_checklist (task ids are 1..N in send order). With a Business connection configured, the checklist is sent natively and tick events arrive as channel events with kind="checklist_task_changed". Returns JSON with message_id and mode. Limit: 30 tasks.',
|
|
272
272
|
inputSchema: {
|
|
273
273
|
type: 'object',
|
|
274
274
|
properties: {
|
|
@@ -357,7 +357,7 @@ const TOOL_SCHEMAS = [
|
|
|
357
357
|
{
|
|
358
358
|
name: 'update_checklist',
|
|
359
359
|
description:
|
|
360
|
-
'Patch
|
|
360
|
+
'Patch a checklist previously sent with send_checklist: update the title, append tasks, or mark tasks done/undone. Tasks with an id target existing items (ids are 1..N in send order for text-mode checklists); tasks without an id are appended. Removal is not supported. The edit re-renders in the mode the checklist was sent in (text for most deployments). Returns JSON { ok, ... }; after a gateway restart the stored task state may be gone — then it returns ok:false with reason "unknown_checklist" unless you pass a full replacement (title + every task\'s text).',
|
|
361
361
|
inputSchema: {
|
|
362
362
|
type: 'object',
|
|
363
363
|
properties: {
|
|
@@ -25241,7 +25241,7 @@ var TOOL_SCHEMAS = [
|
|
|
25241
25241
|
},
|
|
25242
25242
|
{
|
|
25243
25243
|
name: "send_checklist",
|
|
25244
|
-
description: 'Send a
|
|
25244
|
+
description: 'Send a checklist (task list) to a chat. Native interactive Telegram checklists require a Business-account connection, which most deployments do not have \u2014 in that normal case the checklist is sent as a formatted TEXT message (bold title + \u2705/\u2B1C task lines) and the result carries degraded:"text": tasks are NOT tappable, and you tick them yourself via update_checklist (task ids are 1..N in send order). With a Business connection configured, the checklist is sent natively and tick events arrive as channel events with kind="checklist_task_changed". Returns JSON with message_id and mode. Limit: 30 tasks.',
|
|
25245
25245
|
inputSchema: {
|
|
25246
25246
|
type: "object",
|
|
25247
25247
|
properties: {
|
|
@@ -25326,7 +25326,7 @@ var TOOL_SCHEMAS = [
|
|
|
25326
25326
|
},
|
|
25327
25327
|
{
|
|
25328
25328
|
name: "update_checklist",
|
|
25329
|
-
description:
|
|
25329
|
+
description: `Patch a checklist previously sent with send_checklist: update the title, append tasks, or mark tasks done/undone. Tasks with an id target existing items (ids are 1..N in send order for text-mode checklists); tasks without an id are appended. Removal is not supported. The edit re-renders in the mode the checklist was sent in (text for most deployments). Returns JSON { ok, ... }; after a gateway restart the stored task state may be gone \u2014 then it returns ok:false with reason "unknown_checklist" unless you pass a full replacement (title + every task's text).`,
|
|
25330
25330
|
inputSchema: {
|
|
25331
25331
|
type: "object",
|
|
25332
25332
|
properties: {
|
|
@@ -39504,6 +39504,178 @@ function redactChecklistFields(title, tasks, redact) {
|
|
|
39504
39504
|
};
|
|
39505
39505
|
}
|
|
39506
39506
|
|
|
39507
|
+
// gateway/checklist-fallback.ts
|
|
39508
|
+
var CHECKLIST_MAX_TASKS = 30;
|
|
39509
|
+
function buildChecklistTasks(tasks) {
|
|
39510
|
+
if (tasks.length > CHECKLIST_MAX_TASKS) {
|
|
39511
|
+
throw new Error(`checklist exceeds ${CHECKLIST_MAX_TASKS}-task limit (got ${tasks.length})`);
|
|
39512
|
+
}
|
|
39513
|
+
return tasks.map((t, i) => ({ id: i + 1, text: t.text, done: t.done === true }));
|
|
39514
|
+
}
|
|
39515
|
+
function renderChecklistText(cl, opts) {
|
|
39516
|
+
const header = opts?.literal ? cl.title : `**${cl.title}**`;
|
|
39517
|
+
const lines = cl.tasks.map((t) => `${t.done ? "\u2705" : "\u2b1c"} ${t.text}`);
|
|
39518
|
+
return [header, ...lines].join(`
|
|
39519
|
+
`);
|
|
39520
|
+
}
|
|
39521
|
+
function applyChecklistPatch(cl, patch) {
|
|
39522
|
+
const tasks = cl.tasks.map((t) => ({ ...t }));
|
|
39523
|
+
for (const p of patch.tasks ?? []) {
|
|
39524
|
+
const pid = p.id != null ? Number(p.id) : undefined;
|
|
39525
|
+
const existing = pid != null ? tasks.find((t) => t.id === pid) : undefined;
|
|
39526
|
+
if (existing) {
|
|
39527
|
+
if (p.text != null)
|
|
39528
|
+
existing.text = p.text;
|
|
39529
|
+
if (p.done != null)
|
|
39530
|
+
existing.done = p.done;
|
|
39531
|
+
} else {
|
|
39532
|
+
const nextId = pid != null && Number.isFinite(pid) ? pid : tasks.reduce((m, t) => Math.max(m, t.id), 0) + 1;
|
|
39533
|
+
tasks.push({ id: nextId, text: p.text ?? "", done: p.done === true });
|
|
39534
|
+
}
|
|
39535
|
+
}
|
|
39536
|
+
if (tasks.length > CHECKLIST_MAX_TASKS) {
|
|
39537
|
+
throw new Error(`checklist exceeds ${CHECKLIST_MAX_TASKS}-task limit (got ${tasks.length})`);
|
|
39538
|
+
}
|
|
39539
|
+
return { title: patch.title ?? cl.title, tasks };
|
|
39540
|
+
}
|
|
39541
|
+
function buildNativeChecklistPayload(p) {
|
|
39542
|
+
return {
|
|
39543
|
+
business_connection_id: p.businessConnectionId,
|
|
39544
|
+
chat_id: p.chatId,
|
|
39545
|
+
checklist: {
|
|
39546
|
+
title: p.title,
|
|
39547
|
+
tasks: p.tasks.map((t) => ({ id: t.id, text: t.text }))
|
|
39548
|
+
},
|
|
39549
|
+
...p.replyToMessageId != null ? { reply_parameters: { message_id: p.replyToMessageId } } : {},
|
|
39550
|
+
...p.protectContent === true ? { protect_content: true } : {}
|
|
39551
|
+
};
|
|
39552
|
+
}
|
|
39553
|
+
function buildNativeEditChecklistPayload(p) {
|
|
39554
|
+
return {
|
|
39555
|
+
business_connection_id: p.businessConnectionId,
|
|
39556
|
+
chat_id: p.chatId,
|
|
39557
|
+
message_id: p.messageId,
|
|
39558
|
+
checklist: {
|
|
39559
|
+
title: p.title,
|
|
39560
|
+
tasks: p.tasks.map((t) => ({ id: t.id, text: t.text }))
|
|
39561
|
+
}
|
|
39562
|
+
};
|
|
39563
|
+
}
|
|
39564
|
+
function createChecklistStore(cap = 500) {
|
|
39565
|
+
const map = new Map;
|
|
39566
|
+
return {
|
|
39567
|
+
get: (key) => map.get(key),
|
|
39568
|
+
set: (key, state) => {
|
|
39569
|
+
if (map.has(key))
|
|
39570
|
+
map.delete(key);
|
|
39571
|
+
map.set(key, state);
|
|
39572
|
+
while (map.size > cap) {
|
|
39573
|
+
const oldest = map.keys().next().value;
|
|
39574
|
+
if (oldest == null)
|
|
39575
|
+
break;
|
|
39576
|
+
map.delete(oldest);
|
|
39577
|
+
}
|
|
39578
|
+
}
|
|
39579
|
+
};
|
|
39580
|
+
}
|
|
39581
|
+
function checklistStoreKey(chatId, messageId) {
|
|
39582
|
+
return `${chatId}:${messageId}`;
|
|
39583
|
+
}
|
|
39584
|
+
async function performSendChecklist(deps, args) {
|
|
39585
|
+
const tasks = buildChecklistTasks(args.tasks);
|
|
39586
|
+
if (deps.nativeAvailable && deps.businessConnectionId) {
|
|
39587
|
+
try {
|
|
39588
|
+
const sent2 = await deps.sendNative(buildNativeChecklistPayload({
|
|
39589
|
+
businessConnectionId: deps.businessConnectionId,
|
|
39590
|
+
chatId: args.chatId,
|
|
39591
|
+
title: args.title,
|
|
39592
|
+
tasks,
|
|
39593
|
+
replyToMessageId: args.replyToMessageId,
|
|
39594
|
+
protectContent: args.protectContent
|
|
39595
|
+
}));
|
|
39596
|
+
return {
|
|
39597
|
+
message_id: sent2.message_id,
|
|
39598
|
+
mode: "native",
|
|
39599
|
+
state: { title: args.title, tasks, mode: "native" }
|
|
39600
|
+
};
|
|
39601
|
+
} catch (err) {
|
|
39602
|
+
deps.log(`native sendChecklist failed \u2014 falling back to text render: ${err}
|
|
39603
|
+
`);
|
|
39604
|
+
}
|
|
39605
|
+
}
|
|
39606
|
+
const text = renderChecklistText({ title: args.title, tasks }, { literal: deps.literalText });
|
|
39607
|
+
const sent = await deps.sendText(text);
|
|
39608
|
+
return {
|
|
39609
|
+
message_id: sent.message_id,
|
|
39610
|
+
mode: "text",
|
|
39611
|
+
state: { title: args.title, tasks, mode: "text" }
|
|
39612
|
+
};
|
|
39613
|
+
}
|
|
39614
|
+
function sendChecklistToolText(r) {
|
|
39615
|
+
return JSON.stringify(r.mode === "native" ? { ok: true, message_id: r.message_id, mode: "native" } : {
|
|
39616
|
+
ok: true,
|
|
39617
|
+
message_id: r.message_id,
|
|
39618
|
+
mode: "text",
|
|
39619
|
+
degraded: "text",
|
|
39620
|
+
note: "rendered as a formatted text message (native Telegram checklists require a Business connection) \u2014 tasks are not tappable; use update_checklist with task ids 1..N to tick them"
|
|
39621
|
+
});
|
|
39622
|
+
}
|
|
39623
|
+
async function performUpdateChecklist(deps, patch) {
|
|
39624
|
+
let base = deps.state;
|
|
39625
|
+
if (!base) {
|
|
39626
|
+
const fullReplacement = patch.title != null && (patch.tasks?.length ?? 0) > 0 && (patch.tasks ?? []).every((t) => t.text != null);
|
|
39627
|
+
if (!fullReplacement) {
|
|
39628
|
+
return {
|
|
39629
|
+
ok: false,
|
|
39630
|
+
reason: "unknown_checklist",
|
|
39631
|
+
hint: "no stored state for this checklist (gateway restarted?). Re-send it with send_checklist, or pass a full replacement (title + every task's text) to update_checklist."
|
|
39632
|
+
};
|
|
39633
|
+
}
|
|
39634
|
+
base = { title: patch.title, tasks: [], mode: "text" };
|
|
39635
|
+
}
|
|
39636
|
+
let next;
|
|
39637
|
+
try {
|
|
39638
|
+
next = applyChecklistPatch(base, patch);
|
|
39639
|
+
} catch (err) {
|
|
39640
|
+
return { ok: false, reason: "edit_failed", hint: String(err) };
|
|
39641
|
+
}
|
|
39642
|
+
const state = { title: next.title, tasks: next.tasks, mode: base.mode };
|
|
39643
|
+
if (base.mode === "native") {
|
|
39644
|
+
if (!deps.nativeAvailable || !deps.businessConnectionId) {
|
|
39645
|
+
return {
|
|
39646
|
+
ok: false,
|
|
39647
|
+
reason: "edit_failed",
|
|
39648
|
+
hint: "this checklist was sent natively but the business connection / checklist API is no longer available."
|
|
39649
|
+
};
|
|
39650
|
+
}
|
|
39651
|
+
try {
|
|
39652
|
+
await deps.editNative(buildNativeEditChecklistPayload({
|
|
39653
|
+
businessConnectionId: deps.businessConnectionId,
|
|
39654
|
+
chatId: deps.chatId,
|
|
39655
|
+
messageId: deps.messageId,
|
|
39656
|
+
title: state.title,
|
|
39657
|
+
tasks: state.tasks
|
|
39658
|
+
}));
|
|
39659
|
+
return { ok: true, mode: "native", state };
|
|
39660
|
+
} catch (err) {
|
|
39661
|
+
deps.log(`native editMessageChecklist failed: ${err}
|
|
39662
|
+
`);
|
|
39663
|
+
return { ok: false, reason: "edit_failed", hint: String(err) };
|
|
39664
|
+
}
|
|
39665
|
+
}
|
|
39666
|
+
try {
|
|
39667
|
+
await deps.editText(renderChecklistText(state, { literal: deps.literalText }));
|
|
39668
|
+
return { ok: true, mode: "text", state };
|
|
39669
|
+
} catch (err) {
|
|
39670
|
+
deps.log(`checklist text edit failed: ${err}
|
|
39671
|
+
`);
|
|
39672
|
+
return { ok: false, reason: "edit_failed", hint: String(err) };
|
|
39673
|
+
}
|
|
39674
|
+
}
|
|
39675
|
+
function updateChecklistToolText(r, messageId) {
|
|
39676
|
+
return JSON.stringify(r.ok ? { ok: true, message_id: messageId, mode: r.mode, ...r.mode === "text" ? { degraded: "text" } : {} } : { ok: false, reason: r.reason, hint: r.hint });
|
|
39677
|
+
}
|
|
39678
|
+
|
|
39507
39679
|
// interrupt-marker.ts
|
|
39508
39680
|
function parseInterruptMarker(text) {
|
|
39509
39681
|
const trimmed = text.trimStart();
|
|
@@ -91772,6 +91944,18 @@ function computeTurnStatus(turn) {
|
|
|
91772
91944
|
return turn.finalAnswerDelivered ? "complete" : "no_reply";
|
|
91773
91945
|
}
|
|
91774
91946
|
}
|
|
91947
|
+
function computeTurnRoute(turn) {
|
|
91948
|
+
switch (turn.deliveryOutcome) {
|
|
91949
|
+
case "failed":
|
|
91950
|
+
return "none";
|
|
91951
|
+
case "delivered":
|
|
91952
|
+
return "flush";
|
|
91953
|
+
case "suppressed":
|
|
91954
|
+
return "reply";
|
|
91955
|
+
default:
|
|
91956
|
+
return turn.finalAnswerDelivered ? turn.replyCalled ? "reply" : "stream" : "none";
|
|
91957
|
+
}
|
|
91958
|
+
}
|
|
91775
91959
|
function buildTurnRecord(turn, endedAt) {
|
|
91776
91960
|
return {
|
|
91777
91961
|
ts: Math.floor(endedAt / 1000),
|
|
@@ -91780,6 +91964,11 @@ function buildTurnRecord(turn, endedAt) {
|
|
|
91780
91964
|
tools: turn.toolCallCount ?? 0,
|
|
91781
91965
|
status: computeTurnStatus(turn),
|
|
91782
91966
|
turn_id: turn.turnId,
|
|
91967
|
+
route: computeTurnRoute({
|
|
91968
|
+
finalAnswerDelivered: turn.finalAnswerDelivered,
|
|
91969
|
+
replyCalled: turn.replyCalled ?? false,
|
|
91970
|
+
deliveryOutcome: turn.deliveryOutcome
|
|
91971
|
+
}),
|
|
91783
91972
|
...turn.landedUnconfirmed != null && turn.landedUnconfirmed > 0 ? { landed_unconfirmed: turn.landedUnconfirmed } : {}
|
|
91784
91973
|
};
|
|
91785
91974
|
}
|
|
@@ -99917,10 +100106,10 @@ function startOutboxSweep(deps) {
|
|
|
99917
100106
|
}
|
|
99918
100107
|
|
|
99919
100108
|
// ../src/build-info.ts
|
|
99920
|
-
var VERSION2 = "0.19.
|
|
99921
|
-
var COMMIT_SHA = "
|
|
99922
|
-
var COMMIT_DATE = "2026-07-
|
|
99923
|
-
var LATEST_PR =
|
|
100109
|
+
var VERSION2 = "0.19.41";
|
|
100110
|
+
var COMMIT_SHA = "e77ee9c0";
|
|
100111
|
+
var COMMIT_DATE = "2026-07-31T09:46:10Z";
|
|
100112
|
+
var LATEST_PR = 4066;
|
|
99924
100113
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
99925
100114
|
|
|
99926
100115
|
// gateway/boot-version.ts
|
|
@@ -101975,40 +102164,21 @@ var GRAMMY_VERSION = (() => {
|
|
|
101975
102164
|
var _rawSendChecklist;
|
|
101976
102165
|
var _rawEditMessageChecklist;
|
|
101977
102166
|
var CHECKLIST_API_AVAILABLE = false;
|
|
101978
|
-
async function rawSendChecklist(
|
|
101979
|
-
if (!CHECKLIST_API_AVAILABLE)
|
|
102167
|
+
async function rawSendChecklist(payload) {
|
|
102168
|
+
if (!CHECKLIST_API_AVAILABLE)
|
|
101980
102169
|
throw new Error("sendChecklist is not available in this grammY/Telegram Bot API version");
|
|
101981
|
-
|
|
101982
|
-
const MAX_TASKS = 30;
|
|
101983
|
-
if (args.tasks.length > MAX_TASKS) {
|
|
101984
|
-
throw new Error(`checklist exceeds ${MAX_TASKS}-task limit (got ${args.tasks.length})`);
|
|
101985
|
-
}
|
|
101986
|
-
const result = await _rawSendChecklist({
|
|
101987
|
-
chat_id: Number(args.chat_id),
|
|
101988
|
-
title: args.title,
|
|
101989
|
-
tasks: args.tasks.map((t) => ({ text: t.text, ...t.done != null ? { is_completed: t.done } : {} })),
|
|
101990
|
-
...args.message_thread_id != null ? { message_thread_id: args.message_thread_id } : {},
|
|
101991
|
-
...args.reply_to_message_id != null ? { reply_to_message_id: args.reply_to_message_id } : {},
|
|
101992
|
-
...args.protect_content === true ? { protect_content: true } : {}
|
|
101993
|
-
});
|
|
102170
|
+
const result = await _rawSendChecklist(payload);
|
|
101994
102171
|
return { message_id: result.message_id };
|
|
101995
102172
|
}
|
|
101996
|
-
async function rawEditMessageChecklist(
|
|
101997
|
-
if (!CHECKLIST_API_AVAILABLE)
|
|
102173
|
+
async function rawEditMessageChecklist(payload) {
|
|
102174
|
+
if (!CHECKLIST_API_AVAILABLE)
|
|
101998
102175
|
throw new Error("editMessageChecklist is not available in this grammY/Telegram Bot API version");
|
|
101999
|
-
|
|
102000
|
-
|
|
102001
|
-
|
|
102002
|
-
|
|
102003
|
-
|
|
102004
|
-
|
|
102005
|
-
tasks: args.tasks.map((t) => ({
|
|
102006
|
-
...t.id != null ? { id: Number(t.id) } : {},
|
|
102007
|
-
...t.text != null ? { text: t.text } : {},
|
|
102008
|
-
...t.done != null ? { is_completed: t.done } : {}
|
|
102009
|
-
}))
|
|
102010
|
-
} : {}
|
|
102011
|
-
});
|
|
102176
|
+
await _rawEditMessageChecklist(payload);
|
|
102177
|
+
}
|
|
102178
|
+
var checklistStore = createChecklistStore();
|
|
102179
|
+
function resolveBusinessConnectionId() {
|
|
102180
|
+
const v = process.env.SWITCHROOM_TELEGRAM_BUSINESS_CONNECTION_ID?.trim();
|
|
102181
|
+
return v ? v : undefined;
|
|
102012
102182
|
}
|
|
102013
102183
|
var chatLock = createChatLock();
|
|
102014
102184
|
var lockedBot;
|
|
@@ -103399,6 +103569,7 @@ function emitTurnRecord(turn, endedAt) {
|
|
|
103399
103569
|
toolCallCount: turn.toolCallCount ?? 0,
|
|
103400
103570
|
turnId: turn.turnId,
|
|
103401
103571
|
finalAnswerDelivered: turn.finalAnswerDelivered,
|
|
103572
|
+
replyCalled: turn.replyCalled,
|
|
103402
103573
|
deliveryOutcome: turn.deliveryOutcome,
|
|
103403
103574
|
landedUnconfirmed: turn.landedUnconfirmed
|
|
103404
103575
|
}, endedAt)) + `
|
|
@@ -106723,17 +106894,24 @@ async function executeSendChecklist(args) {
|
|
|
106723
106894
|
const protectContent = args.protect_content === true;
|
|
106724
106895
|
assertAllowedChat(chat_id);
|
|
106725
106896
|
const { title: redactedTitle, tasks: redactedTasks } = redactChecklistFields(title, tasks, (t) => redactOutboundText(t, "send_checklist"));
|
|
106726
|
-
const
|
|
106727
|
-
|
|
106728
|
-
title: redactedTitle,
|
|
106729
|
-
tasks: redactedTasks,
|
|
106897
|
+
const literal = (loadAccess().parseMode ?? "html") === "text";
|
|
106898
|
+
const sendOpts = {
|
|
106730
106899
|
...threadId != null ? { message_thread_id: threadId } : {},
|
|
106731
|
-
...replyTo != null ? {
|
|
106900
|
+
...replyTo != null ? { reply_parameters: { message_id: replyTo } } : {},
|
|
106732
106901
|
...protectContent ? { protect_content: true } : {}
|
|
106733
|
-
}
|
|
106734
|
-
|
|
106902
|
+
};
|
|
106903
|
+
const result = await performSendChecklist({
|
|
106904
|
+
businessConnectionId: resolveBusinessConnectionId(),
|
|
106905
|
+
nativeAvailable: CHECKLIST_API_AVAILABLE,
|
|
106906
|
+
sendNative: rawSendChecklist,
|
|
106907
|
+
sendText: (text5) => robustApiCall(() => literal ? lockedBot.api.sendMessage(chat_id, text5, sendOpts) : lockedBot.api.sendRichMessage(chat_id, richMessage2(text5), sendOpts), { verb: "sendMessage", chat_id, threadId }),
|
|
106908
|
+
literalText: literal,
|
|
106909
|
+
log: (l) => process.stderr.write(`telegram gateway: ${l}`)
|
|
106910
|
+
}, { title: redactedTitle, tasks: redactedTasks, chatId: Number(chat_id), replyToMessageId: replyTo, protectContent });
|
|
106911
|
+
checklistStore.set(checklistStoreKey(chat_id, result.message_id), result.state);
|
|
106912
|
+
process.stderr.write(`telegram gateway: send_checklist: sent chatId=${chat_id} messageId=${result.message_id} tasks=${tasks.length} mode=${result.mode}
|
|
106735
106913
|
`);
|
|
106736
|
-
return { content: [{ type: "text", text:
|
|
106914
|
+
return { content: [{ type: "text", text: sendChecklistToolText(result) }] };
|
|
106737
106915
|
}
|
|
106738
106916
|
var linearAuthAlertLast = new Map;
|
|
106739
106917
|
var LINEAR_AUTH_ALERT_COOLDOWN_MS = 21600000;
|
|
@@ -106785,10 +106963,26 @@ async function executeUpdateChecklist(args) {
|
|
|
106785
106963
|
const tasks = args.tasks;
|
|
106786
106964
|
assertAllowedChat(chat_id);
|
|
106787
106965
|
const { title: redactedTitle, tasks: redactedTasks } = redactChecklistFields(title, tasks, (t) => redactOutboundText(t, "update_checklist"));
|
|
106788
|
-
|
|
106789
|
-
|
|
106790
|
-
|
|
106791
|
-
|
|
106966
|
+
const literal = (loadAccess().parseMode ?? "html") === "text";
|
|
106967
|
+
const key = checklistStoreKey(chat_id, message_id);
|
|
106968
|
+
const result = await performUpdateChecklist({
|
|
106969
|
+
state: checklistStore.get(key),
|
|
106970
|
+
businessConnectionId: resolveBusinessConnectionId(),
|
|
106971
|
+
nativeAvailable: CHECKLIST_API_AVAILABLE,
|
|
106972
|
+
editNative: rawEditMessageChecklist,
|
|
106973
|
+
editText: async (text5) => {
|
|
106974
|
+
await robustApiCall(() => lockedBot.api.editMessageText(chat_id, Number(message_id), literal ? text5 : richMessage2(text5)));
|
|
106975
|
+
},
|
|
106976
|
+
literalText: literal,
|
|
106977
|
+
log: (l) => process.stderr.write(`telegram gateway: ${l}`),
|
|
106978
|
+
chatId: Number(chat_id),
|
|
106979
|
+
messageId: Number(message_id)
|
|
106980
|
+
}, { title: redactedTitle, tasks: redactedTasks });
|
|
106981
|
+
if (result.ok)
|
|
106982
|
+
checklistStore.set(key, result.state);
|
|
106983
|
+
process.stderr.write(`telegram gateway: update_checklist: ${result.ok ? `updated mode=${result.mode}` : `failed reason=${result.reason}`} chatId=${chat_id} messageId=${message_id}
|
|
106984
|
+
`);
|
|
106985
|
+
return { content: [{ type: "text", text: updateChecklistToolText(result, Number(message_id)) }] };
|
|
106792
106986
|
}
|
|
106793
106987
|
function redactOutboundText(text5, site) {
|
|
106794
106988
|
const masked = redact2(text5);
|
|
@@ -25007,7 +25007,7 @@ var init_bridge = __esm(async () => {
|
|
|
25007
25007
|
},
|
|
25008
25008
|
{
|
|
25009
25009
|
name: "send_checklist",
|
|
25010
|
-
description: 'Send a
|
|
25010
|
+
description: 'Send a checklist (task list) to a chat. Native interactive Telegram checklists require a Business-account connection, which most deployments do not have \u2014 in that normal case the checklist is sent as a formatted TEXT message (bold title + \u2705/\u2b1c task lines) and the result carries degraded:"text": tasks are NOT tappable, and you tick them yourself via update_checklist (task ids are 1..N in send order). With a Business connection configured, the checklist is sent natively and tick events arrive as channel events with kind="checklist_task_changed". Returns JSON with message_id and mode. Limit: 30 tasks.',
|
|
25011
25011
|
inputSchema: {
|
|
25012
25012
|
type: "object",
|
|
25013
25013
|
properties: {
|
|
@@ -25092,7 +25092,7 @@ var init_bridge = __esm(async () => {
|
|
|
25092
25092
|
},
|
|
25093
25093
|
{
|
|
25094
25094
|
name: "update_checklist",
|
|
25095
|
-
description:
|
|
25095
|
+
description: `Patch a checklist previously sent with send_checklist: update the title, append tasks, or mark tasks done/undone. Tasks with an id target existing items (ids are 1..N in send order for text-mode checklists); tasks without an id are appended. Removal is not supported. The edit re-renders in the mode the checklist was sent in (text for most deployments). Returns JSON { ok, ... }; after a gateway restart the stored task state may be gone \u2014 then it returns ok:false with reason "unknown_checklist" unless you pass a full replacement (title + every task's text).`,
|
|
25096
25096
|
inputSchema: {
|
|
25097
25097
|
type: "object",
|
|
25098
25098
|
properties: {
|