@integrity-labs/agt-cli 0.27.7-test.6 → 0.27.8-test.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/agt.js +131 -37
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-AACMX6LE.js → chunk-DHF75YYA.js} +185 -60
- package/dist/chunk-DHF75YYA.js.map +1 -0
- package/dist/{chunk-GN4XPQWJ.js → chunk-F4NG4EXD.js} +37 -28
- package/dist/chunk-F4NG4EXD.js.map +1 -0
- package/dist/{chunk-YSBGIXJG.js → chunk-HT6EETEL.js} +1 -1
- package/dist/{claude-pair-runtime-ZBQKBBMT.js → claude-pair-runtime-OBAJZDXK.js} +2 -2
- package/dist/lib/manager-worker.js +14 -10
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +9 -462
- package/dist/mcp/index.js +44 -25
- package/dist/mcp/slack-channel.js +315 -604
- package/dist/mcp/telegram-channel.js +251 -513
- package/dist/{persistent-session-ICYFLUAM.js → persistent-session-SBSOZG74.js} +3 -3
- package/dist/{responsiveness-probe-WZNQ2762.js → responsiveness-probe-DU4IJ2RZ.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-AACMX6LE.js.map +0 -1
- package/dist/chunk-GN4XPQWJ.js.map +0 -1
- /package/dist/{chunk-YSBGIXJG.js.map → chunk-HT6EETEL.js.map} +0 -0
- /package/dist/{claude-pair-runtime-ZBQKBBMT.js.map → claude-pair-runtime-OBAJZDXK.js.map} +0 -0
- /package/dist/{persistent-session-ICYFLUAM.js.map → persistent-session-SBSOZG74.js.map} +0 -0
- /package/dist/{responsiveness-probe-WZNQ2762.js.map → responsiveness-probe-DU4IJ2RZ.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,112 @@ 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
|
+
function decideAckReaction(i) {
|
|
15510
|
+
if (!i.hasTarget) return "none";
|
|
15511
|
+
if (!i.integrationReady) return "undeliverable";
|
|
15512
|
+
if (i.tmux === "dead") return "undeliverable";
|
|
15513
|
+
if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
|
|
15514
|
+
const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
|
|
15515
|
+
if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
|
|
15516
|
+
return "undeliverable";
|
|
15517
|
+
}
|
|
15518
|
+
return "ack";
|
|
15519
|
+
}
|
|
15520
|
+
function decideRecoveryHeal(i) {
|
|
15521
|
+
return i.wasUndeliverable && i.hasTarget ? "heal" : "none";
|
|
15522
|
+
}
|
|
15523
|
+
function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
|
|
15524
|
+
if (!dir) return null;
|
|
15525
|
+
let names;
|
|
15526
|
+
try {
|
|
15527
|
+
names = readdirSync(dir);
|
|
15528
|
+
} catch {
|
|
15529
|
+
return null;
|
|
15530
|
+
}
|
|
15531
|
+
let oldest = null;
|
|
15532
|
+
for (const name of names) {
|
|
15533
|
+
if (!name.endsWith(".json")) continue;
|
|
15534
|
+
let receivedAt;
|
|
15535
|
+
try {
|
|
15536
|
+
const raw = JSON.parse(readFileSync2(join3(dir, name), "utf-8"));
|
|
15537
|
+
receivedAt = raw.received_at;
|
|
15538
|
+
} catch {
|
|
15539
|
+
continue;
|
|
15540
|
+
}
|
|
15541
|
+
if (!receivedAt) continue;
|
|
15542
|
+
const t = Date.parse(receivedAt);
|
|
15543
|
+
if (Number.isNaN(t)) continue;
|
|
15544
|
+
const age = now - t;
|
|
15545
|
+
if (age < 0) continue;
|
|
15546
|
+
if (oldest == null || age > oldest) oldest = age;
|
|
15547
|
+
}
|
|
15548
|
+
return oldest;
|
|
15549
|
+
}
|
|
15550
|
+
|
|
15551
|
+
// src/session-probe-runtime.ts
|
|
15552
|
+
import { execFileSync } from "child_process";
|
|
15553
|
+
function agentTmuxSessionName(codeName) {
|
|
15554
|
+
return `agt-${codeName}`;
|
|
15555
|
+
}
|
|
15556
|
+
function escapePgrepRegex(value) {
|
|
15557
|
+
return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
|
|
15558
|
+
}
|
|
15559
|
+
function probeClaudeProcessInTmux(tmuxSession) {
|
|
15560
|
+
const escapedSession = escapePgrepRegex(tmuxSession);
|
|
15561
|
+
const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
|
|
15562
|
+
try {
|
|
15563
|
+
const out = execFileSync("pgrep", ["-f", "--", pattern], {
|
|
15564
|
+
encoding: "utf-8",
|
|
15565
|
+
timeout: 3e3
|
|
15566
|
+
}).trim();
|
|
15567
|
+
return out.length > 0 ? "alive" : "dead";
|
|
15568
|
+
} catch (err) {
|
|
15569
|
+
const e = err;
|
|
15570
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
15571
|
+
return e?.status === 1 ? "dead" : "unknown";
|
|
15572
|
+
}
|
|
15573
|
+
}
|
|
15574
|
+
function probeTmuxSession(tmuxSession) {
|
|
15575
|
+
try {
|
|
15576
|
+
execFileSync("tmux", ["has-session", "-t", tmuxSession], {
|
|
15577
|
+
stdio: "ignore",
|
|
15578
|
+
timeout: 3e3
|
|
15579
|
+
});
|
|
15580
|
+
return "alive";
|
|
15581
|
+
} catch (err) {
|
|
15582
|
+
const e = err;
|
|
15583
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
15584
|
+
return "dead";
|
|
15585
|
+
}
|
|
15586
|
+
}
|
|
15587
|
+
function probeAgentSession(codeName) {
|
|
15588
|
+
const session = agentTmuxSessionName(codeName);
|
|
15589
|
+
const tmux = probeTmuxSession(session);
|
|
15590
|
+
const claude = tmux === "alive" ? probeClaudeProcessInTmux(session) : tmux;
|
|
15591
|
+
return { tmux, claude };
|
|
15592
|
+
}
|
|
15593
|
+
var probeCache = /* @__PURE__ */ new Map();
|
|
15594
|
+
var SESSION_PROBE_TTL_MS = 15e3;
|
|
15595
|
+
function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = Date.now()) {
|
|
15596
|
+
const cached2 = probeCache.get(codeName);
|
|
15597
|
+
if (cached2 && now - cached2.at < ttlMs) return cached2.value;
|
|
15598
|
+
const value = probeAgentSession(codeName);
|
|
15599
|
+
probeCache.set(codeName, { at: now, value });
|
|
15600
|
+
return value;
|
|
15601
|
+
}
|
|
15602
|
+
|
|
15928
15603
|
// src/telegram-channel.ts
|
|
15929
15604
|
function redactId(id) {
|
|
15930
15605
|
return createHash("sha256").update(String(id)).digest("hex").slice(0, 8);
|
|
15931
15606
|
}
|
|
15932
15607
|
var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
|
15933
15608
|
var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
|
|
15934
|
-
var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ?
|
|
15609
|
+
var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
15935
15610
|
var AGT_HOST = process.env.AGT_HOST ?? null;
|
|
15936
15611
|
var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
|
|
15937
15612
|
var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
|
|
@@ -16015,9 +15690,9 @@ if (!BOT_TOKEN) {
|
|
|
16015
15690
|
var stderrLogStream = null;
|
|
16016
15691
|
if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
|
|
16017
15692
|
try {
|
|
16018
|
-
const logDir =
|
|
15693
|
+
const logDir = join4(homedir2(), ".augmented", AGENT_CODE_NAME);
|
|
16019
15694
|
mkdirSync3(logDir, { recursive: true });
|
|
16020
|
-
stderrLogStream = createWriteStream(
|
|
15695
|
+
stderrLogStream = createWriteStream(join4(logDir, "telegram-channel-stderr.log"), {
|
|
16021
15696
|
flags: "a",
|
|
16022
15697
|
mode: 384
|
|
16023
15698
|
});
|
|
@@ -16098,7 +15773,76 @@ async function setMessageReaction(chatId, messageId, emoji2) {
|
|
|
16098
15773
|
);
|
|
16099
15774
|
}
|
|
16100
15775
|
}
|
|
16101
|
-
var
|
|
15776
|
+
var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
|
|
15777
|
+
var lastUndeliverableNoticeAt = /* @__PURE__ */ new Map();
|
|
15778
|
+
async function notifyUndeliverable(chatId, messageId) {
|
|
15779
|
+
const key2 = String(chatId);
|
|
15780
|
+
const now = Date.now();
|
|
15781
|
+
const last = lastUndeliverableNoticeAt.get(key2);
|
|
15782
|
+
if (last != null && now - last < UNDELIVERABLE_NOTICE_THROTTLE_MS) return;
|
|
15783
|
+
lastUndeliverableNoticeAt.set(key2, now);
|
|
15784
|
+
try {
|
|
15785
|
+
const resp = await telegramApiCall(
|
|
15786
|
+
"sendMessage",
|
|
15787
|
+
{
|
|
15788
|
+
chat_id: chatId,
|
|
15789
|
+
text: "\u26A0\uFE0F I can't respond right now \u2014 I'll follow up once I'm back.",
|
|
15790
|
+
reply_to_message_id: Number(messageId),
|
|
15791
|
+
allow_sending_without_reply: true
|
|
15792
|
+
},
|
|
15793
|
+
1e4
|
|
15794
|
+
);
|
|
15795
|
+
if (!resp.ok) {
|
|
15796
|
+
process.stderr.write(
|
|
15797
|
+
`telegram-channel(${AGENT_CODE_NAME}): undeliverable notice failed (chat=${redactId(chatId)}): ${resp.description ?? "unknown"}
|
|
15798
|
+
`
|
|
15799
|
+
);
|
|
15800
|
+
}
|
|
15801
|
+
} catch (err) {
|
|
15802
|
+
process.stderr.write(
|
|
15803
|
+
`telegram-channel(${AGENT_CODE_NAME}): undeliverable notice error: ${err.message}
|
|
15804
|
+
`
|
|
15805
|
+
);
|
|
15806
|
+
}
|
|
15807
|
+
}
|
|
15808
|
+
function __resetUndeliverableNoticeThrottle() {
|
|
15809
|
+
lastUndeliverableNoticeAt.clear();
|
|
15810
|
+
}
|
|
15811
|
+
var lastBackOnlineNoticeAt = /* @__PURE__ */ new Map();
|
|
15812
|
+
function notifyBackOnline(chatId) {
|
|
15813
|
+
const key2 = String(chatId);
|
|
15814
|
+
const now = Date.now();
|
|
15815
|
+
const last = lastBackOnlineNoticeAt.get(key2);
|
|
15816
|
+
if (last != null && now - last < UNDELIVERABLE_NOTICE_THROTTLE_MS) return;
|
|
15817
|
+
lastBackOnlineNoticeAt.set(key2, now);
|
|
15818
|
+
void (async () => {
|
|
15819
|
+
try {
|
|
15820
|
+
const resp = await telegramApiCall(
|
|
15821
|
+
"sendMessage",
|
|
15822
|
+
{
|
|
15823
|
+
chat_id: chatId,
|
|
15824
|
+
text: "\u2705 Back online \u2014 catching up on what I missed."
|
|
15825
|
+
},
|
|
15826
|
+
1e4
|
|
15827
|
+
);
|
|
15828
|
+
if (!resp.ok) {
|
|
15829
|
+
process.stderr.write(
|
|
15830
|
+
`telegram-channel(${AGENT_CODE_NAME}): back-online notice failed (chat=${redactId(chatId)}): ${resp.description ?? "unknown"}
|
|
15831
|
+
`
|
|
15832
|
+
);
|
|
15833
|
+
}
|
|
15834
|
+
} catch (err) {
|
|
15835
|
+
process.stderr.write(
|
|
15836
|
+
`telegram-channel(${AGENT_CODE_NAME}): back-online notice error: ${err.message}
|
|
15837
|
+
`
|
|
15838
|
+
);
|
|
15839
|
+
}
|
|
15840
|
+
})();
|
|
15841
|
+
}
|
|
15842
|
+
function __resetBackOnlineNoticeThrottle() {
|
|
15843
|
+
lastBackOnlineNoticeAt.clear();
|
|
15844
|
+
}
|
|
15845
|
+
var RESTART_FLAGS_DIR = join4(homedir2(), ".augmented", "restart-flags");
|
|
16102
15846
|
function buildTelegramHelpMessage(codeName) {
|
|
16103
15847
|
return [
|
|
16104
15848
|
`\u{1F916} *Available commands for \`${codeName}\`*`,
|
|
@@ -16138,14 +15882,14 @@ async function handleRestartCommand(opts) {
|
|
|
16138
15882
|
if (!existsSync2(RESTART_FLAGS_DIR)) {
|
|
16139
15883
|
mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
|
|
16140
15884
|
}
|
|
16141
|
-
const flagPath =
|
|
15885
|
+
const flagPath = join4(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
|
|
16142
15886
|
const flag = {
|
|
16143
15887
|
codeName: AGENT_CODE_NAME,
|
|
16144
15888
|
source: "telegram",
|
|
16145
15889
|
ts: Date.now(),
|
|
16146
15890
|
reply: { chat_id: opts.chatId, message_id: opts.messageId }
|
|
16147
15891
|
};
|
|
16148
|
-
const tmpPath = `${flagPath}.${process.pid}.${
|
|
15892
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
16149
15893
|
writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
16150
15894
|
renameSync3(tmpPath, flagPath);
|
|
16151
15895
|
process.stderr.write(
|
|
@@ -16254,25 +15998,27 @@ async function classifyRestartCommand(text) {
|
|
|
16254
15998
|
if (!ours) return "verification_failed";
|
|
16255
15999
|
return target === ours ? "act" : "ignore";
|
|
16256
16000
|
}
|
|
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 ?
|
|
16001
|
+
var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
16002
|
+
var PENDING_INBOUND_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-pending-inbound") : null;
|
|
16003
|
+
var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-recovery-outbox") : null;
|
|
16260
16004
|
function safeMarkerName(chatId, messageId) {
|
|
16261
16005
|
const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
16262
16006
|
return `${safe(chatId)}__${safe(messageId)}.json`;
|
|
16263
16007
|
}
|
|
16264
16008
|
function pendingInboundPath(chatId, messageId) {
|
|
16265
16009
|
if (!PENDING_INBOUND_DIR) return null;
|
|
16266
|
-
return
|
|
16010
|
+
return join4(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
|
|
16267
16011
|
}
|
|
16268
|
-
function writePendingInboundMarker(chatId, messageId, chatType) {
|
|
16012
|
+
function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false) {
|
|
16269
16013
|
const path = pendingInboundPath(chatId, messageId);
|
|
16270
16014
|
if (!path || !PENDING_INBOUND_DIR) return;
|
|
16271
16015
|
const marker = {
|
|
16272
16016
|
chat_id: chatId,
|
|
16273
16017
|
message_id: messageId,
|
|
16274
16018
|
chat_type: chatType,
|
|
16275
|
-
received_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
16019
|
+
received_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16020
|
+
// Only persist the flag when set — keeps healthy-path markers byte-identical.
|
|
16021
|
+
...undeliverable ? { undeliverable: true } : {}
|
|
16276
16022
|
};
|
|
16277
16023
|
try {
|
|
16278
16024
|
mkdirSync3(PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
@@ -16284,14 +16030,28 @@ function writePendingInboundMarker(chatId, messageId, chatType) {
|
|
|
16284
16030
|
);
|
|
16285
16031
|
}
|
|
16286
16032
|
}
|
|
16287
|
-
function
|
|
16288
|
-
|
|
16289
|
-
|
|
16033
|
+
function clearTelegramMarkerFileWithHeal(fullPath) {
|
|
16034
|
+
let marker = null;
|
|
16035
|
+
try {
|
|
16036
|
+
marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
|
|
16037
|
+
} catch {
|
|
16038
|
+
}
|
|
16039
|
+
if (marker && decideRecoveryHeal({
|
|
16040
|
+
wasUndeliverable: marker.undeliverable === true,
|
|
16041
|
+
hasTarget: Boolean(marker.chat_id)
|
|
16042
|
+
}) === "heal") {
|
|
16043
|
+
notifyBackOnline(marker.chat_id);
|
|
16044
|
+
}
|
|
16290
16045
|
try {
|
|
16291
|
-
if (existsSync2(
|
|
16046
|
+
if (existsSync2(fullPath)) unlinkSync3(fullPath);
|
|
16292
16047
|
} catch {
|
|
16293
16048
|
}
|
|
16294
16049
|
}
|
|
16050
|
+
function clearPendingInboundMarker(chatId, messageId) {
|
|
16051
|
+
const path = pendingInboundPath(chatId, messageId);
|
|
16052
|
+
if (!path) return;
|
|
16053
|
+
clearTelegramMarkerFileWithHeal(path);
|
|
16054
|
+
}
|
|
16295
16055
|
var MAX_RECOVERY_ATTEMPTS = 3;
|
|
16296
16056
|
function nextRetryName(filename) {
|
|
16297
16057
|
const match = filename.match(/^(.*?)(?:\.retry-(\d+))?\.json$/);
|
|
@@ -16307,10 +16067,10 @@ function nextRetryName(filename) {
|
|
|
16307
16067
|
async function processRecoveryOutboxFile(filename) {
|
|
16308
16068
|
if (!RECOVERY_OUTBOX_DIR) return;
|
|
16309
16069
|
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
16310
|
-
const fullPath =
|
|
16070
|
+
const fullPath = join4(RECOVERY_OUTBOX_DIR, filename);
|
|
16311
16071
|
let payload;
|
|
16312
16072
|
try {
|
|
16313
|
-
const raw =
|
|
16073
|
+
const raw = readFileSync3(fullPath, "utf-8");
|
|
16314
16074
|
payload = JSON.parse(raw);
|
|
16315
16075
|
} catch (err) {
|
|
16316
16076
|
process.stderr.write(
|
|
@@ -16373,7 +16133,7 @@ async function processRecoveryOutboxFile(filename) {
|
|
|
16373
16133
|
const next = nextRetryName(filename);
|
|
16374
16134
|
if (next) {
|
|
16375
16135
|
try {
|
|
16376
|
-
renameSync3(fullPath,
|
|
16136
|
+
renameSync3(fullPath, join4(RECOVERY_OUTBOX_DIR, next.next));
|
|
16377
16137
|
if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
|
|
16378
16138
|
process.stderr.write(
|
|
16379
16139
|
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
@@ -16404,7 +16164,7 @@ function scanRecoveryRetries() {
|
|
|
16404
16164
|
if (!RECOVERY_OUTBOX_DIR) return;
|
|
16405
16165
|
let entries;
|
|
16406
16166
|
try {
|
|
16407
|
-
entries =
|
|
16167
|
+
entries = readdirSync2(RECOVERY_OUTBOX_DIR);
|
|
16408
16168
|
} catch {
|
|
16409
16169
|
return;
|
|
16410
16170
|
}
|
|
@@ -16413,7 +16173,7 @@ function scanRecoveryRetries() {
|
|
|
16413
16173
|
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
16414
16174
|
let mtimeMs;
|
|
16415
16175
|
try {
|
|
16416
|
-
mtimeMs = statSync(
|
|
16176
|
+
mtimeMs = statSync(join4(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
16417
16177
|
} catch {
|
|
16418
16178
|
continue;
|
|
16419
16179
|
}
|
|
@@ -16434,7 +16194,7 @@ function startRecoveryOutboxWatcher() {
|
|
|
16434
16194
|
return;
|
|
16435
16195
|
}
|
|
16436
16196
|
try {
|
|
16437
|
-
for (const f of
|
|
16197
|
+
for (const f of readdirSync2(RECOVERY_OUTBOX_DIR)) {
|
|
16438
16198
|
if (isFirstAttemptOutboxFile(f)) void processRecoveryOutboxFile(f);
|
|
16439
16199
|
}
|
|
16440
16200
|
} catch {
|
|
@@ -16443,7 +16203,7 @@ function startRecoveryOutboxWatcher() {
|
|
|
16443
16203
|
const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
16444
16204
|
if (event !== "rename" || !filename) return;
|
|
16445
16205
|
if (!isFirstAttemptOutboxFile(filename)) return;
|
|
16446
|
-
if (existsSync2(
|
|
16206
|
+
if (existsSync2(join4(RECOVERY_OUTBOX_DIR, filename))) {
|
|
16447
16207
|
void processRecoveryOutboxFile(filename);
|
|
16448
16208
|
}
|
|
16449
16209
|
});
|
|
@@ -16459,15 +16219,15 @@ function startRecoveryOutboxWatcher() {
|
|
|
16459
16219
|
}
|
|
16460
16220
|
startRecoveryOutboxWatcher();
|
|
16461
16221
|
var STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
|
|
16462
|
-
function trackPendingMessage(chatId, messageId, chatType) {
|
|
16463
|
-
writePendingInboundMarker(chatId, messageId, chatType);
|
|
16222
|
+
function trackPendingMessage(chatId, messageId, chatType, undeliverable = false) {
|
|
16223
|
+
writePendingInboundMarker(chatId, messageId, chatType, undeliverable);
|
|
16464
16224
|
}
|
|
16465
16225
|
function sweepTelegramStaleMarkersOnBoot() {
|
|
16466
16226
|
if (!PENDING_INBOUND_DIR) return;
|
|
16467
16227
|
if (!existsSync2(PENDING_INBOUND_DIR)) return;
|
|
16468
16228
|
let filenames;
|
|
16469
16229
|
try {
|
|
16470
|
-
filenames =
|
|
16230
|
+
filenames = readdirSync2(PENDING_INBOUND_DIR);
|
|
16471
16231
|
} catch (err) {
|
|
16472
16232
|
process.stderr.write(
|
|
16473
16233
|
`telegram-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
|
|
@@ -16480,10 +16240,10 @@ function sweepTelegramStaleMarkersOnBoot() {
|
|
|
16480
16240
|
for (const filename of filenames) {
|
|
16481
16241
|
if (!filename.endsWith(".json")) continue;
|
|
16482
16242
|
if (filename.endsWith(".tmp")) continue;
|
|
16483
|
-
const fullPath =
|
|
16243
|
+
const fullPath = join4(PENDING_INBOUND_DIR, filename);
|
|
16484
16244
|
let marker;
|
|
16485
16245
|
try {
|
|
16486
|
-
marker = JSON.parse(
|
|
16246
|
+
marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
|
|
16487
16247
|
} catch (err) {
|
|
16488
16248
|
process.stderr.write(
|
|
16489
16249
|
`telegram-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactId(filename)}: ${err.message}
|
|
@@ -16532,17 +16292,14 @@ function clearPendingMessage(chatId, messageId) {
|
|
|
16532
16292
|
const prefix = `${safeChatId}__`;
|
|
16533
16293
|
let filenames;
|
|
16534
16294
|
try {
|
|
16535
|
-
filenames =
|
|
16295
|
+
filenames = readdirSync2(PENDING_INBOUND_DIR);
|
|
16536
16296
|
} catch {
|
|
16537
16297
|
return;
|
|
16538
16298
|
}
|
|
16539
16299
|
for (const filename of filenames) {
|
|
16540
16300
|
if (!filename.startsWith(prefix)) continue;
|
|
16541
16301
|
if (!filename.endsWith(".json")) continue;
|
|
16542
|
-
|
|
16543
|
-
unlinkSync3(join3(PENDING_INBOUND_DIR, filename));
|
|
16544
|
-
} catch {
|
|
16545
|
-
}
|
|
16302
|
+
clearTelegramMarkerFileWithHeal(join4(PENDING_INBOUND_DIR, filename));
|
|
16546
16303
|
}
|
|
16547
16304
|
}
|
|
16548
16305
|
function noteThreadActivity(chatId, messageId) {
|
|
@@ -16567,46 +16324,12 @@ var mcp = new Server(
|
|
|
16567
16324
|
"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
16325
|
'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
16326
|
"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
|
-
|
|
16327
|
+
`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
16328
|
].join(" ")
|
|
16572
16329
|
}
|
|
16573
16330
|
);
|
|
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
16331
|
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16608
16332
|
tools: [
|
|
16609
|
-
...progressRegistry.getDefinitions(),
|
|
16610
16333
|
{
|
|
16611
16334
|
name: "channel_request_input",
|
|
16612
16335
|
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 +16421,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
16698
16421
|
},
|
|
16699
16422
|
{
|
|
16700
16423
|
name: "telegram.react",
|
|
16701
|
-
description:
|
|
16424
|
+
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
16425
|
inputSchema: {
|
|
16703
16426
|
type: "object",
|
|
16704
16427
|
properties: {
|
|
@@ -16725,8 +16448,6 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16725
16448
|
if (isImpersonating() && !channelsEnabledOverride() && TELEGRAM_EGRESS_TOOLS.has(name)) {
|
|
16726
16449
|
return buildImpersonationRefusal(name);
|
|
16727
16450
|
}
|
|
16728
|
-
const progressResult = await progressRegistry.handle(name, args ?? {});
|
|
16729
|
-
if (progressResult !== void 0) return progressResult;
|
|
16730
16451
|
if (name === "channel_request_input") {
|
|
16731
16452
|
return handleChannelRequestInput(args ?? {});
|
|
16732
16453
|
}
|
|
@@ -16926,12 +16647,12 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16926
16647
|
function channelRequestInputAvailable() {
|
|
16927
16648
|
return Boolean(AGT_HOST && AGT_API_KEY && AGT_AGENT_ID);
|
|
16928
16649
|
}
|
|
16929
|
-
function
|
|
16650
|
+
function errResult(text) {
|
|
16930
16651
|
return { content: [{ type: "text", text }], isError: true };
|
|
16931
16652
|
}
|
|
16932
16653
|
async function handleChannelRequestInput(args) {
|
|
16933
16654
|
if (!channelRequestInputAvailable()) {
|
|
16934
|
-
return
|
|
16655
|
+
return errResult(
|
|
16935
16656
|
"channel_request_input is disabled \u2014 missing AGT_HOST / AGT_API_KEY / AGT_AGENT_ID env wiring."
|
|
16936
16657
|
);
|
|
16937
16658
|
}
|
|
@@ -16939,12 +16660,12 @@ async function handleChannelRequestInput(args) {
|
|
|
16939
16660
|
const { apiCall: apiCall2, waitForResolution: waitForResolution2, validateAskUserOptions: validateAskUserOptions2 } = runtime;
|
|
16940
16661
|
const { thread_id, prompt_text, options, schema, request_id, timeout_seconds } = args;
|
|
16941
16662
|
if (typeof thread_id !== "string" || !thread_id) {
|
|
16942
|
-
return
|
|
16663
|
+
return errResult(
|
|
16943
16664
|
"thread_id is required (Telegram shape: `<chat_id>` or `<chat_id>:<reply_to_message_id>`)"
|
|
16944
16665
|
);
|
|
16945
16666
|
}
|
|
16946
16667
|
if (typeof prompt_text !== "string" || !prompt_text) {
|
|
16947
|
-
return
|
|
16668
|
+
return errResult("prompt_text is required");
|
|
16948
16669
|
}
|
|
16949
16670
|
const optsValidation = validateAskUserOptions2(options, {
|
|
16950
16671
|
maxOptions: 4,
|
|
@@ -16952,27 +16673,27 @@ async function handleChannelRequestInput(args) {
|
|
|
16952
16673
|
maxValueChars: 2e3
|
|
16953
16674
|
});
|
|
16954
16675
|
if (!optsValidation.ok) {
|
|
16955
|
-
return
|
|
16676
|
+
return errResult(
|
|
16956
16677
|
`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
|
|
16957
16678
|
);
|
|
16958
16679
|
}
|
|
16959
16680
|
const optionsArr = options;
|
|
16960
16681
|
if (optionsArr.length < 2) {
|
|
16961
|
-
return
|
|
16682
|
+
return errResult(
|
|
16962
16683
|
"options must contain at least 2 entries (this is a picker, not a notification)"
|
|
16963
16684
|
);
|
|
16964
16685
|
}
|
|
16965
16686
|
if (!schema || typeof schema.type !== "string") {
|
|
16966
|
-
return
|
|
16687
|
+
return errResult("schema is required with shape { type: 'yes_no' | 'choice' }");
|
|
16967
16688
|
}
|
|
16968
16689
|
if (schema.type !== "yes_no" && schema.type !== "choice") {
|
|
16969
|
-
return
|
|
16690
|
+
return errResult("schema.type must be 'yes_no' or 'choice'");
|
|
16970
16691
|
}
|
|
16971
16692
|
if (schema.type === "yes_no" && optionsArr.length !== 2) {
|
|
16972
|
-
return
|
|
16693
|
+
return errResult("schema.type='yes_no' requires exactly 2 options");
|
|
16973
16694
|
}
|
|
16974
16695
|
if (request_id !== void 0 && (typeof request_id !== "string" || !request_id)) {
|
|
16975
|
-
return
|
|
16696
|
+
return errResult("request_id must be a non-empty string when provided");
|
|
16976
16697
|
}
|
|
16977
16698
|
const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
|
|
16978
16699
|
const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
|
|
@@ -16994,10 +16715,10 @@ async function handleChannelRequestInput(args) {
|
|
|
16994
16715
|
dispatchPayload = await res.json().catch(() => ({}));
|
|
16995
16716
|
if (!res.ok || !dispatchPayload.ok || !dispatchPayload.callback_id) {
|
|
16996
16717
|
const reason = dispatchPayload.reason ?? `http_${res.status}`;
|
|
16997
|
-
return
|
|
16718
|
+
return errResult(`channel_request_input dispatch failed: ${reason}`);
|
|
16998
16719
|
}
|
|
16999
16720
|
} catch (err) {
|
|
17000
|
-
return
|
|
16721
|
+
return errResult(
|
|
17001
16722
|
`channel_request_input dispatch failed: ${err.message}`
|
|
17002
16723
|
);
|
|
17003
16724
|
}
|
|
@@ -17419,8 +17140,21 @@ async function pollLoop() {
|
|
|
17419
17140
|
}
|
|
17420
17141
|
}
|
|
17421
17142
|
const messageId = String(msg.message_id);
|
|
17422
|
-
|
|
17423
|
-
|
|
17143
|
+
const ackProbe = process.env.TMUX && AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
17144
|
+
const ackDecision = decideAckReaction({
|
|
17145
|
+
hasTarget: Boolean(chatId && messageId),
|
|
17146
|
+
integrationReady: Boolean(BOT_TOKEN),
|
|
17147
|
+
tmux: ackProbe.tmux,
|
|
17148
|
+
claude: ackProbe.claude,
|
|
17149
|
+
withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
|
|
17150
|
+
oldestPendingAgeMs: oldestPendingMarkerAgeMs(PENDING_INBOUND_DIR)
|
|
17151
|
+
});
|
|
17152
|
+
if (ackDecision === "ack") {
|
|
17153
|
+
void setMessageReaction(chatId, messageId, ACK_EMOJI);
|
|
17154
|
+
} else if (ackDecision === "undeliverable") {
|
|
17155
|
+
void notifyUndeliverable(chatId, messageId);
|
|
17156
|
+
}
|
|
17157
|
+
trackPendingMessage(chatId, messageId, msg.chat.type, ackDecision === "undeliverable");
|
|
17424
17158
|
const fileMeta = [];
|
|
17425
17159
|
for (const attachment of classifiedAttachments) {
|
|
17426
17160
|
if (attachment.kind === "image") {
|
|
@@ -17546,3 +17280,7 @@ pollLoop().catch((err) => {
|
|
|
17546
17280
|
);
|
|
17547
17281
|
process.exit(1);
|
|
17548
17282
|
});
|
|
17283
|
+
export {
|
|
17284
|
+
__resetBackOnlineNoticeThrottle,
|
|
17285
|
+
__resetUndeliverableNoticeThrottle
|
|
17286
|
+
};
|