@integrity-labs/agt-cli 0.27.7-test.7 → 0.27.8-test.10
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/bin/agt.js +146 -37
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-2G35ES7G.js → chunk-2E5OABKX.js} +241 -61
- package/dist/chunk-2E5OABKX.js.map +1 -0
- package/dist/{chunk-YSBGIXJG.js → chunk-6HFXSNNY.js} +31 -18
- package/dist/chunk-6HFXSNNY.js.map +1 -0
- package/dist/{chunk-GN4XPQWJ.js → chunk-JIETOJQV.js} +37 -28
- package/dist/chunk-JIETOJQV.js.map +1 -0
- package/dist/{claude-pair-runtime-ZBQKBBMT.js → claude-pair-runtime-3UHTNKZ4.js} +2 -2
- package/dist/lib/manager-worker.js +536 -140
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +9 -462
- package/dist/mcp/slack-channel.js +691 -638
- package/dist/mcp/telegram-channel.js +257 -513
- package/dist/{persistent-session-ICYFLUAM.js → persistent-session-HSHIZC2Q.js} +3 -3
- package/dist/{responsiveness-probe-WZNQ2762.js → responsiveness-probe-RYKS5TRP.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-2G35ES7G.js.map +0 -1
- package/dist/chunk-GN4XPQWJ.js.map +0 -1
- package/dist/chunk-YSBGIXJG.js.map +0 -1
- /package/dist/{claude-pair-runtime-ZBQKBBMT.js.map → claude-pair-runtime-3UHTNKZ4.js.map} +0 -0
- /package/dist/{persistent-session-ICYFLUAM.js.map → persistent-session-HSHIZC2Q.js.map} +0 -0
- /package/dist/{responsiveness-probe-WZNQ2762.js.map → responsiveness-probe-RYKS5TRP.js.map} +0 -0
|
@@ -14293,13 +14293,13 @@ var StdioServerTransport = class {
|
|
|
14293
14293
|
|
|
14294
14294
|
// src/telegram-channel.ts
|
|
14295
14295
|
import https from "https";
|
|
14296
|
-
import { createHash, randomUUID
|
|
14296
|
+
import { createHash, randomUUID } from "crypto";
|
|
14297
14297
|
import {
|
|
14298
14298
|
createWriteStream,
|
|
14299
14299
|
existsSync as existsSync2,
|
|
14300
14300
|
mkdirSync as mkdirSync3,
|
|
14301
|
-
readFileSync as
|
|
14302
|
-
readdirSync,
|
|
14301
|
+
readFileSync as readFileSync3,
|
|
14302
|
+
readdirSync as readdirSync2,
|
|
14303
14303
|
renameSync as renameSync3,
|
|
14304
14304
|
statSync,
|
|
14305
14305
|
unlinkSync as unlinkSync3,
|
|
@@ -14307,7 +14307,7 @@ import {
|
|
|
14307
14307
|
writeFileSync as writeFileSync3
|
|
14308
14308
|
} from "fs";
|
|
14309
14309
|
import { homedir as homedir2 } from "os";
|
|
14310
|
-
import { join as
|
|
14310
|
+
import { join as join4 } from "path";
|
|
14311
14311
|
|
|
14312
14312
|
// src/channel-attachments.ts
|
|
14313
14313
|
import { homedir } from "os";
|
|
@@ -15386,430 +15386,6 @@ function createInboundContextClient(args) {
|
|
|
15386
15386
|
};
|
|
15387
15387
|
}
|
|
15388
15388
|
|
|
15389
|
-
// src/progress-tools.ts
|
|
15390
|
-
import { randomUUID } from "crypto";
|
|
15391
|
-
|
|
15392
|
-
// src/progress-handle.ts
|
|
15393
|
-
async function startProgressHandle(opts) {
|
|
15394
|
-
const now = opts.now ?? (() => Date.now());
|
|
15395
|
-
const setTimer = opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
15396
|
-
const clearTimer = opts.clearTimer ?? ((id) => clearTimeout(id));
|
|
15397
|
-
const debounceMs = opts.debounceMs ?? 1e3;
|
|
15398
|
-
const onPostTerminalUpdate = opts.onPostTerminalUpdate ?? defaultPostTerminalWarn;
|
|
15399
|
-
const state = {
|
|
15400
|
-
initialLabel: opts.initialLabel,
|
|
15401
|
-
mode: opts.initialMode ?? "working",
|
|
15402
|
-
lastChangeAt: now()
|
|
15403
|
-
};
|
|
15404
|
-
let lastFlushAt = 0;
|
|
15405
|
-
let pendingTimer = null;
|
|
15406
|
-
let inFlight = null;
|
|
15407
|
-
let terminal = false;
|
|
15408
|
-
async function doFlush() {
|
|
15409
|
-
while (inFlight) await inFlight;
|
|
15410
|
-
if (pendingTimer != null) {
|
|
15411
|
-
clearTimer(pendingTimer);
|
|
15412
|
-
pendingTimer = null;
|
|
15413
|
-
}
|
|
15414
|
-
lastFlushAt = now();
|
|
15415
|
-
const promise = opts.flush(snapshotState());
|
|
15416
|
-
inFlight = promise.then(
|
|
15417
|
-
() => {
|
|
15418
|
-
inFlight = null;
|
|
15419
|
-
},
|
|
15420
|
-
(err) => {
|
|
15421
|
-
inFlight = null;
|
|
15422
|
-
throw err;
|
|
15423
|
-
}
|
|
15424
|
-
);
|
|
15425
|
-
await inFlight;
|
|
15426
|
-
}
|
|
15427
|
-
function snapshotState() {
|
|
15428
|
-
return {
|
|
15429
|
-
initialLabel: state.initialLabel,
|
|
15430
|
-
mode: state.mode,
|
|
15431
|
-
stepLabel: state.stepLabel,
|
|
15432
|
-
stepNumber: state.stepNumber ? { ...state.stepNumber } : void 0,
|
|
15433
|
-
detail: state.detail,
|
|
15434
|
-
lastChangeAt: state.lastChangeAt,
|
|
15435
|
-
terminal: state.terminal ? { ...state.terminal } : void 0
|
|
15436
|
-
};
|
|
15437
|
-
}
|
|
15438
|
-
function applyUpdate(next) {
|
|
15439
|
-
if (next.mode !== void 0) state.mode = next.mode;
|
|
15440
|
-
if (next.stepLabel !== void 0) state.stepLabel = next.stepLabel;
|
|
15441
|
-
if (next.stepNumber !== void 0) state.stepNumber = { ...next.stepNumber };
|
|
15442
|
-
if (next.detail !== void 0) state.detail = next.detail;
|
|
15443
|
-
state.lastChangeAt = now();
|
|
15444
|
-
}
|
|
15445
|
-
async function scheduleOrFlush() {
|
|
15446
|
-
const elapsed = now() - lastFlushAt;
|
|
15447
|
-
if (elapsed >= debounceMs) {
|
|
15448
|
-
await doFlush();
|
|
15449
|
-
return;
|
|
15450
|
-
}
|
|
15451
|
-
if (pendingTimer != null) return;
|
|
15452
|
-
const remaining = debounceMs - elapsed;
|
|
15453
|
-
pendingTimer = setTimer(() => {
|
|
15454
|
-
pendingTimer = null;
|
|
15455
|
-
void doFlush().catch(() => {
|
|
15456
|
-
});
|
|
15457
|
-
}, remaining);
|
|
15458
|
-
}
|
|
15459
|
-
lastFlushAt = now();
|
|
15460
|
-
await opts.flush(snapshotState());
|
|
15461
|
-
return {
|
|
15462
|
-
snapshot: snapshotState,
|
|
15463
|
-
async update(next) {
|
|
15464
|
-
if (terminal) {
|
|
15465
|
-
const peeked = snapshotState();
|
|
15466
|
-
Object.assign(peeked, {
|
|
15467
|
-
mode: next.mode ?? peeked.mode,
|
|
15468
|
-
stepLabel: next.stepLabel ?? peeked.stepLabel,
|
|
15469
|
-
stepNumber: next.stepNumber ?? peeked.stepNumber,
|
|
15470
|
-
detail: next.detail ?? peeked.detail
|
|
15471
|
-
});
|
|
15472
|
-
try {
|
|
15473
|
-
onPostTerminalUpdate(peeked);
|
|
15474
|
-
} catch (err) {
|
|
15475
|
-
console.warn(
|
|
15476
|
-
"[progress-handle] onPostTerminalUpdate threw \u2014 swallowed to keep update() non-throwing:",
|
|
15477
|
-
err
|
|
15478
|
-
);
|
|
15479
|
-
}
|
|
15480
|
-
return;
|
|
15481
|
-
}
|
|
15482
|
-
applyUpdate(next);
|
|
15483
|
-
await scheduleOrFlush();
|
|
15484
|
-
},
|
|
15485
|
-
async complete(summary) {
|
|
15486
|
-
if (terminal) return;
|
|
15487
|
-
terminal = true;
|
|
15488
|
-
state.terminal = { kind: "completed", message: summary };
|
|
15489
|
-
state.lastChangeAt = now();
|
|
15490
|
-
await doFlush();
|
|
15491
|
-
},
|
|
15492
|
-
async fail(reason) {
|
|
15493
|
-
if (terminal) return;
|
|
15494
|
-
terminal = true;
|
|
15495
|
-
state.terminal = { kind: "failed", message: reason };
|
|
15496
|
-
state.lastChangeAt = now();
|
|
15497
|
-
await doFlush();
|
|
15498
|
-
},
|
|
15499
|
-
isTerminal: () => terminal
|
|
15500
|
-
};
|
|
15501
|
-
}
|
|
15502
|
-
function defaultPostTerminalWarn(state) {
|
|
15503
|
-
console.warn(
|
|
15504
|
-
`[progress-handle] update() called after terminal transition (${state.terminal?.kind}) \u2014 ignored.`
|
|
15505
|
-
);
|
|
15506
|
-
}
|
|
15507
|
-
|
|
15508
|
-
// src/progress-tools.ts
|
|
15509
|
-
var ProgressToolRegistry = class {
|
|
15510
|
-
constructor(opts) {
|
|
15511
|
-
this.opts = opts;
|
|
15512
|
-
this.toolNames = {
|
|
15513
|
-
start: `${opts.prefix}_progress_start`,
|
|
15514
|
-
update: `${opts.prefix}_progress_update`,
|
|
15515
|
-
complete: `${opts.prefix}_progress_complete`,
|
|
15516
|
-
fail: `${opts.prefix}_progress_fail`
|
|
15517
|
-
};
|
|
15518
|
-
}
|
|
15519
|
-
handles = /* @__PURE__ */ new Map();
|
|
15520
|
-
toolNames;
|
|
15521
|
-
/** Tool definitions to splice into the MCP ListToolsRequest response. */
|
|
15522
|
-
getDefinitions() {
|
|
15523
|
-
const { surfaceDescription, startArgsSchema } = this.opts;
|
|
15524
|
-
const debounceMs = this.opts.debounceMs ?? 1e3;
|
|
15525
|
-
const debounceText = debounceMs === 1e3 ? "per second" : `every ${debounceMs}ms`;
|
|
15526
|
-
return [
|
|
15527
|
-
{
|
|
15528
|
-
name: this.toolNames.start,
|
|
15529
|
-
description: `Post a "still working" anchor to ${surfaceDescription} and return a progress_id. The anchor message updates in place as you call ${this.toolNames.update} \u2014 operators see one message that ticks forward instead of a flood of new ones. Always end with ${this.toolNames.complete} or ${this.toolNames.fail}; otherwise the anchor lingers as "working" until the stale-state sweep flips it. Opt-in \u2014 only call this for tasks that will take more than a few seconds.`,
|
|
15530
|
-
inputSchema: {
|
|
15531
|
-
type: "object",
|
|
15532
|
-
properties: {
|
|
15533
|
-
label: {
|
|
15534
|
-
type: "string",
|
|
15535
|
-
description: 'Short top-line label for the task (e.g. "diagnose vigil"). Stays constant for the lifetime of this progress anchor.'
|
|
15536
|
-
},
|
|
15537
|
-
initial_mode: {
|
|
15538
|
-
type: "string",
|
|
15539
|
-
enum: ["thinking", "working", "waiting"],
|
|
15540
|
-
description: 'Initial mode emoji \u2014 defaults to "working".'
|
|
15541
|
-
},
|
|
15542
|
-
...startArgsSchema.properties
|
|
15543
|
-
},
|
|
15544
|
-
required: ["label", ...startArgsSchema.required]
|
|
15545
|
-
}
|
|
15546
|
-
},
|
|
15547
|
-
{
|
|
15548
|
-
name: this.toolNames.update,
|
|
15549
|
-
description: `Update the progress anchor in place. The state machine debounces calls to one ${surfaceDescription} API call ${debounceText}, so spamming this every step is safe \u2014 only the latest pending state will paint. Any subset of fields can be passed; omitted ones retain their previous value.`,
|
|
15550
|
-
inputSchema: {
|
|
15551
|
-
type: "object",
|
|
15552
|
-
properties: {
|
|
15553
|
-
progress_id: { type: "string", description: "The progress_id returned from start." },
|
|
15554
|
-
step_label: { type: "string", description: 'Short label for the current step (e.g. "Querying CloudWatch logs").' },
|
|
15555
|
-
step_current: { type: "number", description: "1-based step counter \u2014 current." },
|
|
15556
|
-
step_total: { type: "number", description: "Total steps if known. Omit for open-ended progress." },
|
|
15557
|
-
mode: { type: "string", enum: ["thinking", "working", "waiting"] },
|
|
15558
|
-
detail: { type: "string", description: "Free-text detail line under the step label." }
|
|
15559
|
-
},
|
|
15560
|
-
required: ["progress_id"]
|
|
15561
|
-
}
|
|
15562
|
-
},
|
|
15563
|
-
{
|
|
15564
|
-
name: this.toolNames.complete,
|
|
15565
|
-
description: `Mark the progress anchor as \u2705 completed. Flushes any pending debounced state before painting the terminal banner \u2014 the operator never sees a stale "Step N" beside the \u2705. After this, the handle is dead; further updates are no-ops.`,
|
|
15566
|
-
inputSchema: {
|
|
15567
|
-
type: "object",
|
|
15568
|
-
properties: {
|
|
15569
|
-
progress_id: { type: "string" },
|
|
15570
|
-
summary: { type: "string", description: "Optional one-line summary of what was achieved." }
|
|
15571
|
-
},
|
|
15572
|
-
required: ["progress_id"]
|
|
15573
|
-
}
|
|
15574
|
-
},
|
|
15575
|
-
{
|
|
15576
|
-
name: this.toolNames.fail,
|
|
15577
|
-
description: `Mark the progress anchor as \u274C failed. Always include a one-sentence reason \u2014 the operator can't tell *why* from emoji alone. Same flush-then-paint behaviour as complete.`,
|
|
15578
|
-
inputSchema: {
|
|
15579
|
-
type: "object",
|
|
15580
|
-
properties: {
|
|
15581
|
-
progress_id: { type: "string" },
|
|
15582
|
-
reason: { type: "string", description: "One-sentence failure reason." }
|
|
15583
|
-
},
|
|
15584
|
-
required: ["progress_id", "reason"]
|
|
15585
|
-
}
|
|
15586
|
-
}
|
|
15587
|
-
];
|
|
15588
|
-
}
|
|
15589
|
-
/**
|
|
15590
|
-
* Dispatch a CallToolRequest. Returns `undefined` if the tool name
|
|
15591
|
-
* isn't one of ours (so the caller can keep walking its dispatch
|
|
15592
|
-
* chain). Returns a tool result otherwise.
|
|
15593
|
-
*/
|
|
15594
|
-
async handle(name, args) {
|
|
15595
|
-
if (name === this.toolNames.start) return this.handleStart(args);
|
|
15596
|
-
if (name === this.toolNames.update) return this.handleUpdate(args);
|
|
15597
|
-
if (name === this.toolNames.complete) return this.handleComplete(args);
|
|
15598
|
-
if (name === this.toolNames.fail) return this.handleFail(args);
|
|
15599
|
-
return void 0;
|
|
15600
|
-
}
|
|
15601
|
-
/** Live count of in-flight handles. Exposed for tests + diagnostics. */
|
|
15602
|
-
size() {
|
|
15603
|
-
return this.handles.size;
|
|
15604
|
-
}
|
|
15605
|
-
async handleStart(args) {
|
|
15606
|
-
const label = typeof args.label === "string" ? args.label : null;
|
|
15607
|
-
if (!label) return errResult(`${this.toolNames.start}: 'label' must be a non-empty string`);
|
|
15608
|
-
if ("initial_mode" in args && !isMode(args.initial_mode)) {
|
|
15609
|
-
return errResult(
|
|
15610
|
-
`${this.toolNames.start}: 'initial_mode' must be one of 'thinking', 'working', or 'waiting'`
|
|
15611
|
-
);
|
|
15612
|
-
}
|
|
15613
|
-
for (const k of this.opts.startArgsSchema.required) {
|
|
15614
|
-
if (typeof args[k] !== "string" || args[k].length === 0) {
|
|
15615
|
-
return errResult(`${this.toolNames.start}: '${k}' must be a non-empty string`);
|
|
15616
|
-
}
|
|
15617
|
-
}
|
|
15618
|
-
const initialMode = isMode(args.initial_mode) ? args.initial_mode : void 0;
|
|
15619
|
-
const progressId = randomUUID();
|
|
15620
|
-
try {
|
|
15621
|
-
const flush = this.opts.createFlush(args);
|
|
15622
|
-
const handle = await startProgressHandle({
|
|
15623
|
-
initialLabel: label,
|
|
15624
|
-
initialMode,
|
|
15625
|
-
flush,
|
|
15626
|
-
debounceMs: this.opts.debounceMs ?? 1e3
|
|
15627
|
-
});
|
|
15628
|
-
this.handles.set(progressId, handle);
|
|
15629
|
-
return okResult(JSON.stringify({ progress_id: progressId }));
|
|
15630
|
-
} catch (err) {
|
|
15631
|
-
return errResult(`${this.toolNames.start} failed: ${err.message ?? String(err)}`);
|
|
15632
|
-
}
|
|
15633
|
-
}
|
|
15634
|
-
async handleUpdate(args) {
|
|
15635
|
-
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
15636
|
-
if (!id) return errResult(`${this.toolNames.update}: 'progress_id' must be a string`);
|
|
15637
|
-
const handle = this.handles.get(id);
|
|
15638
|
-
if (!handle) return errResult(`${this.toolNames.update}: unknown progress_id (already terminated, or never started)`);
|
|
15639
|
-
if ("mode" in args && !isMode(args.mode)) {
|
|
15640
|
-
return errResult(
|
|
15641
|
-
`${this.toolNames.update}: 'mode' must be one of 'thinking', 'working', or 'waiting'`
|
|
15642
|
-
);
|
|
15643
|
-
}
|
|
15644
|
-
if (typeof args.step_total === "number" && typeof args.step_current !== "number") {
|
|
15645
|
-
return errResult(
|
|
15646
|
-
`${this.toolNames.update}: 'step_total' requires 'step_current'`
|
|
15647
|
-
);
|
|
15648
|
-
}
|
|
15649
|
-
const next = {};
|
|
15650
|
-
if (typeof args.step_label === "string") next.stepLabel = args.step_label;
|
|
15651
|
-
if (typeof args.step_current === "number") {
|
|
15652
|
-
next.stepNumber = {
|
|
15653
|
-
current: args.step_current,
|
|
15654
|
-
...typeof args.step_total === "number" ? { total: args.step_total } : {}
|
|
15655
|
-
};
|
|
15656
|
-
}
|
|
15657
|
-
if (isMode(args.mode)) next.mode = args.mode;
|
|
15658
|
-
if (typeof args.detail === "string") next.detail = args.detail;
|
|
15659
|
-
try {
|
|
15660
|
-
await handle.update(next);
|
|
15661
|
-
return okResult("ok");
|
|
15662
|
-
} catch (err) {
|
|
15663
|
-
return errResult(`${this.toolNames.update} failed: ${err.message ?? String(err)}`);
|
|
15664
|
-
}
|
|
15665
|
-
}
|
|
15666
|
-
async handleComplete(args) {
|
|
15667
|
-
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
15668
|
-
if (!id) return errResult(`${this.toolNames.complete}: 'progress_id' must be a string`);
|
|
15669
|
-
const handle = this.handles.get(id);
|
|
15670
|
-
if (!handle) return errResult(`${this.toolNames.complete}: unknown progress_id`);
|
|
15671
|
-
const summary = typeof args.summary === "string" ? args.summary : void 0;
|
|
15672
|
-
try {
|
|
15673
|
-
await handle.complete(summary);
|
|
15674
|
-
this.handles.delete(id);
|
|
15675
|
-
return okResult("ok");
|
|
15676
|
-
} catch (err) {
|
|
15677
|
-
return errResult(`${this.toolNames.complete} failed: ${err.message ?? String(err)}`);
|
|
15678
|
-
}
|
|
15679
|
-
}
|
|
15680
|
-
async handleFail(args) {
|
|
15681
|
-
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
15682
|
-
if (!id) return errResult(`${this.toolNames.fail}: 'progress_id' must be a string`);
|
|
15683
|
-
const reason = typeof args.reason === "string" ? args.reason : null;
|
|
15684
|
-
if (!reason) return errResult(`${this.toolNames.fail}: 'reason' must be a non-empty string`);
|
|
15685
|
-
const handle = this.handles.get(id);
|
|
15686
|
-
if (!handle) return errResult(`${this.toolNames.fail}: unknown progress_id`);
|
|
15687
|
-
try {
|
|
15688
|
-
await handle.fail(reason);
|
|
15689
|
-
this.handles.delete(id);
|
|
15690
|
-
return okResult("ok");
|
|
15691
|
-
} catch (err) {
|
|
15692
|
-
return errResult(`${this.toolNames.fail} failed: ${err.message ?? String(err)}`);
|
|
15693
|
-
}
|
|
15694
|
-
}
|
|
15695
|
-
};
|
|
15696
|
-
function okResult(text) {
|
|
15697
|
-
return { content: [{ type: "text", text }] };
|
|
15698
|
-
}
|
|
15699
|
-
function errResult(text) {
|
|
15700
|
-
return { content: [{ type: "text", text }], isError: true };
|
|
15701
|
-
}
|
|
15702
|
-
function isMode(value) {
|
|
15703
|
-
return value === "thinking" || value === "working" || value === "waiting";
|
|
15704
|
-
}
|
|
15705
|
-
|
|
15706
|
-
// src/telegram-progress.ts
|
|
15707
|
-
var MODE_EMOJI = {
|
|
15708
|
-
thinking: "\u{1F4AD}",
|
|
15709
|
-
working: "\u{1F6E0}\uFE0F",
|
|
15710
|
-
waiting: "\u23F3"
|
|
15711
|
-
};
|
|
15712
|
-
var TERMINAL_EMOJI = {
|
|
15713
|
-
completed: "\u2705",
|
|
15714
|
-
failed: "\u274C"
|
|
15715
|
-
};
|
|
15716
|
-
function formatWallClock(ms, timeZone) {
|
|
15717
|
-
const fmt = new Intl.DateTimeFormat("en-GB", {
|
|
15718
|
-
hour: "2-digit",
|
|
15719
|
-
minute: "2-digit",
|
|
15720
|
-
second: "2-digit",
|
|
15721
|
-
hour12: false,
|
|
15722
|
-
timeZone,
|
|
15723
|
-
timeZoneName: "short"
|
|
15724
|
-
});
|
|
15725
|
-
const parts = fmt.formatToParts(new Date(ms));
|
|
15726
|
-
const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
|
|
15727
|
-
return `${get("hour")}:${get("minute")}:${get("second")} ${get("timeZoneName")}`;
|
|
15728
|
-
}
|
|
15729
|
-
function escapeHtml(s) {
|
|
15730
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
15731
|
-
}
|
|
15732
|
-
function renderProgressHtml(state) {
|
|
15733
|
-
const lines = [];
|
|
15734
|
-
if (state.terminal) {
|
|
15735
|
-
const banner = state.terminal.kind === "completed" ? "Done" : "Failed";
|
|
15736
|
-
lines.push(`${TERMINAL_EMOJI[state.terminal.kind]} <b>${banner}</b> \u2014 ${escapeHtml(state.initialLabel)}`);
|
|
15737
|
-
if (state.terminal.message) lines.push(escapeHtml(state.terminal.message));
|
|
15738
|
-
const verb = state.terminal.kind === "completed" ? "completed" : "failed";
|
|
15739
|
-
lines.push(`<i>${verb}: ${formatWallClock(state.lastChangeAt)}</i>`);
|
|
15740
|
-
return lines.join("\n");
|
|
15741
|
-
}
|
|
15742
|
-
lines.push(`${MODE_EMOJI[state.mode]} <b>Working on:</b> ${escapeHtml(state.initialLabel)}`);
|
|
15743
|
-
if (state.stepNumber || state.stepLabel) {
|
|
15744
|
-
const parts = [];
|
|
15745
|
-
if (state.stepNumber) {
|
|
15746
|
-
if (state.stepNumber.total != null) {
|
|
15747
|
-
parts.push(`<b>Step ${state.stepNumber.current} of ${state.stepNumber.total}</b>`);
|
|
15748
|
-
} else {
|
|
15749
|
-
parts.push(`<b>Step ${state.stepNumber.current}</b>`);
|
|
15750
|
-
}
|
|
15751
|
-
}
|
|
15752
|
-
if (state.stepLabel) parts.push(escapeHtml(state.stepLabel));
|
|
15753
|
-
lines.push(parts.join(": "));
|
|
15754
|
-
}
|
|
15755
|
-
if (state.detail) lines.push(`<i>${escapeHtml(state.detail)}</i>`);
|
|
15756
|
-
lines.push(`<i>last update: ${formatWallClock(state.lastChangeAt)}</i>`);
|
|
15757
|
-
return lines.join("\n");
|
|
15758
|
-
}
|
|
15759
|
-
var TelegramApiError = class extends Error {
|
|
15760
|
-
constructor(endpoint, apiError) {
|
|
15761
|
-
super(`Telegram ${endpoint} failed: ${apiError}`);
|
|
15762
|
-
this.endpoint = endpoint;
|
|
15763
|
-
this.apiError = apiError;
|
|
15764
|
-
this.name = "TelegramApiError";
|
|
15765
|
-
}
|
|
15766
|
-
};
|
|
15767
|
-
function isMessageNotModified(description) {
|
|
15768
|
-
if (!description) return false;
|
|
15769
|
-
return /message is not modified/i.test(description);
|
|
15770
|
-
}
|
|
15771
|
-
function createTelegramProgressFlush(opts) {
|
|
15772
|
-
let anchorMessageId = null;
|
|
15773
|
-
return async function flush(state) {
|
|
15774
|
-
const text = renderProgressHtml(state);
|
|
15775
|
-
if (anchorMessageId == null) {
|
|
15776
|
-
const body = {
|
|
15777
|
-
chat_id: opts.chatId,
|
|
15778
|
-
text,
|
|
15779
|
-
parse_mode: "HTML",
|
|
15780
|
-
disable_notification: true
|
|
15781
|
-
};
|
|
15782
|
-
if (opts.messageThreadId != null) body.message_thread_id = opts.messageThreadId;
|
|
15783
|
-
if (opts.replyToMessageId != null) body.reply_to_message_id = opts.replyToMessageId;
|
|
15784
|
-
const res = await opts.callImpl("sendMessage", body);
|
|
15785
|
-
if (!res.ok || !res.result?.message_id) {
|
|
15786
|
-
throw new TelegramApiError("sendMessage", res.description ?? "missing message_id");
|
|
15787
|
-
}
|
|
15788
|
-
anchorMessageId = res.result.message_id;
|
|
15789
|
-
opts.onAnchorPosted?.(res.result.message_id);
|
|
15790
|
-
return;
|
|
15791
|
-
}
|
|
15792
|
-
try {
|
|
15793
|
-
const res = await opts.callImpl("editMessageText", {
|
|
15794
|
-
chat_id: opts.chatId,
|
|
15795
|
-
message_id: anchorMessageId,
|
|
15796
|
-
text,
|
|
15797
|
-
parse_mode: "HTML"
|
|
15798
|
-
});
|
|
15799
|
-
if (!res.ok) {
|
|
15800
|
-
if (isMessageNotModified(res.description)) return;
|
|
15801
|
-
throw new TelegramApiError("editMessageText", res.description ?? "unknown");
|
|
15802
|
-
}
|
|
15803
|
-
} catch (err) {
|
|
15804
|
-
if (opts.onApiError && err instanceof TelegramApiError) {
|
|
15805
|
-
await opts.onApiError(err);
|
|
15806
|
-
return;
|
|
15807
|
-
}
|
|
15808
|
-
throw err;
|
|
15809
|
-
}
|
|
15810
|
-
};
|
|
15811
|
-
}
|
|
15812
|
-
|
|
15813
15389
|
// src/impersonation.ts
|
|
15814
15390
|
var ENV_VAR = "AGT_ACT_AS_AGENT_ID";
|
|
15815
15391
|
var OVERRIDE_ENV_VAR = "ENABLE_IMPERSONATION_CHANNELS";
|
|
@@ -15925,13 +15501,118 @@ function readLockHolder(path) {
|
|
|
15925
15501
|
}
|
|
15926
15502
|
}
|
|
15927
15503
|
|
|
15504
|
+
// src/ack-reaction.ts
|
|
15505
|
+
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
15506
|
+
import { join as join3 } from "path";
|
|
15507
|
+
var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
15508
|
+
var ACK_STARTUP_GRACE_MS = 6e4;
|
|
15509
|
+
var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
|
|
15510
|
+
function decideAckReaction(i) {
|
|
15511
|
+
if (!i.hasTarget) return "none";
|
|
15512
|
+
if (!i.integrationReady) return "undeliverable";
|
|
15513
|
+
if (i.tmux === "dead") return "undeliverable";
|
|
15514
|
+
if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
|
|
15515
|
+
const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
|
|
15516
|
+
if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
|
|
15517
|
+
const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
|
|
15518
|
+
const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
|
|
15519
|
+
if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
|
|
15520
|
+
return "ack";
|
|
15521
|
+
}
|
|
15522
|
+
return "undeliverable";
|
|
15523
|
+
}
|
|
15524
|
+
return "ack";
|
|
15525
|
+
}
|
|
15526
|
+
function decideRecoveryHeal(i) {
|
|
15527
|
+
return i.wasUndeliverable && i.hasTarget ? "heal" : "none";
|
|
15528
|
+
}
|
|
15529
|
+
function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
|
|
15530
|
+
if (!dir) return null;
|
|
15531
|
+
let names;
|
|
15532
|
+
try {
|
|
15533
|
+
names = readdirSync(dir);
|
|
15534
|
+
} catch {
|
|
15535
|
+
return null;
|
|
15536
|
+
}
|
|
15537
|
+
let oldest = null;
|
|
15538
|
+
for (const name of names) {
|
|
15539
|
+
if (!name.endsWith(".json")) continue;
|
|
15540
|
+
let receivedAt;
|
|
15541
|
+
try {
|
|
15542
|
+
const raw = JSON.parse(readFileSync2(join3(dir, name), "utf-8"));
|
|
15543
|
+
receivedAt = raw.received_at;
|
|
15544
|
+
} catch {
|
|
15545
|
+
continue;
|
|
15546
|
+
}
|
|
15547
|
+
if (!receivedAt) continue;
|
|
15548
|
+
const t = Date.parse(receivedAt);
|
|
15549
|
+
if (Number.isNaN(t)) continue;
|
|
15550
|
+
const age = now - t;
|
|
15551
|
+
if (age < 0) continue;
|
|
15552
|
+
if (oldest == null || age > oldest) oldest = age;
|
|
15553
|
+
}
|
|
15554
|
+
return oldest;
|
|
15555
|
+
}
|
|
15556
|
+
|
|
15557
|
+
// src/session-probe-runtime.ts
|
|
15558
|
+
import { execFileSync } from "child_process";
|
|
15559
|
+
function agentTmuxSessionName(codeName) {
|
|
15560
|
+
return `agt-${codeName}`;
|
|
15561
|
+
}
|
|
15562
|
+
function escapePgrepRegex(value) {
|
|
15563
|
+
return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
|
|
15564
|
+
}
|
|
15565
|
+
function probeClaudeProcessInTmux(tmuxSession) {
|
|
15566
|
+
const escapedSession = escapePgrepRegex(tmuxSession);
|
|
15567
|
+
const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
|
|
15568
|
+
try {
|
|
15569
|
+
const out = execFileSync("pgrep", ["-f", "--", pattern], {
|
|
15570
|
+
encoding: "utf-8",
|
|
15571
|
+
timeout: 3e3
|
|
15572
|
+
}).trim();
|
|
15573
|
+
return out.length > 0 ? "alive" : "dead";
|
|
15574
|
+
} catch (err) {
|
|
15575
|
+
const e = err;
|
|
15576
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
15577
|
+
return e?.status === 1 ? "dead" : "unknown";
|
|
15578
|
+
}
|
|
15579
|
+
}
|
|
15580
|
+
function probeTmuxSession(tmuxSession) {
|
|
15581
|
+
try {
|
|
15582
|
+
execFileSync("tmux", ["has-session", "-t", tmuxSession], {
|
|
15583
|
+
stdio: "ignore",
|
|
15584
|
+
timeout: 3e3
|
|
15585
|
+
});
|
|
15586
|
+
return "alive";
|
|
15587
|
+
} catch (err) {
|
|
15588
|
+
const e = err;
|
|
15589
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
15590
|
+
return "dead";
|
|
15591
|
+
}
|
|
15592
|
+
}
|
|
15593
|
+
function probeAgentSession(codeName) {
|
|
15594
|
+
const session = agentTmuxSessionName(codeName);
|
|
15595
|
+
const tmux = probeTmuxSession(session);
|
|
15596
|
+
const claude = tmux === "alive" ? probeClaudeProcessInTmux(session) : tmux;
|
|
15597
|
+
return { tmux, claude };
|
|
15598
|
+
}
|
|
15599
|
+
var probeCache = /* @__PURE__ */ new Map();
|
|
15600
|
+
var SESSION_PROBE_TTL_MS = 15e3;
|
|
15601
|
+
function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = Date.now()) {
|
|
15602
|
+
const cached2 = probeCache.get(codeName);
|
|
15603
|
+
if (cached2 && now - cached2.at < ttlMs) return cached2.value;
|
|
15604
|
+
const value = probeAgentSession(codeName);
|
|
15605
|
+
probeCache.set(codeName, { at: now, value });
|
|
15606
|
+
return value;
|
|
15607
|
+
}
|
|
15608
|
+
|
|
15928
15609
|
// src/telegram-channel.ts
|
|
15929
15610
|
function redactId(id) {
|
|
15930
15611
|
return createHash("sha256").update(String(id)).digest("hex").slice(0, 8);
|
|
15931
15612
|
}
|
|
15932
15613
|
var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
|
15933
15614
|
var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
|
|
15934
|
-
var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ?
|
|
15615
|
+
var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
15935
15616
|
var AGT_HOST = process.env.AGT_HOST ?? null;
|
|
15936
15617
|
var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
|
|
15937
15618
|
var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
|
|
@@ -16015,9 +15696,9 @@ if (!BOT_TOKEN) {
|
|
|
16015
15696
|
var stderrLogStream = null;
|
|
16016
15697
|
if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
|
|
16017
15698
|
try {
|
|
16018
|
-
const logDir =
|
|
15699
|
+
const logDir = join4(homedir2(), ".augmented", AGENT_CODE_NAME);
|
|
16019
15700
|
mkdirSync3(logDir, { recursive: true });
|
|
16020
|
-
stderrLogStream = createWriteStream(
|
|
15701
|
+
stderrLogStream = createWriteStream(join4(logDir, "telegram-channel-stderr.log"), {
|
|
16021
15702
|
flags: "a",
|
|
16022
15703
|
mode: 384
|
|
16023
15704
|
});
|
|
@@ -16098,7 +15779,76 @@ async function setMessageReaction(chatId, messageId, emoji2) {
|
|
|
16098
15779
|
);
|
|
16099
15780
|
}
|
|
16100
15781
|
}
|
|
16101
|
-
var
|
|
15782
|
+
var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
|
|
15783
|
+
var lastUndeliverableNoticeAt = /* @__PURE__ */ new Map();
|
|
15784
|
+
async function notifyUndeliverable(chatId, messageId) {
|
|
15785
|
+
const key2 = String(chatId);
|
|
15786
|
+
const now = Date.now();
|
|
15787
|
+
const last = lastUndeliverableNoticeAt.get(key2);
|
|
15788
|
+
if (last != null && now - last < UNDELIVERABLE_NOTICE_THROTTLE_MS) return;
|
|
15789
|
+
lastUndeliverableNoticeAt.set(key2, now);
|
|
15790
|
+
try {
|
|
15791
|
+
const resp = await telegramApiCall(
|
|
15792
|
+
"sendMessage",
|
|
15793
|
+
{
|
|
15794
|
+
chat_id: chatId,
|
|
15795
|
+
text: "\u26A0\uFE0F I can't respond right now \u2014 I'll follow up once I'm back.",
|
|
15796
|
+
reply_to_message_id: Number(messageId),
|
|
15797
|
+
allow_sending_without_reply: true
|
|
15798
|
+
},
|
|
15799
|
+
1e4
|
|
15800
|
+
);
|
|
15801
|
+
if (!resp.ok) {
|
|
15802
|
+
process.stderr.write(
|
|
15803
|
+
`telegram-channel(${AGENT_CODE_NAME}): undeliverable notice failed (chat=${redactId(chatId)}): ${resp.description ?? "unknown"}
|
|
15804
|
+
`
|
|
15805
|
+
);
|
|
15806
|
+
}
|
|
15807
|
+
} catch (err) {
|
|
15808
|
+
process.stderr.write(
|
|
15809
|
+
`telegram-channel(${AGENT_CODE_NAME}): undeliverable notice error: ${err.message}
|
|
15810
|
+
`
|
|
15811
|
+
);
|
|
15812
|
+
}
|
|
15813
|
+
}
|
|
15814
|
+
function __resetUndeliverableNoticeThrottle() {
|
|
15815
|
+
lastUndeliverableNoticeAt.clear();
|
|
15816
|
+
}
|
|
15817
|
+
var lastBackOnlineNoticeAt = /* @__PURE__ */ new Map();
|
|
15818
|
+
function notifyBackOnline(chatId) {
|
|
15819
|
+
const key2 = String(chatId);
|
|
15820
|
+
const now = Date.now();
|
|
15821
|
+
const last = lastBackOnlineNoticeAt.get(key2);
|
|
15822
|
+
if (last != null && now - last < UNDELIVERABLE_NOTICE_THROTTLE_MS) return;
|
|
15823
|
+
lastBackOnlineNoticeAt.set(key2, now);
|
|
15824
|
+
void (async () => {
|
|
15825
|
+
try {
|
|
15826
|
+
const resp = await telegramApiCall(
|
|
15827
|
+
"sendMessage",
|
|
15828
|
+
{
|
|
15829
|
+
chat_id: chatId,
|
|
15830
|
+
text: "\u2705 Back online \u2014 catching up on what I missed."
|
|
15831
|
+
},
|
|
15832
|
+
1e4
|
|
15833
|
+
);
|
|
15834
|
+
if (!resp.ok) {
|
|
15835
|
+
process.stderr.write(
|
|
15836
|
+
`telegram-channel(${AGENT_CODE_NAME}): back-online notice failed (chat=${redactId(chatId)}): ${resp.description ?? "unknown"}
|
|
15837
|
+
`
|
|
15838
|
+
);
|
|
15839
|
+
}
|
|
15840
|
+
} catch (err) {
|
|
15841
|
+
process.stderr.write(
|
|
15842
|
+
`telegram-channel(${AGENT_CODE_NAME}): back-online notice error: ${err.message}
|
|
15843
|
+
`
|
|
15844
|
+
);
|
|
15845
|
+
}
|
|
15846
|
+
})();
|
|
15847
|
+
}
|
|
15848
|
+
function __resetBackOnlineNoticeThrottle() {
|
|
15849
|
+
lastBackOnlineNoticeAt.clear();
|
|
15850
|
+
}
|
|
15851
|
+
var RESTART_FLAGS_DIR = join4(homedir2(), ".augmented", "restart-flags");
|
|
16102
15852
|
function buildTelegramHelpMessage(codeName) {
|
|
16103
15853
|
return [
|
|
16104
15854
|
`\u{1F916} *Available commands for \`${codeName}\`*`,
|
|
@@ -16138,14 +15888,14 @@ async function handleRestartCommand(opts) {
|
|
|
16138
15888
|
if (!existsSync2(RESTART_FLAGS_DIR)) {
|
|
16139
15889
|
mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
|
|
16140
15890
|
}
|
|
16141
|
-
const flagPath =
|
|
15891
|
+
const flagPath = join4(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
|
|
16142
15892
|
const flag = {
|
|
16143
15893
|
codeName: AGENT_CODE_NAME,
|
|
16144
15894
|
source: "telegram",
|
|
16145
15895
|
ts: Date.now(),
|
|
16146
15896
|
reply: { chat_id: opts.chatId, message_id: opts.messageId }
|
|
16147
15897
|
};
|
|
16148
|
-
const tmpPath = `${flagPath}.${process.pid}.${
|
|
15898
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
16149
15899
|
writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
16150
15900
|
renameSync3(tmpPath, flagPath);
|
|
16151
15901
|
process.stderr.write(
|
|
@@ -16254,25 +16004,27 @@ async function classifyRestartCommand(text) {
|
|
|
16254
16004
|
if (!ours) return "verification_failed";
|
|
16255
16005
|
return target === ours ? "act" : "ignore";
|
|
16256
16006
|
}
|
|
16257
|
-
var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ?
|
|
16258
|
-
var PENDING_INBOUND_DIR = AGENT_DIR ?
|
|
16259
|
-
var RECOVERY_OUTBOX_DIR = AGENT_DIR ?
|
|
16007
|
+
var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
16008
|
+
var PENDING_INBOUND_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-pending-inbound") : null;
|
|
16009
|
+
var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-recovery-outbox") : null;
|
|
16260
16010
|
function safeMarkerName(chatId, messageId) {
|
|
16261
16011
|
const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
16262
16012
|
return `${safe(chatId)}__${safe(messageId)}.json`;
|
|
16263
16013
|
}
|
|
16264
16014
|
function pendingInboundPath(chatId, messageId) {
|
|
16265
16015
|
if (!PENDING_INBOUND_DIR) return null;
|
|
16266
|
-
return
|
|
16016
|
+
return join4(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
|
|
16267
16017
|
}
|
|
16268
|
-
function writePendingInboundMarker(chatId, messageId, chatType) {
|
|
16018
|
+
function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false) {
|
|
16269
16019
|
const path = pendingInboundPath(chatId, messageId);
|
|
16270
16020
|
if (!path || !PENDING_INBOUND_DIR) return;
|
|
16271
16021
|
const marker = {
|
|
16272
16022
|
chat_id: chatId,
|
|
16273
16023
|
message_id: messageId,
|
|
16274
16024
|
chat_type: chatType,
|
|
16275
|
-
received_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
16025
|
+
received_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16026
|
+
// Only persist the flag when set — keeps healthy-path markers byte-identical.
|
|
16027
|
+
...undeliverable ? { undeliverable: true } : {}
|
|
16276
16028
|
};
|
|
16277
16029
|
try {
|
|
16278
16030
|
mkdirSync3(PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
@@ -16284,14 +16036,28 @@ function writePendingInboundMarker(chatId, messageId, chatType) {
|
|
|
16284
16036
|
);
|
|
16285
16037
|
}
|
|
16286
16038
|
}
|
|
16287
|
-
function
|
|
16288
|
-
|
|
16289
|
-
|
|
16039
|
+
function clearTelegramMarkerFileWithHeal(fullPath) {
|
|
16040
|
+
let marker = null;
|
|
16041
|
+
try {
|
|
16042
|
+
marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
|
|
16043
|
+
} catch {
|
|
16044
|
+
}
|
|
16045
|
+
if (marker && decideRecoveryHeal({
|
|
16046
|
+
wasUndeliverable: marker.undeliverable === true,
|
|
16047
|
+
hasTarget: Boolean(marker.chat_id)
|
|
16048
|
+
}) === "heal") {
|
|
16049
|
+
notifyBackOnline(marker.chat_id);
|
|
16050
|
+
}
|
|
16290
16051
|
try {
|
|
16291
|
-
if (existsSync2(
|
|
16052
|
+
if (existsSync2(fullPath)) unlinkSync3(fullPath);
|
|
16292
16053
|
} catch {
|
|
16293
16054
|
}
|
|
16294
16055
|
}
|
|
16056
|
+
function clearPendingInboundMarker(chatId, messageId) {
|
|
16057
|
+
const path = pendingInboundPath(chatId, messageId);
|
|
16058
|
+
if (!path) return;
|
|
16059
|
+
clearTelegramMarkerFileWithHeal(path);
|
|
16060
|
+
}
|
|
16295
16061
|
var MAX_RECOVERY_ATTEMPTS = 3;
|
|
16296
16062
|
function nextRetryName(filename) {
|
|
16297
16063
|
const match = filename.match(/^(.*?)(?:\.retry-(\d+))?\.json$/);
|
|
@@ -16307,10 +16073,10 @@ function nextRetryName(filename) {
|
|
|
16307
16073
|
async function processRecoveryOutboxFile(filename) {
|
|
16308
16074
|
if (!RECOVERY_OUTBOX_DIR) return;
|
|
16309
16075
|
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
16310
|
-
const fullPath =
|
|
16076
|
+
const fullPath = join4(RECOVERY_OUTBOX_DIR, filename);
|
|
16311
16077
|
let payload;
|
|
16312
16078
|
try {
|
|
16313
|
-
const raw =
|
|
16079
|
+
const raw = readFileSync3(fullPath, "utf-8");
|
|
16314
16080
|
payload = JSON.parse(raw);
|
|
16315
16081
|
} catch (err) {
|
|
16316
16082
|
process.stderr.write(
|
|
@@ -16373,7 +16139,7 @@ async function processRecoveryOutboxFile(filename) {
|
|
|
16373
16139
|
const next = nextRetryName(filename);
|
|
16374
16140
|
if (next) {
|
|
16375
16141
|
try {
|
|
16376
|
-
renameSync3(fullPath,
|
|
16142
|
+
renameSync3(fullPath, join4(RECOVERY_OUTBOX_DIR, next.next));
|
|
16377
16143
|
if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
|
|
16378
16144
|
process.stderr.write(
|
|
16379
16145
|
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
@@ -16404,7 +16170,7 @@ function scanRecoveryRetries() {
|
|
|
16404
16170
|
if (!RECOVERY_OUTBOX_DIR) return;
|
|
16405
16171
|
let entries;
|
|
16406
16172
|
try {
|
|
16407
|
-
entries =
|
|
16173
|
+
entries = readdirSync2(RECOVERY_OUTBOX_DIR);
|
|
16408
16174
|
} catch {
|
|
16409
16175
|
return;
|
|
16410
16176
|
}
|
|
@@ -16413,7 +16179,7 @@ function scanRecoveryRetries() {
|
|
|
16413
16179
|
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
16414
16180
|
let mtimeMs;
|
|
16415
16181
|
try {
|
|
16416
|
-
mtimeMs = statSync(
|
|
16182
|
+
mtimeMs = statSync(join4(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
16417
16183
|
} catch {
|
|
16418
16184
|
continue;
|
|
16419
16185
|
}
|
|
@@ -16434,7 +16200,7 @@ function startRecoveryOutboxWatcher() {
|
|
|
16434
16200
|
return;
|
|
16435
16201
|
}
|
|
16436
16202
|
try {
|
|
16437
|
-
for (const f of
|
|
16203
|
+
for (const f of readdirSync2(RECOVERY_OUTBOX_DIR)) {
|
|
16438
16204
|
if (isFirstAttemptOutboxFile(f)) void processRecoveryOutboxFile(f);
|
|
16439
16205
|
}
|
|
16440
16206
|
} catch {
|
|
@@ -16443,7 +16209,7 @@ function startRecoveryOutboxWatcher() {
|
|
|
16443
16209
|
const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
16444
16210
|
if (event !== "rename" || !filename) return;
|
|
16445
16211
|
if (!isFirstAttemptOutboxFile(filename)) return;
|
|
16446
|
-
if (existsSync2(
|
|
16212
|
+
if (existsSync2(join4(RECOVERY_OUTBOX_DIR, filename))) {
|
|
16447
16213
|
void processRecoveryOutboxFile(filename);
|
|
16448
16214
|
}
|
|
16449
16215
|
});
|
|
@@ -16459,15 +16225,15 @@ function startRecoveryOutboxWatcher() {
|
|
|
16459
16225
|
}
|
|
16460
16226
|
startRecoveryOutboxWatcher();
|
|
16461
16227
|
var STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
|
|
16462
|
-
function trackPendingMessage(chatId, messageId, chatType) {
|
|
16463
|
-
writePendingInboundMarker(chatId, messageId, chatType);
|
|
16228
|
+
function trackPendingMessage(chatId, messageId, chatType, undeliverable = false) {
|
|
16229
|
+
writePendingInboundMarker(chatId, messageId, chatType, undeliverable);
|
|
16464
16230
|
}
|
|
16465
16231
|
function sweepTelegramStaleMarkersOnBoot() {
|
|
16466
16232
|
if (!PENDING_INBOUND_DIR) return;
|
|
16467
16233
|
if (!existsSync2(PENDING_INBOUND_DIR)) return;
|
|
16468
16234
|
let filenames;
|
|
16469
16235
|
try {
|
|
16470
|
-
filenames =
|
|
16236
|
+
filenames = readdirSync2(PENDING_INBOUND_DIR);
|
|
16471
16237
|
} catch (err) {
|
|
16472
16238
|
process.stderr.write(
|
|
16473
16239
|
`telegram-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
|
|
@@ -16480,10 +16246,10 @@ function sweepTelegramStaleMarkersOnBoot() {
|
|
|
16480
16246
|
for (const filename of filenames) {
|
|
16481
16247
|
if (!filename.endsWith(".json")) continue;
|
|
16482
16248
|
if (filename.endsWith(".tmp")) continue;
|
|
16483
|
-
const fullPath =
|
|
16249
|
+
const fullPath = join4(PENDING_INBOUND_DIR, filename);
|
|
16484
16250
|
let marker;
|
|
16485
16251
|
try {
|
|
16486
|
-
marker = JSON.parse(
|
|
16252
|
+
marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
|
|
16487
16253
|
} catch (err) {
|
|
16488
16254
|
process.stderr.write(
|
|
16489
16255
|
`telegram-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactId(filename)}: ${err.message}
|
|
@@ -16532,17 +16298,14 @@ function clearPendingMessage(chatId, messageId) {
|
|
|
16532
16298
|
const prefix = `${safeChatId}__`;
|
|
16533
16299
|
let filenames;
|
|
16534
16300
|
try {
|
|
16535
|
-
filenames =
|
|
16301
|
+
filenames = readdirSync2(PENDING_INBOUND_DIR);
|
|
16536
16302
|
} catch {
|
|
16537
16303
|
return;
|
|
16538
16304
|
}
|
|
16539
16305
|
for (const filename of filenames) {
|
|
16540
16306
|
if (!filename.startsWith(prefix)) continue;
|
|
16541
16307
|
if (!filename.endsWith(".json")) continue;
|
|
16542
|
-
|
|
16543
|
-
unlinkSync3(join3(PENDING_INBOUND_DIR, filename));
|
|
16544
|
-
} catch {
|
|
16545
|
-
}
|
|
16308
|
+
clearTelegramMarkerFileWithHeal(join4(PENDING_INBOUND_DIR, filename));
|
|
16546
16309
|
}
|
|
16547
16310
|
}
|
|
16548
16311
|
function noteThreadActivity(chatId, messageId) {
|
|
@@ -16567,46 +16330,12 @@ var mcp = new Server(
|
|
|
16567
16330
|
"Inbound attachments: <channel> `files` is a JSON-serialised array \u2014 JSON.parse it. If an entry has `path`, the image is already downloaded \u2014 Read it directly, do NOT call telegram.download_attachment. Use that tool only for entries with `file_id` but NO `path` (PDF, docx, voice, audio, video, animations): pass file_id + chat_id verbatim, then Read the returned path. Single-image messages also get a top-level `image_path`. Caption arrives as channel content. Don't surface internal file-handling errors that don't affect the answer.",
|
|
16568
16331
|
'For work >30s follow CLAUDE.md kanban flow: kanban_add \u2192 reply "On it \u2014 tracking here: <kanban URL>" \u2192 move to in_progress \u2192 do the work \u2192 reply with the result. Simple lookups skip kanban but still reply.',
|
|
16569
16332
|
"Address users by user_name; user is the numeric Telegram ID. Resolve ambiguous times against your own Timezone from CLAUDE.md \u2014 do not ask.",
|
|
16570
|
-
|
|
16333
|
+
`Reaction taxonomy (use telegram.react sparingly \u2014 prefer telegram.reply): \u{1F440} = ack (already auto-added on inbound, do not duplicate); \u{1F44D} or \u{1F389} = success. NEVER react to signal failure of YOUR work. On failure, telegram.reply with one sentence explaining what went wrong. Free-tier emoji: \u{1F44D} \u{1F44E} \u2764 \u{1F525} \u{1F389} \u{1F914} \u{1F92F} \u{1F64F} \u{1F44C} \u{1F440} \u{1F4AF} \u270D \u{1FAE1} \u{1F192} \u{1F973} \u{1F494}. (If a message arrived while you were offline you may see a system-posted "can't respond right now" notice \u2014 that's the runtime, not you; don't repeat or apologise for it.)`
|
|
16571
16334
|
].join(" ")
|
|
16572
16335
|
}
|
|
16573
16336
|
);
|
|
16574
|
-
var TELEGRAM_PROGRESS_TIMEOUT_MS = 5e3;
|
|
16575
|
-
var progressRegistry = new ProgressToolRegistry({
|
|
16576
|
-
prefix: "telegram",
|
|
16577
|
-
surfaceDescription: "a Telegram chat",
|
|
16578
|
-
startArgsSchema: {
|
|
16579
|
-
properties: {
|
|
16580
|
-
chat_id: {
|
|
16581
|
-
type: "string",
|
|
16582
|
-
description: "Telegram chat id (from the `chat_id` attribute on the inbound <channel> tag). Numeric ids are passed as strings; the API accepts both."
|
|
16583
|
-
},
|
|
16584
|
-
message_thread_id: {
|
|
16585
|
-
type: "string",
|
|
16586
|
-
description: "Forum topic id (`message_thread_id`) \u2014 pass this if the inbound arrived in a forum topic so the anchor lands in the same topic. Omit for normal chats."
|
|
16587
|
-
},
|
|
16588
|
-
reply_to_message_id: {
|
|
16589
|
-
type: "string",
|
|
16590
|
-
description: "Optional message id to thread the anchor under (typically the inbound message_id from the <channel> tag)."
|
|
16591
|
-
}
|
|
16592
|
-
},
|
|
16593
|
-
required: ["chat_id"]
|
|
16594
|
-
},
|
|
16595
|
-
createFlush: (args) => {
|
|
16596
|
-
const chatId = args.chat_id;
|
|
16597
|
-
const threadId = typeof args.message_thread_id === "string" && args.message_thread_id.length > 0 ? Number(args.message_thread_id) : void 0;
|
|
16598
|
-
const replyTo = typeof args.reply_to_message_id === "string" && args.reply_to_message_id.length > 0 ? Number(args.reply_to_message_id) : void 0;
|
|
16599
|
-
return createTelegramProgressFlush({
|
|
16600
|
-
chatId,
|
|
16601
|
-
messageThreadId: Number.isFinite(threadId) ? threadId : void 0,
|
|
16602
|
-
replyToMessageId: Number.isFinite(replyTo) ? replyTo : void 0,
|
|
16603
|
-
callImpl: (method, body) => telegramApiCall(method, body, TELEGRAM_PROGRESS_TIMEOUT_MS)
|
|
16604
|
-
});
|
|
16605
|
-
}
|
|
16606
|
-
});
|
|
16607
16337
|
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16608
16338
|
tools: [
|
|
16609
|
-
...progressRegistry.getDefinitions(),
|
|
16610
16339
|
{
|
|
16611
16340
|
name: "channel_request_input",
|
|
16612
16341
|
description: "Generic Yes/No or multi-choice picker. Renders as a Telegram inline keyboard; the tool blocks until the user taps an option (or the timeout elapses) and returns the resolved value. Use this when you need a structured pick rather than freeform text \u2014 e.g. confirming a draft action or disambiguating an ambiguous fork.",
|
|
@@ -16698,7 +16427,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
16698
16427
|
},
|
|
16699
16428
|
{
|
|
16700
16429
|
name: "telegram.react",
|
|
16701
|
-
description:
|
|
16430
|
+
description: `Add an emoji reaction to a Telegram message. Use sparingly \u2014 prefer a text reply via telegram.reply. Reaction taxonomy: \u{1F44D} or \u{1F389} = action completed successfully. NEVER react to signal failure of your work \u2014 users can't tell why it failed from a reaction. On failure, call telegram.reply with one sentence explaining what went wrong instead. \u{1F440} is already auto-applied on inbound; do not duplicate. Only free-tier emoji reactions are available to bots (Premium-only emoji fail silently). Pass an empty string or omit emoji to clear the bot's reaction on that message. (Note: when a message arrives while the agent is offline/wedged, the runtime \u2014 not you \u2014 posts a brief "can't respond right now" notice; you never need to add a failure reaction for that.)`,
|
|
16702
16431
|
inputSchema: {
|
|
16703
16432
|
type: "object",
|
|
16704
16433
|
properties: {
|
|
@@ -16725,8 +16454,6 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16725
16454
|
if (isImpersonating() && !channelsEnabledOverride() && TELEGRAM_EGRESS_TOOLS.has(name)) {
|
|
16726
16455
|
return buildImpersonationRefusal(name);
|
|
16727
16456
|
}
|
|
16728
|
-
const progressResult = await progressRegistry.handle(name, args ?? {});
|
|
16729
|
-
if (progressResult !== void 0) return progressResult;
|
|
16730
16457
|
if (name === "channel_request_input") {
|
|
16731
16458
|
return handleChannelRequestInput(args ?? {});
|
|
16732
16459
|
}
|
|
@@ -16926,12 +16653,12 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16926
16653
|
function channelRequestInputAvailable() {
|
|
16927
16654
|
return Boolean(AGT_HOST && AGT_API_KEY && AGT_AGENT_ID);
|
|
16928
16655
|
}
|
|
16929
|
-
function
|
|
16656
|
+
function errResult(text) {
|
|
16930
16657
|
return { content: [{ type: "text", text }], isError: true };
|
|
16931
16658
|
}
|
|
16932
16659
|
async function handleChannelRequestInput(args) {
|
|
16933
16660
|
if (!channelRequestInputAvailable()) {
|
|
16934
|
-
return
|
|
16661
|
+
return errResult(
|
|
16935
16662
|
"channel_request_input is disabled \u2014 missing AGT_HOST / AGT_API_KEY / AGT_AGENT_ID env wiring."
|
|
16936
16663
|
);
|
|
16937
16664
|
}
|
|
@@ -16939,12 +16666,12 @@ async function handleChannelRequestInput(args) {
|
|
|
16939
16666
|
const { apiCall: apiCall2, waitForResolution: waitForResolution2, validateAskUserOptions: validateAskUserOptions2 } = runtime;
|
|
16940
16667
|
const { thread_id, prompt_text, options, schema, request_id, timeout_seconds } = args;
|
|
16941
16668
|
if (typeof thread_id !== "string" || !thread_id) {
|
|
16942
|
-
return
|
|
16669
|
+
return errResult(
|
|
16943
16670
|
"thread_id is required (Telegram shape: `<chat_id>` or `<chat_id>:<reply_to_message_id>`)"
|
|
16944
16671
|
);
|
|
16945
16672
|
}
|
|
16946
16673
|
if (typeof prompt_text !== "string" || !prompt_text) {
|
|
16947
|
-
return
|
|
16674
|
+
return errResult("prompt_text is required");
|
|
16948
16675
|
}
|
|
16949
16676
|
const optsValidation = validateAskUserOptions2(options, {
|
|
16950
16677
|
maxOptions: 4,
|
|
@@ -16952,27 +16679,27 @@ async function handleChannelRequestInput(args) {
|
|
|
16952
16679
|
maxValueChars: 2e3
|
|
16953
16680
|
});
|
|
16954
16681
|
if (!optsValidation.ok) {
|
|
16955
|
-
return
|
|
16682
|
+
return errResult(
|
|
16956
16683
|
`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
|
|
16957
16684
|
);
|
|
16958
16685
|
}
|
|
16959
16686
|
const optionsArr = options;
|
|
16960
16687
|
if (optionsArr.length < 2) {
|
|
16961
|
-
return
|
|
16688
|
+
return errResult(
|
|
16962
16689
|
"options must contain at least 2 entries (this is a picker, not a notification)"
|
|
16963
16690
|
);
|
|
16964
16691
|
}
|
|
16965
16692
|
if (!schema || typeof schema.type !== "string") {
|
|
16966
|
-
return
|
|
16693
|
+
return errResult("schema is required with shape { type: 'yes_no' | 'choice' }");
|
|
16967
16694
|
}
|
|
16968
16695
|
if (schema.type !== "yes_no" && schema.type !== "choice") {
|
|
16969
|
-
return
|
|
16696
|
+
return errResult("schema.type must be 'yes_no' or 'choice'");
|
|
16970
16697
|
}
|
|
16971
16698
|
if (schema.type === "yes_no" && optionsArr.length !== 2) {
|
|
16972
|
-
return
|
|
16699
|
+
return errResult("schema.type='yes_no' requires exactly 2 options");
|
|
16973
16700
|
}
|
|
16974
16701
|
if (request_id !== void 0 && (typeof request_id !== "string" || !request_id)) {
|
|
16975
|
-
return
|
|
16702
|
+
return errResult("request_id must be a non-empty string when provided");
|
|
16976
16703
|
}
|
|
16977
16704
|
const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
|
|
16978
16705
|
const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
|
|
@@ -16994,10 +16721,10 @@ async function handleChannelRequestInput(args) {
|
|
|
16994
16721
|
dispatchPayload = await res.json().catch(() => ({}));
|
|
16995
16722
|
if (!res.ok || !dispatchPayload.ok || !dispatchPayload.callback_id) {
|
|
16996
16723
|
const reason = dispatchPayload.reason ?? `http_${res.status}`;
|
|
16997
|
-
return
|
|
16724
|
+
return errResult(`channel_request_input dispatch failed: ${reason}`);
|
|
16998
16725
|
}
|
|
16999
16726
|
} catch (err) {
|
|
17000
|
-
return
|
|
16727
|
+
return errResult(
|
|
17001
16728
|
`channel_request_input dispatch failed: ${err.message}`
|
|
17002
16729
|
);
|
|
17003
16730
|
}
|
|
@@ -17419,8 +17146,21 @@ async function pollLoop() {
|
|
|
17419
17146
|
}
|
|
17420
17147
|
}
|
|
17421
17148
|
const messageId = String(msg.message_id);
|
|
17422
|
-
|
|
17423
|
-
|
|
17149
|
+
const ackProbe = process.env.TMUX && AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
17150
|
+
const ackDecision = decideAckReaction({
|
|
17151
|
+
hasTarget: Boolean(chatId && messageId),
|
|
17152
|
+
integrationReady: Boolean(BOT_TOKEN),
|
|
17153
|
+
tmux: ackProbe.tmux,
|
|
17154
|
+
claude: ackProbe.claude,
|
|
17155
|
+
withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
|
|
17156
|
+
oldestPendingAgeMs: oldestPendingMarkerAgeMs(PENDING_INBOUND_DIR)
|
|
17157
|
+
});
|
|
17158
|
+
if (ackDecision === "ack") {
|
|
17159
|
+
void setMessageReaction(chatId, messageId, ACK_EMOJI);
|
|
17160
|
+
} else if (ackDecision === "undeliverable") {
|
|
17161
|
+
void notifyUndeliverable(chatId, messageId);
|
|
17162
|
+
}
|
|
17163
|
+
trackPendingMessage(chatId, messageId, msg.chat.type, ackDecision === "undeliverable");
|
|
17424
17164
|
const fileMeta = [];
|
|
17425
17165
|
for (const attachment of classifiedAttachments) {
|
|
17426
17166
|
if (attachment.kind === "image") {
|
|
@@ -17546,3 +17286,7 @@ pollLoop().catch((err) => {
|
|
|
17546
17286
|
);
|
|
17547
17287
|
process.exit(1);
|
|
17548
17288
|
});
|
|
17289
|
+
export {
|
|
17290
|
+
__resetBackOnlineNoticeThrottle,
|
|
17291
|
+
__resetUndeliverableNoticeThrottle
|
|
17292
|
+
};
|