switchroom 0.14.19 → 0.14.21
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 +6 -1
- package/dist/auth-broker/index.js +6 -1
- package/dist/cli/notion-write-pretool.mjs +6 -1
- package/dist/cli/switchroom.js +17 -3
- package/dist/host-control/main.js +6 -1
- package/dist/vault/approvals/kernel-server.js +6 -1
- package/dist/vault/broker/server.js +6 -1
- package/package.json +2 -2
- package/telegram-plugin/README.md +7 -3
- package/telegram-plugin/bridge/bridge.ts +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +368 -153
- package/telegram-plugin/dist/server.js +1 -1
- package/telegram-plugin/gateway/coalesce-attachments.ts +79 -0
- package/telegram-plugin/gateway/gateway.ts +257 -39
- package/telegram-plugin/gateway/interrupt-defer.ts +106 -0
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +21 -4
- package/telegram-plugin/tests/coalesce-attachments.test.ts +170 -0
- package/telegram-plugin/tests/interrupt-defer.test.ts +160 -0
- package/telegram-plugin/tests/pending-inbound-buffer.test.ts +36 -0
- package/telegram-plugin/tests/permission-verdict-resume-guard.test.ts +86 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +127 -0
- package/telegram-plugin/uat/assertions.ts +53 -0
- package/telegram-plugin/uat/driver.ts +28 -0
- package/telegram-plugin/uat/feed-matcher.test.ts +80 -0
- package/telegram-plugin/uat/fixtures/album/blue.jpg +0 -0
- package/telegram-plugin/uat/fixtures/album/green.jpg +0 -0
- package/telegram-plugin/uat/fixtures/album/red.jpg +0 -0
- package/telegram-plugin/uat/scenarios/jtbd-album-coalescing-dm.test.ts +136 -0
- package/telegram-plugin/uat/scenarios/jtbd-forwarded-burst-dm.test.ts +158 -0
- package/telegram-plugin/uat/scenarios/jtbd-memory-survives-restart-dm.test.ts +17 -2
- package/telegram-plugin/worker-activity-feed.ts +65 -9
|
@@ -23738,8 +23738,13 @@ var init_schema = __esm(() => {
|
|
|
23738
23738
|
author_name: exports_external.string().optional().describe("Telegraph article byline. Defaults to soul.name when set.")
|
|
23739
23739
|
}).optional().describe("Long-reply publishing via Telegraph (#579). When enabled, replies " + "above the threshold publish as a Telegraph article rendered in " + "Telegram via native Instant View. Off by default \u2014 content " + "residency is real for some personas (lawyer, health-coach with PHI). " + "Cascades from defaults.channels.telegram.telegraph. " + "(Migrated from per-agent root in #596.)"),
|
|
23740
23740
|
coalesce: exports_external.object({
|
|
23741
|
-
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged).")
|
|
23741
|
+
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged)."),
|
|
23742
|
+
max_attachments: exports_external.number().int().positive().optional().describe("Maximum number of media attachments carried into ONE coalesced " + "Claude turn. Default 10 \u2014 a full Telegram album (media_group caps " + "at 10) or a text+multi-image forwarded burst arrives as a single " + "turn; the agent sees numbered attachment fields (image_path, " + "image_path_2, \u2026). Set 1 to restore the historical " + "single-attachment-per-turn behaviour. Excess attachments beyond " + "the cap spill into the next turn. Each attachment is downloaded, " + "so a high cap on a slow link delays turn start.")
|
|
23742
23743
|
}).optional().describe("Inbound coalescing \u2014 how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
23744
|
+
interrupt: exports_external.object({
|
|
23745
|
+
safe_boundary: exports_external.boolean().optional().describe("When true (the default), a `!`-prefix interrupt that arrives while " + "the agent is mid-tool-call is DEFERRED: the SIGINT and the " + "replacement turn wait until the in-flight tool call finishes (a " + "clean boundary) instead of C-c'ing the agent mid-write/mid-bash. If " + "no tool is in flight the interrupt still fires immediately. Bounded " + "by max_wait_ms so a long tool never strands the user. Set false to " + "fire synchronously the moment `!` is received (historical " + "behaviour). Rapid repeated `!` while one is pending coalesce into a " + "single deferred interrupt carrying the latest body."),
|
|
23746
|
+
max_wait_ms: exports_external.number().int().positive().optional().describe("Upper bound (ms) the gateway waits for a safe boundary before firing " + "a deferred `!` interrupt anyway. Only consulted when safe_boundary is " + "true. Default 8000. Keep it short \u2014 the user explicitly asked to " + "interrupt, so a long in-flight tool shouldn't ghost them; the cap " + "trades a tiny risk of a mid-tool C-c for a guaranteed response.")
|
|
23747
|
+
}).optional().describe("Interrupt timing \u2014 how a `!`-prefix interrupt behaves when it lands " + "mid-tool-call. Off by default (fire immediately). Cascades from " + "defaults.channels.telegram.interrupt."),
|
|
23743
23748
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default \u2014 webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 \u2014 see #577.)"),
|
|
23744
23749
|
webhook_dispatch: exports_external.object({
|
|
23745
23750
|
github: exports_external.array(exports_external.object({
|
|
@@ -30154,6 +30159,136 @@ var init_materialize_bot_token = __esm(() => {
|
|
|
30154
30159
|
};
|
|
30155
30160
|
});
|
|
30156
30161
|
|
|
30162
|
+
// ../src/agents/tmux.ts
|
|
30163
|
+
var exports_tmux = {};
|
|
30164
|
+
__export(exports_tmux, {
|
|
30165
|
+
sendAgentInterrupt: () => sendAgentInterrupt,
|
|
30166
|
+
captureAgentPane: () => captureAgentPane
|
|
30167
|
+
});
|
|
30168
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
30169
|
+
import { mkdirSync as mkdirSync25, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync14, writeFileSync as writeFileSync24 } from "node:fs";
|
|
30170
|
+
import { resolve as resolve7 } from "node:path";
|
|
30171
|
+
function captureAgentPane(opts) {
|
|
30172
|
+
const { agentName: agentName3, agentDir, reason } = opts;
|
|
30173
|
+
const scrollback = opts.scrollback !== false;
|
|
30174
|
+
const retain = typeof opts.retain === "number" && opts.retain > 0 ? opts.retain : 20;
|
|
30175
|
+
const socket = `switchroom-${agentName3}`;
|
|
30176
|
+
const outDir = resolve7(agentDir, "crash-reports");
|
|
30177
|
+
const ts = isoStamp(new Date);
|
|
30178
|
+
const reasonSlug = sanitizeReason(reason);
|
|
30179
|
+
const outPath = resolve7(outDir, `${ts}-${reasonSlug}.txt`);
|
|
30180
|
+
try {
|
|
30181
|
+
mkdirSync25(outDir, { recursive: true, mode: 493 });
|
|
30182
|
+
} catch (err) {
|
|
30183
|
+
const msg = `mkdir crash-reports failed: ${err.message}`;
|
|
30184
|
+
console.error(`[tmux-capture] ${agentName3}: ${msg}`);
|
|
30185
|
+
return { error: msg };
|
|
30186
|
+
}
|
|
30187
|
+
const args = ["-L", socket, "capture-pane", "-p"];
|
|
30188
|
+
if (scrollback) {
|
|
30189
|
+
args.push("-S", "-");
|
|
30190
|
+
}
|
|
30191
|
+
args.push("-t", agentName3);
|
|
30192
|
+
let pane;
|
|
30193
|
+
try {
|
|
30194
|
+
pane = execFileSync4("tmux", args, {
|
|
30195
|
+
timeout: 5000,
|
|
30196
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
30197
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
30198
|
+
});
|
|
30199
|
+
} catch (err) {
|
|
30200
|
+
const msg = `tmux capture-pane failed: ${err.message}`;
|
|
30201
|
+
console.error(`[tmux-capture] ${agentName3}: ${msg}`);
|
|
30202
|
+
return { error: msg };
|
|
30203
|
+
}
|
|
30204
|
+
let body = pane;
|
|
30205
|
+
if (body.byteLength > MAX_BYTES) {
|
|
30206
|
+
body = body.subarray(body.byteLength - MAX_BYTES);
|
|
30207
|
+
}
|
|
30208
|
+
const header = `# agent: ${agentName3}
|
|
30209
|
+
` + `# reason: ${reason}
|
|
30210
|
+
` + `# captured-at: ${ts}
|
|
30211
|
+
` + `# tmux-socket: ${socket}
|
|
30212
|
+
` + `
|
|
30213
|
+
`;
|
|
30214
|
+
try {
|
|
30215
|
+
writeFileSync24(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
|
|
30216
|
+
mode: 420
|
|
30217
|
+
});
|
|
30218
|
+
} catch (err) {
|
|
30219
|
+
const msg = `write crash-report failed: ${err.message}`;
|
|
30220
|
+
console.error(`[tmux-capture] ${agentName3}: ${msg}`);
|
|
30221
|
+
return { error: msg };
|
|
30222
|
+
}
|
|
30223
|
+
try {
|
|
30224
|
+
pruneOldReports(outDir, retain);
|
|
30225
|
+
} catch (err) {
|
|
30226
|
+
console.error(`[tmux-capture] ${agentName3}: retention prune failed: ${err.message}`);
|
|
30227
|
+
}
|
|
30228
|
+
return { path: outPath };
|
|
30229
|
+
}
|
|
30230
|
+
function isoStamp(d) {
|
|
30231
|
+
return d.toISOString().replace(/\.\d+Z$/, "Z").replace(/:/g, "-");
|
|
30232
|
+
}
|
|
30233
|
+
function sanitizeReason(reason) {
|
|
30234
|
+
const slug = reason.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
30235
|
+
return slug || "unknown";
|
|
30236
|
+
}
|
|
30237
|
+
function sendAgentInterrupt(opts) {
|
|
30238
|
+
const { agentName: agentName3 } = opts;
|
|
30239
|
+
const attempts = typeof opts.attempts === "number" && opts.attempts > 0 ? opts.attempts : 1;
|
|
30240
|
+
const retryDelayMs = typeof opts.retryDelayMs === "number" && opts.retryDelayMs >= 0 ? opts.retryDelayMs : 100;
|
|
30241
|
+
const socket = `switchroom-${agentName3}`;
|
|
30242
|
+
const args = ["-L", socket, "send-keys", "-t", agentName3, "C-c"];
|
|
30243
|
+
let lastError = null;
|
|
30244
|
+
for (let i = 0;i < attempts; i++) {
|
|
30245
|
+
try {
|
|
30246
|
+
execFileSync4("tmux", args, {
|
|
30247
|
+
timeout: 3000,
|
|
30248
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
30249
|
+
});
|
|
30250
|
+
return { ok: true };
|
|
30251
|
+
} catch (err) {
|
|
30252
|
+
lastError = `tmux send-keys C-c failed: ${err.message}`;
|
|
30253
|
+
console.error(`[tmux-interrupt] ${agentName3}: ${lastError}`);
|
|
30254
|
+
}
|
|
30255
|
+
if (i < attempts - 1 && retryDelayMs > 0) {
|
|
30256
|
+
sleepSync2(retryDelayMs);
|
|
30257
|
+
}
|
|
30258
|
+
}
|
|
30259
|
+
return { error: lastError ?? "tmux send-keys C-c failed" };
|
|
30260
|
+
}
|
|
30261
|
+
function sleepSync2(ms) {
|
|
30262
|
+
const sab = new SharedArrayBuffer(4);
|
|
30263
|
+
const view = new Int32Array(sab);
|
|
30264
|
+
Atomics.wait(view, 0, 0, ms);
|
|
30265
|
+
}
|
|
30266
|
+
function pruneOldReports(dir, retain) {
|
|
30267
|
+
let entries;
|
|
30268
|
+
try {
|
|
30269
|
+
entries = readdirSync6(dir);
|
|
30270
|
+
} catch {
|
|
30271
|
+
return;
|
|
30272
|
+
}
|
|
30273
|
+
const files = entries.filter((n) => n.endsWith(".txt")).map((n) => {
|
|
30274
|
+
const full = resolve7(dir, n);
|
|
30275
|
+
let mtimeMs = 0;
|
|
30276
|
+
try {
|
|
30277
|
+
mtimeMs = statSync12(full).mtimeMs;
|
|
30278
|
+
} catch {}
|
|
30279
|
+
return { full, mtimeMs };
|
|
30280
|
+
}).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
30281
|
+
for (const stale of files.slice(retain)) {
|
|
30282
|
+
try {
|
|
30283
|
+
unlinkSync14(stale.full);
|
|
30284
|
+
} catch {}
|
|
30285
|
+
}
|
|
30286
|
+
}
|
|
30287
|
+
var MAX_BYTES;
|
|
30288
|
+
var init_tmux = __esm(() => {
|
|
30289
|
+
MAX_BYTES = 10 * 1024 * 1024;
|
|
30290
|
+
});
|
|
30291
|
+
|
|
30157
30292
|
// gateway/config-approval-handler.ts
|
|
30158
30293
|
var exports_config_approval_handler = {};
|
|
30159
30294
|
__export(exports_config_approval_handler, {
|
|
@@ -30372,136 +30507,6 @@ var init_config_approval_handler = __esm(() => {
|
|
|
30372
30507
|
pending = new Map;
|
|
30373
30508
|
});
|
|
30374
30509
|
|
|
30375
|
-
// ../src/agents/tmux.ts
|
|
30376
|
-
var exports_tmux = {};
|
|
30377
|
-
__export(exports_tmux, {
|
|
30378
|
-
sendAgentInterrupt: () => sendAgentInterrupt,
|
|
30379
|
-
captureAgentPane: () => captureAgentPane
|
|
30380
|
-
});
|
|
30381
|
-
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
30382
|
-
import { mkdirSync as mkdirSync25, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync14, writeFileSync as writeFileSync24 } from "node:fs";
|
|
30383
|
-
import { resolve as resolve7 } from "node:path";
|
|
30384
|
-
function captureAgentPane(opts) {
|
|
30385
|
-
const { agentName: agentName3, agentDir, reason } = opts;
|
|
30386
|
-
const scrollback = opts.scrollback !== false;
|
|
30387
|
-
const retain = typeof opts.retain === "number" && opts.retain > 0 ? opts.retain : 20;
|
|
30388
|
-
const socket = `switchroom-${agentName3}`;
|
|
30389
|
-
const outDir = resolve7(agentDir, "crash-reports");
|
|
30390
|
-
const ts = isoStamp(new Date);
|
|
30391
|
-
const reasonSlug = sanitizeReason(reason);
|
|
30392
|
-
const outPath = resolve7(outDir, `${ts}-${reasonSlug}.txt`);
|
|
30393
|
-
try {
|
|
30394
|
-
mkdirSync25(outDir, { recursive: true, mode: 493 });
|
|
30395
|
-
} catch (err) {
|
|
30396
|
-
const msg = `mkdir crash-reports failed: ${err.message}`;
|
|
30397
|
-
console.error(`[tmux-capture] ${agentName3}: ${msg}`);
|
|
30398
|
-
return { error: msg };
|
|
30399
|
-
}
|
|
30400
|
-
const args = ["-L", socket, "capture-pane", "-p"];
|
|
30401
|
-
if (scrollback) {
|
|
30402
|
-
args.push("-S", "-");
|
|
30403
|
-
}
|
|
30404
|
-
args.push("-t", agentName3);
|
|
30405
|
-
let pane;
|
|
30406
|
-
try {
|
|
30407
|
-
pane = execFileSync4("tmux", args, {
|
|
30408
|
-
timeout: 5000,
|
|
30409
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
30410
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
30411
|
-
});
|
|
30412
|
-
} catch (err) {
|
|
30413
|
-
const msg = `tmux capture-pane failed: ${err.message}`;
|
|
30414
|
-
console.error(`[tmux-capture] ${agentName3}: ${msg}`);
|
|
30415
|
-
return { error: msg };
|
|
30416
|
-
}
|
|
30417
|
-
let body = pane;
|
|
30418
|
-
if (body.byteLength > MAX_BYTES) {
|
|
30419
|
-
body = body.subarray(body.byteLength - MAX_BYTES);
|
|
30420
|
-
}
|
|
30421
|
-
const header = `# agent: ${agentName3}
|
|
30422
|
-
` + `# reason: ${reason}
|
|
30423
|
-
` + `# captured-at: ${ts}
|
|
30424
|
-
` + `# tmux-socket: ${socket}
|
|
30425
|
-
` + `
|
|
30426
|
-
`;
|
|
30427
|
-
try {
|
|
30428
|
-
writeFileSync24(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
|
|
30429
|
-
mode: 420
|
|
30430
|
-
});
|
|
30431
|
-
} catch (err) {
|
|
30432
|
-
const msg = `write crash-report failed: ${err.message}`;
|
|
30433
|
-
console.error(`[tmux-capture] ${agentName3}: ${msg}`);
|
|
30434
|
-
return { error: msg };
|
|
30435
|
-
}
|
|
30436
|
-
try {
|
|
30437
|
-
pruneOldReports(outDir, retain);
|
|
30438
|
-
} catch (err) {
|
|
30439
|
-
console.error(`[tmux-capture] ${agentName3}: retention prune failed: ${err.message}`);
|
|
30440
|
-
}
|
|
30441
|
-
return { path: outPath };
|
|
30442
|
-
}
|
|
30443
|
-
function isoStamp(d) {
|
|
30444
|
-
return d.toISOString().replace(/\.\d+Z$/, "Z").replace(/:/g, "-");
|
|
30445
|
-
}
|
|
30446
|
-
function sanitizeReason(reason) {
|
|
30447
|
-
const slug = reason.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
30448
|
-
return slug || "unknown";
|
|
30449
|
-
}
|
|
30450
|
-
function sendAgentInterrupt(opts) {
|
|
30451
|
-
const { agentName: agentName3 } = opts;
|
|
30452
|
-
const attempts = typeof opts.attempts === "number" && opts.attempts > 0 ? opts.attempts : 1;
|
|
30453
|
-
const retryDelayMs = typeof opts.retryDelayMs === "number" && opts.retryDelayMs >= 0 ? opts.retryDelayMs : 100;
|
|
30454
|
-
const socket = `switchroom-${agentName3}`;
|
|
30455
|
-
const args = ["-L", socket, "send-keys", "-t", agentName3, "C-c"];
|
|
30456
|
-
let lastError = null;
|
|
30457
|
-
for (let i = 0;i < attempts; i++) {
|
|
30458
|
-
try {
|
|
30459
|
-
execFileSync4("tmux", args, {
|
|
30460
|
-
timeout: 3000,
|
|
30461
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
30462
|
-
});
|
|
30463
|
-
return { ok: true };
|
|
30464
|
-
} catch (err) {
|
|
30465
|
-
lastError = `tmux send-keys C-c failed: ${err.message}`;
|
|
30466
|
-
console.error(`[tmux-interrupt] ${agentName3}: ${lastError}`);
|
|
30467
|
-
}
|
|
30468
|
-
if (i < attempts - 1 && retryDelayMs > 0) {
|
|
30469
|
-
sleepSync2(retryDelayMs);
|
|
30470
|
-
}
|
|
30471
|
-
}
|
|
30472
|
-
return { error: lastError ?? "tmux send-keys C-c failed" };
|
|
30473
|
-
}
|
|
30474
|
-
function sleepSync2(ms) {
|
|
30475
|
-
const sab = new SharedArrayBuffer(4);
|
|
30476
|
-
const view = new Int32Array(sab);
|
|
30477
|
-
Atomics.wait(view, 0, 0, ms);
|
|
30478
|
-
}
|
|
30479
|
-
function pruneOldReports(dir, retain) {
|
|
30480
|
-
let entries;
|
|
30481
|
-
try {
|
|
30482
|
-
entries = readdirSync6(dir);
|
|
30483
|
-
} catch {
|
|
30484
|
-
return;
|
|
30485
|
-
}
|
|
30486
|
-
const files = entries.filter((n) => n.endsWith(".txt")).map((n) => {
|
|
30487
|
-
const full = resolve7(dir, n);
|
|
30488
|
-
let mtimeMs = 0;
|
|
30489
|
-
try {
|
|
30490
|
-
mtimeMs = statSync12(full).mtimeMs;
|
|
30491
|
-
} catch {}
|
|
30492
|
-
return { full, mtimeMs };
|
|
30493
|
-
}).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
30494
|
-
for (const stale of files.slice(retain)) {
|
|
30495
|
-
try {
|
|
30496
|
-
unlinkSync14(stale.full);
|
|
30497
|
-
} catch {}
|
|
30498
|
-
}
|
|
30499
|
-
}
|
|
30500
|
-
var MAX_BYTES;
|
|
30501
|
-
var init_tmux = __esm(() => {
|
|
30502
|
-
MAX_BYTES = 10 * 1024 * 1024;
|
|
30503
|
-
});
|
|
30504
|
-
|
|
30505
30510
|
// ../src/vault/approvals/client.ts
|
|
30506
30511
|
function resolveKernelSocketPath2(opts) {
|
|
30507
30512
|
if (opts?.socket)
|
|
@@ -31025,6 +31030,54 @@ function parseInterruptMarker(text) {
|
|
|
31025
31030
|
};
|
|
31026
31031
|
}
|
|
31027
31032
|
|
|
31033
|
+
// gateway/interrupt-defer.ts
|
|
31034
|
+
class ToolFlightTracker {
|
|
31035
|
+
inFlight = new Set;
|
|
31036
|
+
onEvent(ev) {
|
|
31037
|
+
switch (ev.kind) {
|
|
31038
|
+
case "tool_use":
|
|
31039
|
+
if (typeof ev.toolUseId === "string" && ev.toolUseId.length > 0) {
|
|
31040
|
+
this.inFlight.add(ev.toolUseId);
|
|
31041
|
+
}
|
|
31042
|
+
break;
|
|
31043
|
+
case "tool_result":
|
|
31044
|
+
if (typeof ev.toolUseId === "string" && ev.toolUseId.length > 0) {
|
|
31045
|
+
this.inFlight.delete(ev.toolUseId);
|
|
31046
|
+
}
|
|
31047
|
+
break;
|
|
31048
|
+
case "turn_end":
|
|
31049
|
+
case "enqueue":
|
|
31050
|
+
this.inFlight.clear();
|
|
31051
|
+
break;
|
|
31052
|
+
default:
|
|
31053
|
+
break;
|
|
31054
|
+
}
|
|
31055
|
+
}
|
|
31056
|
+
isMidToolCall() {
|
|
31057
|
+
return this.inFlight.size > 0;
|
|
31058
|
+
}
|
|
31059
|
+
inFlightCount() {
|
|
31060
|
+
return this.inFlight.size;
|
|
31061
|
+
}
|
|
31062
|
+
clear() {
|
|
31063
|
+
this.inFlight.clear();
|
|
31064
|
+
}
|
|
31065
|
+
}
|
|
31066
|
+
function decideInterruptTiming(opts) {
|
|
31067
|
+
if (!opts.safeBoundaryEnabled)
|
|
31068
|
+
return "fire-now";
|
|
31069
|
+
return opts.midToolCall ? "defer" : "fire-now";
|
|
31070
|
+
}
|
|
31071
|
+
var DEFAULT_INTERRUPT_MAX_WAIT_MS = 8000;
|
|
31072
|
+
function resolveInterruptMaxWaitMs(configured) {
|
|
31073
|
+
if (typeof configured === "number" && configured > 0)
|
|
31074
|
+
return configured;
|
|
31075
|
+
return DEFAULT_INTERRUPT_MAX_WAIT_MS;
|
|
31076
|
+
}
|
|
31077
|
+
function resolveSafeBoundaryEnabled(configured) {
|
|
31078
|
+
return configured !== false;
|
|
31079
|
+
}
|
|
31080
|
+
|
|
31028
31081
|
// sticker-aliases.ts
|
|
31029
31082
|
function looksLikeFileId(s) {
|
|
31030
31083
|
return /^[A-Za-z0-9_-]{10,200}$/.test(s);
|
|
@@ -31562,6 +31615,37 @@ function inboundCoalesceKey(chatId, threadId, userId) {
|
|
|
31562
31615
|
return `${chatId}:${t}:${userId}`;
|
|
31563
31616
|
}
|
|
31564
31617
|
|
|
31618
|
+
// gateway/coalesce-attachments.ts
|
|
31619
|
+
var DEFAULT_MAX_ATTACHMENTS = 10;
|
|
31620
|
+
function resolveCoalesceMaxAttachments(configured) {
|
|
31621
|
+
return Math.max(1, configured ?? DEFAULT_MAX_ATTACHMENTS);
|
|
31622
|
+
}
|
|
31623
|
+
function splitCoalescedAttachments(entries, hasAttachment, maxAttachments) {
|
|
31624
|
+
const withAttachment = entries.filter(hasAttachment);
|
|
31625
|
+
const capped = withAttachment.slice(0, Math.max(1, maxAttachments));
|
|
31626
|
+
const [primary, ...extras] = capped;
|
|
31627
|
+
return { primary, extras };
|
|
31628
|
+
}
|
|
31629
|
+
function buildExtraAttachmentMeta(resolved) {
|
|
31630
|
+
const out = {};
|
|
31631
|
+
resolved.forEach((ex, i) => {
|
|
31632
|
+
const n = i + 2;
|
|
31633
|
+
if (ex.imagePath)
|
|
31634
|
+
out[`image_path_${n}`] = ex.imagePath;
|
|
31635
|
+
if (ex.attachment) {
|
|
31636
|
+
out[`attachment_kind_${n}`] = ex.attachment.kind;
|
|
31637
|
+
out[`attachment_file_id_${n}`] = ex.attachment.file_id;
|
|
31638
|
+
if (ex.attachment.size != null)
|
|
31639
|
+
out[`attachment_size_${n}`] = String(ex.attachment.size);
|
|
31640
|
+
if (ex.attachment.mime)
|
|
31641
|
+
out[`attachment_mime_${n}`] = ex.attachment.mime;
|
|
31642
|
+
if (ex.attachment.name)
|
|
31643
|
+
out[`attachment_name_${n}`] = ex.attachment.name;
|
|
31644
|
+
}
|
|
31645
|
+
});
|
|
31646
|
+
return out;
|
|
31647
|
+
}
|
|
31648
|
+
|
|
31565
31649
|
// status-reactions.ts
|
|
31566
31650
|
var TELEGRAM_REACTION_WHITELIST = new Set([
|
|
31567
31651
|
"\uD83D\uDC4D",
|
|
@@ -31879,9 +31963,13 @@ class DeferredDoneReactions {
|
|
|
31879
31963
|
}
|
|
31880
31964
|
|
|
31881
31965
|
// worker-activity-feed.ts
|
|
31966
|
+
function isWorkerActivityFeedEnabled(envVal) {
|
|
31967
|
+
return envVal !== "0";
|
|
31968
|
+
}
|
|
31882
31969
|
var DESC_MAX = 80;
|
|
31883
31970
|
var TOOL_ARG_MAX = 64;
|
|
31884
31971
|
var SUMMARY_MAX = 100;
|
|
31972
|
+
var NARRATIVE_MAX_LINES = 6;
|
|
31885
31973
|
function renderWorkerActivity(v) {
|
|
31886
31974
|
const desc = truncate(v.description.trim() || "background task", DESC_MAX);
|
|
31887
31975
|
const elapsed = formatDuration(v.elapsedMs);
|
|
@@ -31900,10 +31988,17 @@ function renderWorkerActivity(v) {
|
|
|
31900
31988
|
} else {
|
|
31901
31989
|
activity = `<i>starting\u2026 (${elapsed})</i>`;
|
|
31902
31990
|
}
|
|
31903
|
-
const summary = v.latestSummary.trim();
|
|
31904
31991
|
const lines = [header, activity];
|
|
31905
|
-
|
|
31906
|
-
|
|
31992
|
+
const narrative = (v.narrativeLines ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
31993
|
+
if (narrative.length > 0) {
|
|
31994
|
+
for (const line of narrative) {
|
|
31995
|
+
lines.push(` \u21b3 <i>${escapeHtml(truncate(line, SUMMARY_MAX))}</i>`);
|
|
31996
|
+
}
|
|
31997
|
+
} else {
|
|
31998
|
+
const summary = v.latestSummary.trim();
|
|
31999
|
+
if (summary.length > 0) {
|
|
32000
|
+
lines.push(` \u21b3 <i>${escapeHtml(truncate(summary, SUMMARY_MAX))}</i>`);
|
|
32001
|
+
}
|
|
31907
32002
|
}
|
|
31908
32003
|
return lines.join(`
|
|
31909
32004
|
`);
|
|
@@ -31940,10 +32035,22 @@ function createWorkerActivityFeed(opts) {
|
|
|
31940
32035
|
h.cooldownUntil = nowFn() + retryAfter * 1000 + COOLDOWN_JITTER_MS;
|
|
31941
32036
|
log(`worker-feed: ${label} 429 \u2014 backing off ${retryAfter}s`);
|
|
31942
32037
|
}
|
|
32038
|
+
function accumulateNarrative(h, view) {
|
|
32039
|
+
const line = view.latestSummary.trim();
|
|
32040
|
+
if (line.length === 0)
|
|
32041
|
+
return;
|
|
32042
|
+
if (h.narrative[h.narrative.length - 1] === line)
|
|
32043
|
+
return;
|
|
32044
|
+
h.narrative.push(line);
|
|
32045
|
+
if (h.narrative.length > NARRATIVE_MAX_LINES) {
|
|
32046
|
+
h.narrative.splice(0, h.narrative.length - NARRATIVE_MAX_LINES);
|
|
32047
|
+
}
|
|
32048
|
+
}
|
|
31943
32049
|
async function doUpdate(h, view) {
|
|
32050
|
+
accumulateNarrative(h, view);
|
|
31944
32051
|
if (nowFn() < h.cooldownUntil)
|
|
31945
32052
|
return;
|
|
31946
|
-
const body = renderWorkerActivity(view);
|
|
32053
|
+
const body = renderWorkerActivity({ ...view, narrativeLines: h.narrative });
|
|
31947
32054
|
if (h.messageId == null) {
|
|
31948
32055
|
if (view.elapsedMs < firstPaintMin)
|
|
31949
32056
|
return;
|
|
@@ -32010,6 +32117,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
32010
32117
|
lastBody: null,
|
|
32011
32118
|
lastEditAt: 0,
|
|
32012
32119
|
cooldownUntil: 0,
|
|
32120
|
+
narrative: [],
|
|
32013
32121
|
chain: Promise.resolve()
|
|
32014
32122
|
};
|
|
32015
32123
|
handles.set(agentId, h);
|
|
@@ -46399,6 +46507,7 @@ function planBufferedRedelivery(pending) {
|
|
|
46399
46507
|
flush();
|
|
46400
46508
|
return out;
|
|
46401
46509
|
}
|
|
46510
|
+
var ATTACHMENT_META_RE = /^(image_path|attachment_)/;
|
|
46402
46511
|
function mergeRun(run2) {
|
|
46403
46512
|
const last = run2[run2.length - 1];
|
|
46404
46513
|
const mediaEntry = run2.find(inboundHasMedia);
|
|
@@ -46409,6 +46518,14 @@ function mergeRun(run2) {
|
|
|
46409
46518
|
};
|
|
46410
46519
|
delete merged.imagePath;
|
|
46411
46520
|
delete merged.attachment;
|
|
46521
|
+
if (mediaEntry != null && mediaEntry !== last) {
|
|
46522
|
+
const splicedMeta = { ...merged.meta };
|
|
46523
|
+
for (const [k, v] of Object.entries(mediaEntry.meta)) {
|
|
46524
|
+
if (ATTACHMENT_META_RE.test(k))
|
|
46525
|
+
splicedMeta[k] = v;
|
|
46526
|
+
}
|
|
46527
|
+
merged.meta = splicedMeta;
|
|
46528
|
+
}
|
|
46412
46529
|
if (mediaEntry?.imagePath != null)
|
|
46413
46530
|
merged.imagePath = mediaEntry.imagePath;
|
|
46414
46531
|
if (mediaEntry?.attachment != null)
|
|
@@ -47037,6 +47154,7 @@ function planBufferedRedelivery2(pending) {
|
|
|
47037
47154
|
flush();
|
|
47038
47155
|
return out;
|
|
47039
47156
|
}
|
|
47157
|
+
var ATTACHMENT_META_RE2 = /^(image_path|attachment_)/;
|
|
47040
47158
|
function mergeRun2(run2) {
|
|
47041
47159
|
const last = run2[run2.length - 1];
|
|
47042
47160
|
const mediaEntry = run2.find(inboundHasMedia2);
|
|
@@ -47047,6 +47165,14 @@ function mergeRun2(run2) {
|
|
|
47047
47165
|
};
|
|
47048
47166
|
delete merged.imagePath;
|
|
47049
47167
|
delete merged.attachment;
|
|
47168
|
+
if (mediaEntry != null && mediaEntry !== last) {
|
|
47169
|
+
const splicedMeta = { ...merged.meta };
|
|
47170
|
+
for (const [k, v] of Object.entries(mediaEntry.meta)) {
|
|
47171
|
+
if (ATTACHMENT_META_RE2.test(k))
|
|
47172
|
+
splicedMeta[k] = v;
|
|
47173
|
+
}
|
|
47174
|
+
merged.meta = splicedMeta;
|
|
47175
|
+
}
|
|
47050
47176
|
if (mediaEntry?.imagePath != null)
|
|
47051
47177
|
merged.imagePath = mediaEntry.imagePath;
|
|
47052
47178
|
if (mediaEntry?.attachment != null)
|
|
@@ -51244,10 +51370,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
51244
51370
|
}
|
|
51245
51371
|
|
|
51246
51372
|
// ../src/build-info.ts
|
|
51247
|
-
var VERSION = "0.14.
|
|
51248
|
-
var COMMIT_SHA = "
|
|
51249
|
-
var COMMIT_DATE = "2026-05-
|
|
51250
|
-
var LATEST_PR =
|
|
51373
|
+
var VERSION = "0.14.21";
|
|
51374
|
+
var COMMIT_SHA = "62ddded0";
|
|
51375
|
+
var COMMIT_DATE = "2026-05-31T02:54:29Z";
|
|
51376
|
+
var LATEST_PR = 2024;
|
|
51251
51377
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
51252
51378
|
|
|
51253
51379
|
// gateway/boot-version.ts
|
|
@@ -52015,6 +52141,9 @@ function readAccessFile() {
|
|
|
52015
52141
|
parseMode: parsed.parseMode,
|
|
52016
52142
|
disableLinkPreview: parsed.disableLinkPreview,
|
|
52017
52143
|
coalescingGapMs: parsed.coalescingGapMs,
|
|
52144
|
+
coalesceMaxAttachments: parsed.coalesceMaxAttachments,
|
|
52145
|
+
interruptSafeBoundary: parsed.interruptSafeBoundary,
|
|
52146
|
+
interruptMaxWaitMs: parsed.interruptMaxWaitMs,
|
|
52018
52147
|
statusReactions: parsed.statusReactions,
|
|
52019
52148
|
historyEnabled: parsed.historyEnabled,
|
|
52020
52149
|
historyRetentionDays: parsed.historyRetentionDays,
|
|
@@ -52222,6 +52351,40 @@ var lastPtyPreviewByChat = new Map;
|
|
|
52222
52351
|
var progressUpdateLastSent = new Map;
|
|
52223
52352
|
var progressUpdateTurnCount = new Map;
|
|
52224
52353
|
var currentTurn = null;
|
|
52354
|
+
var toolFlightTracker = new ToolFlightTracker;
|
|
52355
|
+
var pendingDeferredInterrupt = null;
|
|
52356
|
+
async function fireDeferredInterrupt(reason) {
|
|
52357
|
+
const pending2 = pendingDeferredInterrupt;
|
|
52358
|
+
if (pending2 == null)
|
|
52359
|
+
return;
|
|
52360
|
+
pendingDeferredInterrupt = null;
|
|
52361
|
+
clearTimeout(pending2.deadlineTimer);
|
|
52362
|
+
const waitedMs = Date.now() - pending2.registeredAt;
|
|
52363
|
+
process.stderr.write(`telegram gateway: deferred-interrupt firing reason=${reason} agent=${pending2.agentName} chat=${pending2.chatId} waited_ms=${waitedMs} in_flight=${toolFlightTracker.inFlightCount()}
|
|
52364
|
+
`);
|
|
52365
|
+
try {
|
|
52366
|
+
const { sendAgentInterrupt: sendAgentInterrupt2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
52367
|
+
const r = sendAgentInterrupt2({ agentName: pending2.agentName });
|
|
52368
|
+
if ("ok" in r) {
|
|
52369
|
+
process.stderr.write(`telegram gateway: deferred-interrupt SIGINT delivered via tmux send-keys agent=${pending2.agentName}
|
|
52370
|
+
`);
|
|
52371
|
+
} else {
|
|
52372
|
+
process.stderr.write(`telegram gateway: deferred-interrupt SIGINT via tmux failed agent=${pending2.agentName}: ${r.error}
|
|
52373
|
+
`);
|
|
52374
|
+
}
|
|
52375
|
+
} catch (err) {
|
|
52376
|
+
process.stderr.write(`telegram gateway: deferred-interrupt SIGINT failed: ${err.message}
|
|
52377
|
+
`);
|
|
52378
|
+
}
|
|
52379
|
+
const delivered = ipcServer.sendToAgent(pending2.agentName, pending2.inboundMsg);
|
|
52380
|
+
if (delivered) {
|
|
52381
|
+
markClaudeBusyForInbound(pending2.inboundMsg);
|
|
52382
|
+
} else {
|
|
52383
|
+
pendingInboundBuffer.push(pending2.agentName, pending2.inboundMsg);
|
|
52384
|
+
process.stderr.write(`telegram gateway: deferred-interrupt body buffered (bridge miss) agent=${pending2.agentName} chat=${pending2.chatId}
|
|
52385
|
+
`);
|
|
52386
|
+
}
|
|
52387
|
+
}
|
|
52225
52388
|
var preambleSuppressor = new PreambleSuppressor({
|
|
52226
52389
|
emitAnswer: (cumulative) => {
|
|
52227
52390
|
const stream = currentTurn?.answerStream ?? null;
|
|
@@ -52963,23 +53126,27 @@ function looksLikeAuthCode(text) {
|
|
|
52963
53126
|
return true;
|
|
52964
53127
|
return false;
|
|
52965
53128
|
}
|
|
52966
|
-
var bufferedAttachmentKeys = new
|
|
53129
|
+
var bufferedAttachmentKeys = new Map;
|
|
53130
|
+
function coalesceMaxAttachments() {
|
|
53131
|
+
return resolveCoalesceMaxAttachments(loadAccess().coalesceMaxAttachments);
|
|
53132
|
+
}
|
|
52967
53133
|
var inboundCoalescer = createInboundCoalescer({
|
|
52968
53134
|
gapMs: () => loadAccess().coalescingGapMs ?? 500,
|
|
52969
53135
|
merge: (entries) => {
|
|
52970
53136
|
const last = entries[entries.length - 1];
|
|
52971
|
-
const
|
|
53137
|
+
const { primary, extras } = splitCoalescedAttachments(entries, (e) => e.downloadImage != null || e.attachment != null, coalesceMaxAttachments());
|
|
52972
53138
|
return {
|
|
52973
|
-
text: entries.map((e) => e.text).join(`
|
|
53139
|
+
text: entries.map((e) => e.text).filter((t) => t.length > 0).join(`
|
|
52974
53140
|
`),
|
|
52975
53141
|
ctx: last.ctx,
|
|
52976
|
-
downloadImage:
|
|
52977
|
-
attachment:
|
|
53142
|
+
downloadImage: primary?.downloadImage,
|
|
53143
|
+
attachment: primary?.attachment,
|
|
53144
|
+
extraAttachments: extras.length > 0 ? extras.map((e) => ({ downloadImage: e.downloadImage, attachment: e.attachment })) : undefined
|
|
52978
53145
|
};
|
|
52979
53146
|
},
|
|
52980
53147
|
onFlush: (key, merged) => {
|
|
52981
53148
|
bufferedAttachmentKeys.delete(key);
|
|
52982
|
-
handleInbound(merged.ctx, merged.text, merged.downloadImage, merged.attachment);
|
|
53149
|
+
handleInbound(merged.ctx, merged.text, merged.downloadImage, merged.attachment, merged.extraAttachments);
|
|
52983
53150
|
}
|
|
52984
53151
|
});
|
|
52985
53152
|
function emitGatewayOperatorEvent(event) {
|
|
@@ -53498,6 +53665,10 @@ var ipcServer = createIpcServer({
|
|
|
53498
53665
|
const threadHint = msg.threadId != null ? String(msg.threadId) : undefined;
|
|
53499
53666
|
progressDriver?.ingest(ev, chatHint, threadHint);
|
|
53500
53667
|
handleSessionEvent(ev);
|
|
53668
|
+
toolFlightTracker.onEvent(ev);
|
|
53669
|
+
if (pendingDeferredInterrupt != null && !toolFlightTracker.isMidToolCall()) {
|
|
53670
|
+
fireDeferredInterrupt("boundary");
|
|
53671
|
+
}
|
|
53501
53672
|
if (currentTurn != null) {
|
|
53502
53673
|
const key = statusKey(currentTurn.sessionChatId, currentTurn.sessionThreadId);
|
|
53503
53674
|
if (ev.kind === "thinking") {
|
|
@@ -56141,7 +56312,8 @@ async function handleInboundCoalesced(ctx, text, downloadImage, attachment) {
|
|
|
56141
56312
|
return handleInbound(ctx, text, downloadImage, attachment);
|
|
56142
56313
|
}
|
|
56143
56314
|
const hasAttachment = downloadImage != null || attachment != null;
|
|
56144
|
-
|
|
56315
|
+
const maxAttachments = coalesceMaxAttachments();
|
|
56316
|
+
if (hasAttachment && ctx.message?.media_group_id != null && maxAttachments <= 1) {
|
|
56145
56317
|
return handleInbound(ctx, text, downloadImage, attachment);
|
|
56146
56318
|
}
|
|
56147
56319
|
const from = ctx.from;
|
|
@@ -56149,7 +56321,7 @@ async function handleInboundCoalesced(ctx, text, downloadImage, attachment) {
|
|
|
56149
56321
|
return;
|
|
56150
56322
|
if (hasAttachment) {
|
|
56151
56323
|
const probeKey = inboundCoalesceKey(String(ctx.chat.id), ctx.message?.message_thread_id, String(from.id));
|
|
56152
|
-
if (bufferedAttachmentKeys.
|
|
56324
|
+
if ((bufferedAttachmentKeys.get(probeKey) ?? 0) >= maxAttachments) {
|
|
56153
56325
|
return handleInbound(ctx, text, downloadImage, attachment);
|
|
56154
56326
|
}
|
|
56155
56327
|
}
|
|
@@ -56159,7 +56331,7 @@ async function handleInboundCoalesced(ctx, text, downloadImage, attachment) {
|
|
|
56159
56331
|
if (result.bypass)
|
|
56160
56332
|
return handleInbound(ctx, text, downloadImage, attachment);
|
|
56161
56333
|
if (hasAttachment)
|
|
56162
|
-
bufferedAttachmentKeys.
|
|
56334
|
+
bufferedAttachmentKeys.set(key, (bufferedAttachmentKeys.get(key) ?? 0) + 1);
|
|
56163
56335
|
}
|
|
56164
56336
|
function maybeEarlyAckReaction(ctx, from) {
|
|
56165
56337
|
const msgId = ctx.message?.message_id;
|
|
@@ -56180,7 +56352,7 @@ function maybeEarlyAckReaction(ctx, from) {
|
|
|
56180
56352
|
]).catch(() => {});
|
|
56181
56353
|
bot.api.sendChatAction(chatId, "typing").catch(() => {});
|
|
56182
56354
|
}
|
|
56183
|
-
async function handleInbound(ctx, text, downloadImage, attachment) {
|
|
56355
|
+
async function handleInbound(ctx, text, downloadImage, attachment, extraAttachments) {
|
|
56184
56356
|
const isTopicMessage = ctx.message?.is_topic_message ?? false;
|
|
56185
56357
|
const messageThreadId = ctx.message?.message_thread_id;
|
|
56186
56358
|
if (TOPIC_ID != null) {
|
|
@@ -56240,16 +56412,22 @@ async function handleInbound(ctx, text, downloadImage, attachment) {
|
|
|
56240
56412
|
`);
|
|
56241
56413
|
}
|
|
56242
56414
|
const interrupt = parseInterruptMarker(text);
|
|
56415
|
+
let deferInterrupt = false;
|
|
56243
56416
|
if (interrupt.isInterrupt) {
|
|
56244
56417
|
const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
|
|
56245
|
-
|
|
56418
|
+
const access2 = loadAccess();
|
|
56419
|
+
deferInterrupt = !interrupt.emptyBody && decideInterruptTiming({
|
|
56420
|
+
safeBoundaryEnabled: resolveSafeBoundaryEnabled(access2.interruptSafeBoundary),
|
|
56421
|
+
midToolCall: toolFlightTracker.isMidToolCall()
|
|
56422
|
+
}) === "defer";
|
|
56423
|
+
process.stderr.write(`telegram gateway: interrupt-marker received chat_id=${chat_id} agent=${agentName3 ?? "-"} body_len=${interrupt.body.length} empty=${interrupt.emptyBody} defer=${deferInterrupt} in_flight=${toolFlightTracker.inFlightCount()}
|
|
56246
56424
|
`);
|
|
56247
56425
|
if (msgId != null) {
|
|
56248
56426
|
bot.api.setMessageReaction(chat_id, msgId, [
|
|
56249
56427
|
{ type: "emoji", emoji: "\u26A1" }
|
|
56250
56428
|
]).catch(() => {});
|
|
56251
56429
|
}
|
|
56252
|
-
if (agentName3) {
|
|
56430
|
+
if (agentName3 && !deferInterrupt) {
|
|
56253
56431
|
try {
|
|
56254
56432
|
const { sendAgentInterrupt: sendAgentInterrupt2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
56255
56433
|
const r = sendAgentInterrupt2({ agentName: agentName3 });
|
|
@@ -56688,6 +56866,16 @@ ${preBlock(write.output)}`;
|
|
|
56688
56866
|
}
|
|
56689
56867
|
}
|
|
56690
56868
|
const imagePath = downloadImage ? await downloadImage() : undefined;
|
|
56869
|
+
const extraResolved = [];
|
|
56870
|
+
if (extraAttachments && extraAttachments.length > 0) {
|
|
56871
|
+
for (const ex of extraAttachments) {
|
|
56872
|
+
const exImagePath = ex.downloadImage ? await ex.downloadImage() : undefined;
|
|
56873
|
+
extraResolved.push({ imagePath: exImagePath, attachment: ex.attachment });
|
|
56874
|
+
}
|
|
56875
|
+
}
|
|
56876
|
+
const extraMeta = buildExtraAttachmentMeta(extraResolved);
|
|
56877
|
+
const primaryHasAttachment = imagePath != null || attachment != null;
|
|
56878
|
+
const attachmentCount = (primaryHasAttachment ? 1 : 0) + extraResolved.length;
|
|
56691
56879
|
const replyToMsg = ctx.message?.reply_to_message;
|
|
56692
56880
|
const replyToMessageId = replyToMsg?.message_id;
|
|
56693
56881
|
const replyToTextRaw = replyToMsg ? replyToMsg.text ?? replyToMsg.caption ?? undefined : undefined;
|
|
@@ -56771,10 +56959,37 @@ ${preBlock(write.output)}`;
|
|
|
56771
56959
|
...attachment.size != null ? { attachment_size: String(attachment.size) } : {},
|
|
56772
56960
|
...attachment.mime ? { attachment_mime: attachment.mime } : {},
|
|
56773
56961
|
...attachment.name ? { attachment_name: attachment.name } : {}
|
|
56774
|
-
} : {}
|
|
56962
|
+
} : {},
|
|
56963
|
+
...attachmentCount > 1 ? { attachment_count: String(attachmentCount) } : {},
|
|
56964
|
+
...extraMeta
|
|
56775
56965
|
}
|
|
56776
56966
|
};
|
|
56777
56967
|
const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
|
|
56968
|
+
if (deferInterrupt) {
|
|
56969
|
+
const selfAgentDefer = process.env.SWITCHROOM_AGENT_NAME ?? "";
|
|
56970
|
+
if (pendingDeferredInterrupt != null) {
|
|
56971
|
+
pendingDeferredInterrupt.inboundMsg = inboundMsg;
|
|
56972
|
+
pendingDeferredInterrupt.msgId = msgId ?? null;
|
|
56973
|
+
process.stderr.write(`telegram gateway: deferred-interrupt coalesced (replacing pending body) agent=${selfAgentDefer} chat=${chat_id} msg=${msgId ?? "-"}
|
|
56974
|
+
`);
|
|
56975
|
+
} else {
|
|
56976
|
+
const maxWaitMs = resolveInterruptMaxWaitMs(loadAccess().interruptMaxWaitMs);
|
|
56977
|
+
pendingDeferredInterrupt = {
|
|
56978
|
+
agentName: selfAgentDefer,
|
|
56979
|
+
inboundMsg,
|
|
56980
|
+
chatId: chat_id,
|
|
56981
|
+
msgId: msgId ?? null,
|
|
56982
|
+
threadId: messageThreadId ?? undefined,
|
|
56983
|
+
registeredAt: Date.now(),
|
|
56984
|
+
deadlineTimer: setTimeout(() => {
|
|
56985
|
+
fireDeferredInterrupt("timeout");
|
|
56986
|
+
}, maxWaitMs)
|
|
56987
|
+
};
|
|
56988
|
+
process.stderr.write(`telegram gateway: deferred-interrupt parked agent=${selfAgentDefer} chat=${chat_id} msg=${msgId ?? "-"} max_wait_ms=${maxWaitMs} in_flight=${toolFlightTracker.inFlightCount()}
|
|
56989
|
+
`);
|
|
56990
|
+
}
|
|
56991
|
+
return;
|
|
56992
|
+
}
|
|
56778
56993
|
if (decideInboundDelivery({
|
|
56779
56994
|
turnInFlight: turnInFlightAtReceipt,
|
|
56780
56995
|
isSteering,
|
|
@@ -61449,7 +61664,7 @@ var didOneTimeSetup = false;
|
|
|
61449
61664
|
if (streamMode === "checklist") {
|
|
61450
61665
|
const watcherAgentDir = resolveAgentDirFromEnv();
|
|
61451
61666
|
if (watcherAgentDir != null) {
|
|
61452
|
-
const workerFeedEnabled = process.env.SWITCHROOM_WORKER_ACTIVITY_FEED
|
|
61667
|
+
const workerFeedEnabled = isWorkerActivityFeedEnabled(process.env.SWITCHROOM_WORKER_ACTIVITY_FEED);
|
|
61453
61668
|
const workerActivityFeed = createWorkerActivityFeed({
|
|
61454
61669
|
bot: {
|
|
61455
61670
|
sendMessage: async (cid, text, sendOpts) => {
|