@integrity-labs/agt-cli 0.27.14 → 0.27.16
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 +3 -3
- package/dist/{chunk-HKZFMGYE.js → chunk-6D55CCVZ.js} +2 -38
- package/dist/chunk-6D55CCVZ.js.map +1 -0
- package/dist/{chunk-5ZUNHYKV.js → chunk-F4NG4EXD.js} +36 -27
- package/dist/chunk-F4NG4EXD.js.map +1 -0
- package/dist/{claude-pair-runtime-ISBA6RDU.js → claude-pair-runtime-OBAJZDXK.js} +2 -2
- package/dist/lib/manager-worker.js +6 -6
- package/dist/mcp/direct-chat-channel.js +1 -462
- package/dist/mcp/slack-channel.js +176 -558
- package/dist/mcp/telegram-channel.js +188 -501
- package/dist/{persistent-session-N6SYAERB.js → persistent-session-SBSOZG74.js} +2 -2
- package/dist/{responsiveness-probe-GPRQBBZG.js → responsiveness-probe-DU4IJ2RZ.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-5ZUNHYKV.js.map +0 -1
- package/dist/chunk-HKZFMGYE.js.map +0 -1
- /package/dist/{claude-pair-runtime-ISBA6RDU.js.map → claude-pair-runtime-OBAJZDXK.js.map} +0 -0
- /package/dist/{persistent-session-N6SYAERB.js.map → persistent-session-SBSOZG74.js.map} +0 -0
- /package/dist/{responsiveness-probe-GPRQBBZG.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,109 @@ 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 oldestPendingMarkerAgeMs(dir, now = Date.now()) {
|
|
15521
|
+
if (!dir) return null;
|
|
15522
|
+
let names;
|
|
15523
|
+
try {
|
|
15524
|
+
names = readdirSync(dir);
|
|
15525
|
+
} catch {
|
|
15526
|
+
return null;
|
|
15527
|
+
}
|
|
15528
|
+
let oldest = null;
|
|
15529
|
+
for (const name of names) {
|
|
15530
|
+
if (!name.endsWith(".json")) continue;
|
|
15531
|
+
let receivedAt;
|
|
15532
|
+
try {
|
|
15533
|
+
const raw = JSON.parse(readFileSync2(join3(dir, name), "utf-8"));
|
|
15534
|
+
receivedAt = raw.received_at;
|
|
15535
|
+
} catch {
|
|
15536
|
+
continue;
|
|
15537
|
+
}
|
|
15538
|
+
if (!receivedAt) continue;
|
|
15539
|
+
const t = Date.parse(receivedAt);
|
|
15540
|
+
if (Number.isNaN(t)) continue;
|
|
15541
|
+
const age = now - t;
|
|
15542
|
+
if (age < 0) continue;
|
|
15543
|
+
if (oldest == null || age > oldest) oldest = age;
|
|
15544
|
+
}
|
|
15545
|
+
return oldest;
|
|
15546
|
+
}
|
|
15547
|
+
|
|
15548
|
+
// src/session-probe-runtime.ts
|
|
15549
|
+
import { execFileSync } from "child_process";
|
|
15550
|
+
function agentTmuxSessionName(codeName) {
|
|
15551
|
+
return `agt-${codeName}`;
|
|
15552
|
+
}
|
|
15553
|
+
function escapePgrepRegex(value) {
|
|
15554
|
+
return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
|
|
15555
|
+
}
|
|
15556
|
+
function probeClaudeProcessInTmux(tmuxSession) {
|
|
15557
|
+
const escapedSession = escapePgrepRegex(tmuxSession);
|
|
15558
|
+
const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
|
|
15559
|
+
try {
|
|
15560
|
+
const out = execFileSync("pgrep", ["-f", "--", pattern], {
|
|
15561
|
+
encoding: "utf-8",
|
|
15562
|
+
timeout: 3e3
|
|
15563
|
+
}).trim();
|
|
15564
|
+
return out.length > 0 ? "alive" : "dead";
|
|
15565
|
+
} catch (err) {
|
|
15566
|
+
const e = err;
|
|
15567
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
15568
|
+
return e?.status === 1 ? "dead" : "unknown";
|
|
15569
|
+
}
|
|
15570
|
+
}
|
|
15571
|
+
function probeTmuxSession(tmuxSession) {
|
|
15572
|
+
try {
|
|
15573
|
+
execFileSync("tmux", ["has-session", "-t", tmuxSession], {
|
|
15574
|
+
stdio: "ignore",
|
|
15575
|
+
timeout: 3e3
|
|
15576
|
+
});
|
|
15577
|
+
return "alive";
|
|
15578
|
+
} catch (err) {
|
|
15579
|
+
const e = err;
|
|
15580
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
15581
|
+
return "dead";
|
|
15582
|
+
}
|
|
15583
|
+
}
|
|
15584
|
+
function probeAgentSession(codeName) {
|
|
15585
|
+
const session = agentTmuxSessionName(codeName);
|
|
15586
|
+
const tmux = probeTmuxSession(session);
|
|
15587
|
+
const claude = tmux === "alive" ? probeClaudeProcessInTmux(session) : tmux;
|
|
15588
|
+
return { tmux, claude };
|
|
15589
|
+
}
|
|
15590
|
+
var probeCache = /* @__PURE__ */ new Map();
|
|
15591
|
+
var SESSION_PROBE_TTL_MS = 15e3;
|
|
15592
|
+
function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = Date.now()) {
|
|
15593
|
+
const cached2 = probeCache.get(codeName);
|
|
15594
|
+
if (cached2 && now - cached2.at < ttlMs) return cached2.value;
|
|
15595
|
+
const value = probeAgentSession(codeName);
|
|
15596
|
+
probeCache.set(codeName, { at: now, value });
|
|
15597
|
+
return value;
|
|
15598
|
+
}
|
|
15599
|
+
|
|
15928
15600
|
// src/telegram-channel.ts
|
|
15929
15601
|
function redactId(id) {
|
|
15930
15602
|
return createHash("sha256").update(String(id)).digest("hex").slice(0, 8);
|
|
15931
15603
|
}
|
|
15932
15604
|
var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
|
15933
15605
|
var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
|
|
15934
|
-
var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ?
|
|
15606
|
+
var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
15935
15607
|
var AGT_HOST = process.env.AGT_HOST ?? null;
|
|
15936
15608
|
var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
|
|
15937
15609
|
var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
|
|
@@ -16015,9 +15687,9 @@ if (!BOT_TOKEN) {
|
|
|
16015
15687
|
var stderrLogStream = null;
|
|
16016
15688
|
if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
|
|
16017
15689
|
try {
|
|
16018
|
-
const logDir =
|
|
15690
|
+
const logDir = join4(homedir2(), ".augmented", AGENT_CODE_NAME);
|
|
16019
15691
|
mkdirSync3(logDir, { recursive: true });
|
|
16020
|
-
stderrLogStream = createWriteStream(
|
|
15692
|
+
stderrLogStream = createWriteStream(join4(logDir, "telegram-channel-stderr.log"), {
|
|
16021
15693
|
flags: "a",
|
|
16022
15694
|
mode: 384
|
|
16023
15695
|
});
|
|
@@ -16098,7 +15770,42 @@ async function setMessageReaction(chatId, messageId, emoji2) {
|
|
|
16098
15770
|
);
|
|
16099
15771
|
}
|
|
16100
15772
|
}
|
|
16101
|
-
var
|
|
15773
|
+
var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
|
|
15774
|
+
var lastUndeliverableNoticeAt = /* @__PURE__ */ new Map();
|
|
15775
|
+
async function notifyUndeliverable(chatId, messageId) {
|
|
15776
|
+
const key2 = String(chatId);
|
|
15777
|
+
const now = Date.now();
|
|
15778
|
+
const last = lastUndeliverableNoticeAt.get(key2);
|
|
15779
|
+
if (last != null && now - last < UNDELIVERABLE_NOTICE_THROTTLE_MS) return;
|
|
15780
|
+
lastUndeliverableNoticeAt.set(key2, now);
|
|
15781
|
+
try {
|
|
15782
|
+
const resp = await telegramApiCall(
|
|
15783
|
+
"sendMessage",
|
|
15784
|
+
{
|
|
15785
|
+
chat_id: chatId,
|
|
15786
|
+
text: "\u26A0\uFE0F I can't respond right now \u2014 I'll follow up once I'm back.",
|
|
15787
|
+
reply_to_message_id: Number(messageId),
|
|
15788
|
+
allow_sending_without_reply: true
|
|
15789
|
+
},
|
|
15790
|
+
1e4
|
|
15791
|
+
);
|
|
15792
|
+
if (!resp.ok) {
|
|
15793
|
+
process.stderr.write(
|
|
15794
|
+
`telegram-channel(${AGENT_CODE_NAME}): undeliverable notice failed (chat=${redactId(chatId)}): ${resp.description ?? "unknown"}
|
|
15795
|
+
`
|
|
15796
|
+
);
|
|
15797
|
+
}
|
|
15798
|
+
} catch (err) {
|
|
15799
|
+
process.stderr.write(
|
|
15800
|
+
`telegram-channel(${AGENT_CODE_NAME}): undeliverable notice error: ${err.message}
|
|
15801
|
+
`
|
|
15802
|
+
);
|
|
15803
|
+
}
|
|
15804
|
+
}
|
|
15805
|
+
function __resetUndeliverableNoticeThrottle() {
|
|
15806
|
+
lastUndeliverableNoticeAt.clear();
|
|
15807
|
+
}
|
|
15808
|
+
var RESTART_FLAGS_DIR = join4(homedir2(), ".augmented", "restart-flags");
|
|
16102
15809
|
function buildTelegramHelpMessage(codeName) {
|
|
16103
15810
|
return [
|
|
16104
15811
|
`\u{1F916} *Available commands for \`${codeName}\`*`,
|
|
@@ -16138,14 +15845,14 @@ async function handleRestartCommand(opts) {
|
|
|
16138
15845
|
if (!existsSync2(RESTART_FLAGS_DIR)) {
|
|
16139
15846
|
mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
|
|
16140
15847
|
}
|
|
16141
|
-
const flagPath =
|
|
15848
|
+
const flagPath = join4(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
|
|
16142
15849
|
const flag = {
|
|
16143
15850
|
codeName: AGENT_CODE_NAME,
|
|
16144
15851
|
source: "telegram",
|
|
16145
15852
|
ts: Date.now(),
|
|
16146
15853
|
reply: { chat_id: opts.chatId, message_id: opts.messageId }
|
|
16147
15854
|
};
|
|
16148
|
-
const tmpPath = `${flagPath}.${process.pid}.${
|
|
15855
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
16149
15856
|
writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
16150
15857
|
renameSync3(tmpPath, flagPath);
|
|
16151
15858
|
process.stderr.write(
|
|
@@ -16254,16 +15961,16 @@ async function classifyRestartCommand(text) {
|
|
|
16254
15961
|
if (!ours) return "verification_failed";
|
|
16255
15962
|
return target === ours ? "act" : "ignore";
|
|
16256
15963
|
}
|
|
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 ?
|
|
15964
|
+
var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
15965
|
+
var PENDING_INBOUND_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-pending-inbound") : null;
|
|
15966
|
+
var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-recovery-outbox") : null;
|
|
16260
15967
|
function safeMarkerName(chatId, messageId) {
|
|
16261
15968
|
const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
16262
15969
|
return `${safe(chatId)}__${safe(messageId)}.json`;
|
|
16263
15970
|
}
|
|
16264
15971
|
function pendingInboundPath(chatId, messageId) {
|
|
16265
15972
|
if (!PENDING_INBOUND_DIR) return null;
|
|
16266
|
-
return
|
|
15973
|
+
return join4(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
|
|
16267
15974
|
}
|
|
16268
15975
|
function writePendingInboundMarker(chatId, messageId, chatType) {
|
|
16269
15976
|
const path = pendingInboundPath(chatId, messageId);
|
|
@@ -16307,10 +16014,10 @@ function nextRetryName(filename) {
|
|
|
16307
16014
|
async function processRecoveryOutboxFile(filename) {
|
|
16308
16015
|
if (!RECOVERY_OUTBOX_DIR) return;
|
|
16309
16016
|
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
16310
|
-
const fullPath =
|
|
16017
|
+
const fullPath = join4(RECOVERY_OUTBOX_DIR, filename);
|
|
16311
16018
|
let payload;
|
|
16312
16019
|
try {
|
|
16313
|
-
const raw =
|
|
16020
|
+
const raw = readFileSync3(fullPath, "utf-8");
|
|
16314
16021
|
payload = JSON.parse(raw);
|
|
16315
16022
|
} catch (err) {
|
|
16316
16023
|
process.stderr.write(
|
|
@@ -16373,7 +16080,7 @@ async function processRecoveryOutboxFile(filename) {
|
|
|
16373
16080
|
const next = nextRetryName(filename);
|
|
16374
16081
|
if (next) {
|
|
16375
16082
|
try {
|
|
16376
|
-
renameSync3(fullPath,
|
|
16083
|
+
renameSync3(fullPath, join4(RECOVERY_OUTBOX_DIR, next.next));
|
|
16377
16084
|
if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
|
|
16378
16085
|
process.stderr.write(
|
|
16379
16086
|
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
@@ -16404,7 +16111,7 @@ function scanRecoveryRetries() {
|
|
|
16404
16111
|
if (!RECOVERY_OUTBOX_DIR) return;
|
|
16405
16112
|
let entries;
|
|
16406
16113
|
try {
|
|
16407
|
-
entries =
|
|
16114
|
+
entries = readdirSync2(RECOVERY_OUTBOX_DIR);
|
|
16408
16115
|
} catch {
|
|
16409
16116
|
return;
|
|
16410
16117
|
}
|
|
@@ -16413,7 +16120,7 @@ function scanRecoveryRetries() {
|
|
|
16413
16120
|
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
16414
16121
|
let mtimeMs;
|
|
16415
16122
|
try {
|
|
16416
|
-
mtimeMs = statSync(
|
|
16123
|
+
mtimeMs = statSync(join4(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
16417
16124
|
} catch {
|
|
16418
16125
|
continue;
|
|
16419
16126
|
}
|
|
@@ -16434,7 +16141,7 @@ function startRecoveryOutboxWatcher() {
|
|
|
16434
16141
|
return;
|
|
16435
16142
|
}
|
|
16436
16143
|
try {
|
|
16437
|
-
for (const f of
|
|
16144
|
+
for (const f of readdirSync2(RECOVERY_OUTBOX_DIR)) {
|
|
16438
16145
|
if (isFirstAttemptOutboxFile(f)) void processRecoveryOutboxFile(f);
|
|
16439
16146
|
}
|
|
16440
16147
|
} catch {
|
|
@@ -16443,7 +16150,7 @@ function startRecoveryOutboxWatcher() {
|
|
|
16443
16150
|
const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
16444
16151
|
if (event !== "rename" || !filename) return;
|
|
16445
16152
|
if (!isFirstAttemptOutboxFile(filename)) return;
|
|
16446
|
-
if (existsSync2(
|
|
16153
|
+
if (existsSync2(join4(RECOVERY_OUTBOX_DIR, filename))) {
|
|
16447
16154
|
void processRecoveryOutboxFile(filename);
|
|
16448
16155
|
}
|
|
16449
16156
|
});
|
|
@@ -16467,7 +16174,7 @@ function sweepTelegramStaleMarkersOnBoot() {
|
|
|
16467
16174
|
if (!existsSync2(PENDING_INBOUND_DIR)) return;
|
|
16468
16175
|
let filenames;
|
|
16469
16176
|
try {
|
|
16470
|
-
filenames =
|
|
16177
|
+
filenames = readdirSync2(PENDING_INBOUND_DIR);
|
|
16471
16178
|
} catch (err) {
|
|
16472
16179
|
process.stderr.write(
|
|
16473
16180
|
`telegram-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
|
|
@@ -16480,10 +16187,10 @@ function sweepTelegramStaleMarkersOnBoot() {
|
|
|
16480
16187
|
for (const filename of filenames) {
|
|
16481
16188
|
if (!filename.endsWith(".json")) continue;
|
|
16482
16189
|
if (filename.endsWith(".tmp")) continue;
|
|
16483
|
-
const fullPath =
|
|
16190
|
+
const fullPath = join4(PENDING_INBOUND_DIR, filename);
|
|
16484
16191
|
let marker;
|
|
16485
16192
|
try {
|
|
16486
|
-
marker = JSON.parse(
|
|
16193
|
+
marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
|
|
16487
16194
|
} catch (err) {
|
|
16488
16195
|
process.stderr.write(
|
|
16489
16196
|
`telegram-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactId(filename)}: ${err.message}
|
|
@@ -16532,7 +16239,7 @@ function clearPendingMessage(chatId, messageId) {
|
|
|
16532
16239
|
const prefix = `${safeChatId}__`;
|
|
16533
16240
|
let filenames;
|
|
16534
16241
|
try {
|
|
16535
|
-
filenames =
|
|
16242
|
+
filenames = readdirSync2(PENDING_INBOUND_DIR);
|
|
16536
16243
|
} catch {
|
|
16537
16244
|
return;
|
|
16538
16245
|
}
|
|
@@ -16540,7 +16247,7 @@ function clearPendingMessage(chatId, messageId) {
|
|
|
16540
16247
|
if (!filename.startsWith(prefix)) continue;
|
|
16541
16248
|
if (!filename.endsWith(".json")) continue;
|
|
16542
16249
|
try {
|
|
16543
|
-
unlinkSync3(
|
|
16250
|
+
unlinkSync3(join4(PENDING_INBOUND_DIR, filename));
|
|
16544
16251
|
} catch {
|
|
16545
16252
|
}
|
|
16546
16253
|
}
|
|
@@ -16567,46 +16274,12 @@ var mcp = new Server(
|
|
|
16567
16274
|
"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
16275
|
'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
16276
|
"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
|
-
|
|
16277
|
+
`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
16278
|
].join(" ")
|
|
16572
16279
|
}
|
|
16573
16280
|
);
|
|
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
16281
|
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16608
16282
|
tools: [
|
|
16609
|
-
...progressRegistry.getDefinitions(),
|
|
16610
16283
|
{
|
|
16611
16284
|
name: "channel_request_input",
|
|
16612
16285
|
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 +16371,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
16698
16371
|
},
|
|
16699
16372
|
{
|
|
16700
16373
|
name: "telegram.react",
|
|
16701
|
-
description:
|
|
16374
|
+
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
16375
|
inputSchema: {
|
|
16703
16376
|
type: "object",
|
|
16704
16377
|
properties: {
|
|
@@ -16725,8 +16398,6 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16725
16398
|
if (isImpersonating() && !channelsEnabledOverride() && TELEGRAM_EGRESS_TOOLS.has(name)) {
|
|
16726
16399
|
return buildImpersonationRefusal(name);
|
|
16727
16400
|
}
|
|
16728
|
-
const progressResult = await progressRegistry.handle(name, args ?? {});
|
|
16729
|
-
if (progressResult !== void 0) return progressResult;
|
|
16730
16401
|
if (name === "channel_request_input") {
|
|
16731
16402
|
return handleChannelRequestInput(args ?? {});
|
|
16732
16403
|
}
|
|
@@ -16926,12 +16597,12 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16926
16597
|
function channelRequestInputAvailable() {
|
|
16927
16598
|
return Boolean(AGT_HOST && AGT_API_KEY && AGT_AGENT_ID);
|
|
16928
16599
|
}
|
|
16929
|
-
function
|
|
16600
|
+
function errResult(text) {
|
|
16930
16601
|
return { content: [{ type: "text", text }], isError: true };
|
|
16931
16602
|
}
|
|
16932
16603
|
async function handleChannelRequestInput(args) {
|
|
16933
16604
|
if (!channelRequestInputAvailable()) {
|
|
16934
|
-
return
|
|
16605
|
+
return errResult(
|
|
16935
16606
|
"channel_request_input is disabled \u2014 missing AGT_HOST / AGT_API_KEY / AGT_AGENT_ID env wiring."
|
|
16936
16607
|
);
|
|
16937
16608
|
}
|
|
@@ -16939,12 +16610,12 @@ async function handleChannelRequestInput(args) {
|
|
|
16939
16610
|
const { apiCall: apiCall2, waitForResolution: waitForResolution2, validateAskUserOptions: validateAskUserOptions2 } = runtime;
|
|
16940
16611
|
const { thread_id, prompt_text, options, schema, request_id, timeout_seconds } = args;
|
|
16941
16612
|
if (typeof thread_id !== "string" || !thread_id) {
|
|
16942
|
-
return
|
|
16613
|
+
return errResult(
|
|
16943
16614
|
"thread_id is required (Telegram shape: `<chat_id>` or `<chat_id>:<reply_to_message_id>`)"
|
|
16944
16615
|
);
|
|
16945
16616
|
}
|
|
16946
16617
|
if (typeof prompt_text !== "string" || !prompt_text) {
|
|
16947
|
-
return
|
|
16618
|
+
return errResult("prompt_text is required");
|
|
16948
16619
|
}
|
|
16949
16620
|
const optsValidation = validateAskUserOptions2(options, {
|
|
16950
16621
|
maxOptions: 4,
|
|
@@ -16952,27 +16623,27 @@ async function handleChannelRequestInput(args) {
|
|
|
16952
16623
|
maxValueChars: 2e3
|
|
16953
16624
|
});
|
|
16954
16625
|
if (!optsValidation.ok) {
|
|
16955
|
-
return
|
|
16626
|
+
return errResult(
|
|
16956
16627
|
`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
|
|
16957
16628
|
);
|
|
16958
16629
|
}
|
|
16959
16630
|
const optionsArr = options;
|
|
16960
16631
|
if (optionsArr.length < 2) {
|
|
16961
|
-
return
|
|
16632
|
+
return errResult(
|
|
16962
16633
|
"options must contain at least 2 entries (this is a picker, not a notification)"
|
|
16963
16634
|
);
|
|
16964
16635
|
}
|
|
16965
16636
|
if (!schema || typeof schema.type !== "string") {
|
|
16966
|
-
return
|
|
16637
|
+
return errResult("schema is required with shape { type: 'yes_no' | 'choice' }");
|
|
16967
16638
|
}
|
|
16968
16639
|
if (schema.type !== "yes_no" && schema.type !== "choice") {
|
|
16969
|
-
return
|
|
16640
|
+
return errResult("schema.type must be 'yes_no' or 'choice'");
|
|
16970
16641
|
}
|
|
16971
16642
|
if (schema.type === "yes_no" && optionsArr.length !== 2) {
|
|
16972
|
-
return
|
|
16643
|
+
return errResult("schema.type='yes_no' requires exactly 2 options");
|
|
16973
16644
|
}
|
|
16974
16645
|
if (request_id !== void 0 && (typeof request_id !== "string" || !request_id)) {
|
|
16975
|
-
return
|
|
16646
|
+
return errResult("request_id must be a non-empty string when provided");
|
|
16976
16647
|
}
|
|
16977
16648
|
const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
|
|
16978
16649
|
const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
|
|
@@ -16994,10 +16665,10 @@ async function handleChannelRequestInput(args) {
|
|
|
16994
16665
|
dispatchPayload = await res.json().catch(() => ({}));
|
|
16995
16666
|
if (!res.ok || !dispatchPayload.ok || !dispatchPayload.callback_id) {
|
|
16996
16667
|
const reason = dispatchPayload.reason ?? `http_${res.status}`;
|
|
16997
|
-
return
|
|
16668
|
+
return errResult(`channel_request_input dispatch failed: ${reason}`);
|
|
16998
16669
|
}
|
|
16999
16670
|
} catch (err) {
|
|
17000
|
-
return
|
|
16671
|
+
return errResult(
|
|
17001
16672
|
`channel_request_input dispatch failed: ${err.message}`
|
|
17002
16673
|
);
|
|
17003
16674
|
}
|
|
@@ -17419,7 +17090,20 @@ async function pollLoop() {
|
|
|
17419
17090
|
}
|
|
17420
17091
|
}
|
|
17421
17092
|
const messageId = String(msg.message_id);
|
|
17422
|
-
|
|
17093
|
+
const ackProbe = process.env.TMUX && AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
17094
|
+
const ackDecision = decideAckReaction({
|
|
17095
|
+
hasTarget: Boolean(chatId && messageId),
|
|
17096
|
+
integrationReady: Boolean(BOT_TOKEN),
|
|
17097
|
+
tmux: ackProbe.tmux,
|
|
17098
|
+
claude: ackProbe.claude,
|
|
17099
|
+
withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
|
|
17100
|
+
oldestPendingAgeMs: oldestPendingMarkerAgeMs(PENDING_INBOUND_DIR)
|
|
17101
|
+
});
|
|
17102
|
+
if (ackDecision === "ack") {
|
|
17103
|
+
void setMessageReaction(chatId, messageId, ACK_EMOJI);
|
|
17104
|
+
} else if (ackDecision === "undeliverable") {
|
|
17105
|
+
void notifyUndeliverable(chatId, messageId);
|
|
17106
|
+
}
|
|
17423
17107
|
trackPendingMessage(chatId, messageId, msg.chat.type);
|
|
17424
17108
|
const fileMeta = [];
|
|
17425
17109
|
for (const attachment of classifiedAttachments) {
|
|
@@ -17546,3 +17230,6 @@ pollLoop().catch((err) => {
|
|
|
17546
17230
|
);
|
|
17547
17231
|
process.exit(1);
|
|
17548
17232
|
});
|
|
17233
|
+
export {
|
|
17234
|
+
__resetUndeliverableNoticeThrottle
|
|
17235
|
+
};
|