@integrity-labs/agt-cli 0.27.15 → 0.27.17
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-LJEV2QHN.js → chunk-QQVNHJ76.js} +30 -40
- package/dist/chunk-QQVNHJ76.js.map +1 -0
- package/dist/lib/manager-worker.js +9 -5
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +1 -462
- package/dist/mcp/slack-channel.js +30 -518
- package/dist/mcp/telegram-channel.js +14 -474
- package/package.json +1 -1
- package/dist/chunk-LJEV2QHN.js.map +0 -1
|
@@ -14447,468 +14447,6 @@ async function isThreadKilled(opts) {
|
|
|
14447
14447
|
}
|
|
14448
14448
|
}
|
|
14449
14449
|
|
|
14450
|
-
// src/slack-progress.ts
|
|
14451
|
-
var MODE_EMOJI = {
|
|
14452
|
-
thinking: "\u{1F4AD}",
|
|
14453
|
-
working: "\u{1F6E0}\uFE0F",
|
|
14454
|
-
waiting: "\u23F3"
|
|
14455
|
-
};
|
|
14456
|
-
var TERMINAL_EMOJI = {
|
|
14457
|
-
completed: "\u2705",
|
|
14458
|
-
failed: "\u274C"
|
|
14459
|
-
};
|
|
14460
|
-
function formatWallClock(ms, timeZone) {
|
|
14461
|
-
const fmt = new Intl.DateTimeFormat("en-GB", {
|
|
14462
|
-
hour: "2-digit",
|
|
14463
|
-
minute: "2-digit",
|
|
14464
|
-
second: "2-digit",
|
|
14465
|
-
hour12: false,
|
|
14466
|
-
timeZone,
|
|
14467
|
-
timeZoneName: "short"
|
|
14468
|
-
});
|
|
14469
|
-
const parts = fmt.formatToParts(new Date(ms));
|
|
14470
|
-
const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
|
|
14471
|
-
return `${get("hour")}:${get("minute")}:${get("second")} ${get("timeZoneName")}`;
|
|
14472
|
-
}
|
|
14473
|
-
function renderHeaderLine(state) {
|
|
14474
|
-
if (state.terminal) {
|
|
14475
|
-
return `${TERMINAL_EMOJI[state.terminal.kind]} *${state.terminal.kind === "completed" ? "Done" : "Failed"}* \u2014 ${state.initialLabel}`;
|
|
14476
|
-
}
|
|
14477
|
-
return `${MODE_EMOJI[state.mode]} *Working on:* ${state.initialLabel}`;
|
|
14478
|
-
}
|
|
14479
|
-
function renderStepLine(state) {
|
|
14480
|
-
if (state.terminal) {
|
|
14481
|
-
return state.terminal.message ? state.terminal.message : void 0;
|
|
14482
|
-
}
|
|
14483
|
-
const parts = [];
|
|
14484
|
-
if (state.stepNumber) {
|
|
14485
|
-
if (state.stepNumber.total != null) {
|
|
14486
|
-
parts.push(`*Step ${state.stepNumber.current} of ${state.stepNumber.total}*`);
|
|
14487
|
-
} else {
|
|
14488
|
-
parts.push(`*Step ${state.stepNumber.current}*`);
|
|
14489
|
-
}
|
|
14490
|
-
}
|
|
14491
|
-
if (state.stepLabel) parts.push(state.stepLabel);
|
|
14492
|
-
return parts.length > 0 ? parts.join(": ") : void 0;
|
|
14493
|
-
}
|
|
14494
|
-
function renderProgressBlocks(state) {
|
|
14495
|
-
const header = renderHeaderLine(state);
|
|
14496
|
-
const step = renderStepLine(state);
|
|
14497
|
-
const footer = state.terminal ? `_${state.terminal.kind === "completed" ? "completed" : "failed"}: ${formatWallClock(state.lastChangeAt)}_` : `_last update: ${formatWallClock(state.lastChangeAt)}_`;
|
|
14498
|
-
const lines = [header];
|
|
14499
|
-
if (step) lines.push(step);
|
|
14500
|
-
if (state.detail) lines.push(`_${state.detail}_`);
|
|
14501
|
-
lines.push(footer);
|
|
14502
|
-
const fallbackText = state.terminal ? `${state.terminal.kind === "completed" ? "Done" : "Failed"} \u2014 ${state.initialLabel}` : `Working on: ${state.initialLabel}`;
|
|
14503
|
-
return {
|
|
14504
|
-
text: fallbackText,
|
|
14505
|
-
blocks: [
|
|
14506
|
-
{
|
|
14507
|
-
type: "section",
|
|
14508
|
-
text: { type: "mrkdwn", text: lines.join("\n") }
|
|
14509
|
-
}
|
|
14510
|
-
]
|
|
14511
|
-
};
|
|
14512
|
-
}
|
|
14513
|
-
var SLACK_POST_URL = "https://slack.com/api/chat.postMessage";
|
|
14514
|
-
var SLACK_UPDATE_URL = "https://slack.com/api/chat.update";
|
|
14515
|
-
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
14516
|
-
var SlackApiError = class extends Error {
|
|
14517
|
-
constructor(endpoint, slackError, status) {
|
|
14518
|
-
super(`Slack ${endpoint} failed: ${slackError} (status=${status})`);
|
|
14519
|
-
this.endpoint = endpoint;
|
|
14520
|
-
this.slackError = slackError;
|
|
14521
|
-
this.status = status;
|
|
14522
|
-
this.name = "SlackApiError";
|
|
14523
|
-
}
|
|
14524
|
-
};
|
|
14525
|
-
function createSlackProgressFlush(opts) {
|
|
14526
|
-
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
14527
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
14528
|
-
let anchorTs = null;
|
|
14529
|
-
async function slackCall(endpoint, body) {
|
|
14530
|
-
const url = endpoint === "chat.postMessage" ? SLACK_POST_URL : SLACK_UPDATE_URL;
|
|
14531
|
-
const controller = new AbortController();
|
|
14532
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
14533
|
-
let response;
|
|
14534
|
-
try {
|
|
14535
|
-
response = await fetchImpl(url, {
|
|
14536
|
-
method: "POST",
|
|
14537
|
-
headers: {
|
|
14538
|
-
"Content-Type": "application/json",
|
|
14539
|
-
Authorization: `Bearer ${opts.botToken}`
|
|
14540
|
-
},
|
|
14541
|
-
body: JSON.stringify(body),
|
|
14542
|
-
signal: controller.signal
|
|
14543
|
-
});
|
|
14544
|
-
} finally {
|
|
14545
|
-
clearTimeout(timer);
|
|
14546
|
-
}
|
|
14547
|
-
const parsed = await response.json().catch(() => ({ ok: false, error: "invalid_json" }));
|
|
14548
|
-
if (!parsed.ok) {
|
|
14549
|
-
throw new SlackApiError(endpoint, parsed.error ?? "unknown", response.status);
|
|
14550
|
-
}
|
|
14551
|
-
return parsed;
|
|
14552
|
-
}
|
|
14553
|
-
return async function flush(state) {
|
|
14554
|
-
const payload = renderProgressBlocks(state);
|
|
14555
|
-
if (anchorTs == null) {
|
|
14556
|
-
const body = {
|
|
14557
|
-
channel: opts.channel,
|
|
14558
|
-
text: payload.text,
|
|
14559
|
-
blocks: payload.blocks
|
|
14560
|
-
};
|
|
14561
|
-
if (opts.threadTs) body.thread_ts = opts.threadTs;
|
|
14562
|
-
try {
|
|
14563
|
-
const res = await slackCall("chat.postMessage", body);
|
|
14564
|
-
if (!res.ts) {
|
|
14565
|
-
throw new SlackApiError(
|
|
14566
|
-
"chat.postMessage",
|
|
14567
|
-
"missing ts in successful response",
|
|
14568
|
-
200
|
|
14569
|
-
);
|
|
14570
|
-
}
|
|
14571
|
-
anchorTs = res.ts;
|
|
14572
|
-
opts.onAnchorPosted?.(res.ts);
|
|
14573
|
-
} catch (err) {
|
|
14574
|
-
throw err;
|
|
14575
|
-
}
|
|
14576
|
-
return;
|
|
14577
|
-
}
|
|
14578
|
-
try {
|
|
14579
|
-
await slackCall("chat.update", {
|
|
14580
|
-
channel: opts.channel,
|
|
14581
|
-
ts: anchorTs,
|
|
14582
|
-
text: payload.text,
|
|
14583
|
-
blocks: payload.blocks
|
|
14584
|
-
});
|
|
14585
|
-
} catch (err) {
|
|
14586
|
-
if (opts.onApiError && err instanceof SlackApiError) {
|
|
14587
|
-
await opts.onApiError(err);
|
|
14588
|
-
return;
|
|
14589
|
-
}
|
|
14590
|
-
throw err;
|
|
14591
|
-
}
|
|
14592
|
-
};
|
|
14593
|
-
}
|
|
14594
|
-
|
|
14595
|
-
// src/progress-tools.ts
|
|
14596
|
-
import { randomUUID } from "crypto";
|
|
14597
|
-
|
|
14598
|
-
// src/progress-handle.ts
|
|
14599
|
-
async function startProgressHandle(opts) {
|
|
14600
|
-
const now = opts.now ?? (() => Date.now());
|
|
14601
|
-
const setTimer = opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
14602
|
-
const clearTimer = opts.clearTimer ?? ((id) => clearTimeout(id));
|
|
14603
|
-
const debounceMs = opts.debounceMs ?? 1e3;
|
|
14604
|
-
const onPostTerminalUpdate = opts.onPostTerminalUpdate ?? defaultPostTerminalWarn;
|
|
14605
|
-
const state = {
|
|
14606
|
-
initialLabel: opts.initialLabel,
|
|
14607
|
-
mode: opts.initialMode ?? "working",
|
|
14608
|
-
lastChangeAt: now()
|
|
14609
|
-
};
|
|
14610
|
-
let lastFlushAt = 0;
|
|
14611
|
-
let pendingTimer = null;
|
|
14612
|
-
let inFlight = null;
|
|
14613
|
-
let terminal = false;
|
|
14614
|
-
async function doFlush() {
|
|
14615
|
-
while (inFlight) await inFlight;
|
|
14616
|
-
if (pendingTimer != null) {
|
|
14617
|
-
clearTimer(pendingTimer);
|
|
14618
|
-
pendingTimer = null;
|
|
14619
|
-
}
|
|
14620
|
-
lastFlushAt = now();
|
|
14621
|
-
const promise = opts.flush(snapshotState());
|
|
14622
|
-
inFlight = promise.then(
|
|
14623
|
-
() => {
|
|
14624
|
-
inFlight = null;
|
|
14625
|
-
},
|
|
14626
|
-
(err) => {
|
|
14627
|
-
inFlight = null;
|
|
14628
|
-
throw err;
|
|
14629
|
-
}
|
|
14630
|
-
);
|
|
14631
|
-
await inFlight;
|
|
14632
|
-
}
|
|
14633
|
-
function snapshotState() {
|
|
14634
|
-
return {
|
|
14635
|
-
initialLabel: state.initialLabel,
|
|
14636
|
-
mode: state.mode,
|
|
14637
|
-
stepLabel: state.stepLabel,
|
|
14638
|
-
stepNumber: state.stepNumber ? { ...state.stepNumber } : void 0,
|
|
14639
|
-
detail: state.detail,
|
|
14640
|
-
lastChangeAt: state.lastChangeAt,
|
|
14641
|
-
terminal: state.terminal ? { ...state.terminal } : void 0
|
|
14642
|
-
};
|
|
14643
|
-
}
|
|
14644
|
-
function applyUpdate(next) {
|
|
14645
|
-
if (next.mode !== void 0) state.mode = next.mode;
|
|
14646
|
-
if (next.stepLabel !== void 0) state.stepLabel = next.stepLabel;
|
|
14647
|
-
if (next.stepNumber !== void 0) state.stepNumber = { ...next.stepNumber };
|
|
14648
|
-
if (next.detail !== void 0) state.detail = next.detail;
|
|
14649
|
-
state.lastChangeAt = now();
|
|
14650
|
-
}
|
|
14651
|
-
async function scheduleOrFlush() {
|
|
14652
|
-
const elapsed = now() - lastFlushAt;
|
|
14653
|
-
if (elapsed >= debounceMs) {
|
|
14654
|
-
await doFlush();
|
|
14655
|
-
return;
|
|
14656
|
-
}
|
|
14657
|
-
if (pendingTimer != null) return;
|
|
14658
|
-
const remaining = debounceMs - elapsed;
|
|
14659
|
-
pendingTimer = setTimer(() => {
|
|
14660
|
-
pendingTimer = null;
|
|
14661
|
-
void doFlush().catch(() => {
|
|
14662
|
-
});
|
|
14663
|
-
}, remaining);
|
|
14664
|
-
}
|
|
14665
|
-
lastFlushAt = now();
|
|
14666
|
-
await opts.flush(snapshotState());
|
|
14667
|
-
return {
|
|
14668
|
-
snapshot: snapshotState,
|
|
14669
|
-
async update(next) {
|
|
14670
|
-
if (terminal) {
|
|
14671
|
-
const peeked = snapshotState();
|
|
14672
|
-
Object.assign(peeked, {
|
|
14673
|
-
mode: next.mode ?? peeked.mode,
|
|
14674
|
-
stepLabel: next.stepLabel ?? peeked.stepLabel,
|
|
14675
|
-
stepNumber: next.stepNumber ?? peeked.stepNumber,
|
|
14676
|
-
detail: next.detail ?? peeked.detail
|
|
14677
|
-
});
|
|
14678
|
-
try {
|
|
14679
|
-
onPostTerminalUpdate(peeked);
|
|
14680
|
-
} catch (err) {
|
|
14681
|
-
console.warn(
|
|
14682
|
-
"[progress-handle] onPostTerminalUpdate threw \u2014 swallowed to keep update() non-throwing:",
|
|
14683
|
-
err
|
|
14684
|
-
);
|
|
14685
|
-
}
|
|
14686
|
-
return;
|
|
14687
|
-
}
|
|
14688
|
-
applyUpdate(next);
|
|
14689
|
-
await scheduleOrFlush();
|
|
14690
|
-
},
|
|
14691
|
-
async complete(summary) {
|
|
14692
|
-
if (terminal) return;
|
|
14693
|
-
terminal = true;
|
|
14694
|
-
state.terminal = { kind: "completed", message: summary };
|
|
14695
|
-
state.lastChangeAt = now();
|
|
14696
|
-
await doFlush();
|
|
14697
|
-
},
|
|
14698
|
-
async fail(reason) {
|
|
14699
|
-
if (terminal) return;
|
|
14700
|
-
terminal = true;
|
|
14701
|
-
state.terminal = { kind: "failed", message: reason };
|
|
14702
|
-
state.lastChangeAt = now();
|
|
14703
|
-
await doFlush();
|
|
14704
|
-
},
|
|
14705
|
-
isTerminal: () => terminal
|
|
14706
|
-
};
|
|
14707
|
-
}
|
|
14708
|
-
function defaultPostTerminalWarn(state) {
|
|
14709
|
-
console.warn(
|
|
14710
|
-
`[progress-handle] update() called after terminal transition (${state.terminal?.kind}) \u2014 ignored.`
|
|
14711
|
-
);
|
|
14712
|
-
}
|
|
14713
|
-
|
|
14714
|
-
// src/progress-tools.ts
|
|
14715
|
-
var ProgressToolRegistry = class {
|
|
14716
|
-
constructor(opts) {
|
|
14717
|
-
this.opts = opts;
|
|
14718
|
-
this.toolNames = {
|
|
14719
|
-
start: `${opts.prefix}_progress_start`,
|
|
14720
|
-
update: `${opts.prefix}_progress_update`,
|
|
14721
|
-
complete: `${opts.prefix}_progress_complete`,
|
|
14722
|
-
fail: `${opts.prefix}_progress_fail`
|
|
14723
|
-
};
|
|
14724
|
-
}
|
|
14725
|
-
handles = /* @__PURE__ */ new Map();
|
|
14726
|
-
toolNames;
|
|
14727
|
-
/** Tool definitions to splice into the MCP ListToolsRequest response. */
|
|
14728
|
-
getDefinitions() {
|
|
14729
|
-
const { surfaceDescription, startArgsSchema } = this.opts;
|
|
14730
|
-
const debounceMs = this.opts.debounceMs ?? 1e3;
|
|
14731
|
-
const debounceText = debounceMs === 1e3 ? "per second" : `every ${debounceMs}ms`;
|
|
14732
|
-
return [
|
|
14733
|
-
{
|
|
14734
|
-
name: this.toolNames.start,
|
|
14735
|
-
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.`,
|
|
14736
|
-
inputSchema: {
|
|
14737
|
-
type: "object",
|
|
14738
|
-
properties: {
|
|
14739
|
-
label: {
|
|
14740
|
-
type: "string",
|
|
14741
|
-
description: 'Short top-line label for the task (e.g. "diagnose vigil"). Stays constant for the lifetime of this progress anchor.'
|
|
14742
|
-
},
|
|
14743
|
-
initial_mode: {
|
|
14744
|
-
type: "string",
|
|
14745
|
-
enum: ["thinking", "working", "waiting"],
|
|
14746
|
-
description: 'Initial mode emoji \u2014 defaults to "working".'
|
|
14747
|
-
},
|
|
14748
|
-
...startArgsSchema.properties
|
|
14749
|
-
},
|
|
14750
|
-
required: ["label", ...startArgsSchema.required]
|
|
14751
|
-
}
|
|
14752
|
-
},
|
|
14753
|
-
{
|
|
14754
|
-
name: this.toolNames.update,
|
|
14755
|
-
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.`,
|
|
14756
|
-
inputSchema: {
|
|
14757
|
-
type: "object",
|
|
14758
|
-
properties: {
|
|
14759
|
-
progress_id: { type: "string", description: "The progress_id returned from start." },
|
|
14760
|
-
step_label: { type: "string", description: 'Short label for the current step (e.g. "Querying CloudWatch logs").' },
|
|
14761
|
-
step_current: { type: "number", description: "1-based step counter \u2014 current." },
|
|
14762
|
-
step_total: { type: "number", description: "Total steps if known. Omit for open-ended progress." },
|
|
14763
|
-
mode: { type: "string", enum: ["thinking", "working", "waiting"] },
|
|
14764
|
-
detail: { type: "string", description: "Free-text detail line under the step label." }
|
|
14765
|
-
},
|
|
14766
|
-
required: ["progress_id"]
|
|
14767
|
-
}
|
|
14768
|
-
},
|
|
14769
|
-
{
|
|
14770
|
-
name: this.toolNames.complete,
|
|
14771
|
-
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.`,
|
|
14772
|
-
inputSchema: {
|
|
14773
|
-
type: "object",
|
|
14774
|
-
properties: {
|
|
14775
|
-
progress_id: { type: "string" },
|
|
14776
|
-
summary: { type: "string", description: "Optional one-line summary of what was achieved." }
|
|
14777
|
-
},
|
|
14778
|
-
required: ["progress_id"]
|
|
14779
|
-
}
|
|
14780
|
-
},
|
|
14781
|
-
{
|
|
14782
|
-
name: this.toolNames.fail,
|
|
14783
|
-
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.`,
|
|
14784
|
-
inputSchema: {
|
|
14785
|
-
type: "object",
|
|
14786
|
-
properties: {
|
|
14787
|
-
progress_id: { type: "string" },
|
|
14788
|
-
reason: { type: "string", description: "One-sentence failure reason." }
|
|
14789
|
-
},
|
|
14790
|
-
required: ["progress_id", "reason"]
|
|
14791
|
-
}
|
|
14792
|
-
}
|
|
14793
|
-
];
|
|
14794
|
-
}
|
|
14795
|
-
/**
|
|
14796
|
-
* Dispatch a CallToolRequest. Returns `undefined` if the tool name
|
|
14797
|
-
* isn't one of ours (so the caller can keep walking its dispatch
|
|
14798
|
-
* chain). Returns a tool result otherwise.
|
|
14799
|
-
*/
|
|
14800
|
-
async handle(name, args) {
|
|
14801
|
-
if (name === this.toolNames.start) return this.handleStart(args);
|
|
14802
|
-
if (name === this.toolNames.update) return this.handleUpdate(args);
|
|
14803
|
-
if (name === this.toolNames.complete) return this.handleComplete(args);
|
|
14804
|
-
if (name === this.toolNames.fail) return this.handleFail(args);
|
|
14805
|
-
return void 0;
|
|
14806
|
-
}
|
|
14807
|
-
/** Live count of in-flight handles. Exposed for tests + diagnostics. */
|
|
14808
|
-
size() {
|
|
14809
|
-
return this.handles.size;
|
|
14810
|
-
}
|
|
14811
|
-
async handleStart(args) {
|
|
14812
|
-
const label = typeof args.label === "string" ? args.label : null;
|
|
14813
|
-
if (!label) return errResult(`${this.toolNames.start}: 'label' must be a non-empty string`);
|
|
14814
|
-
if ("initial_mode" in args && !isMode(args.initial_mode)) {
|
|
14815
|
-
return errResult(
|
|
14816
|
-
`${this.toolNames.start}: 'initial_mode' must be one of 'thinking', 'working', or 'waiting'`
|
|
14817
|
-
);
|
|
14818
|
-
}
|
|
14819
|
-
for (const k of this.opts.startArgsSchema.required) {
|
|
14820
|
-
if (typeof args[k] !== "string" || args[k].length === 0) {
|
|
14821
|
-
return errResult(`${this.toolNames.start}: '${k}' must be a non-empty string`);
|
|
14822
|
-
}
|
|
14823
|
-
}
|
|
14824
|
-
const initialMode = isMode(args.initial_mode) ? args.initial_mode : void 0;
|
|
14825
|
-
const progressId = randomUUID();
|
|
14826
|
-
try {
|
|
14827
|
-
const flush = this.opts.createFlush(args);
|
|
14828
|
-
const handle = await startProgressHandle({
|
|
14829
|
-
initialLabel: label,
|
|
14830
|
-
initialMode,
|
|
14831
|
-
flush,
|
|
14832
|
-
debounceMs: this.opts.debounceMs ?? 1e3
|
|
14833
|
-
});
|
|
14834
|
-
this.handles.set(progressId, handle);
|
|
14835
|
-
return okResult(JSON.stringify({ progress_id: progressId }));
|
|
14836
|
-
} catch (err) {
|
|
14837
|
-
return errResult(`${this.toolNames.start} failed: ${err.message ?? String(err)}`);
|
|
14838
|
-
}
|
|
14839
|
-
}
|
|
14840
|
-
async handleUpdate(args) {
|
|
14841
|
-
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
14842
|
-
if (!id) return errResult(`${this.toolNames.update}: 'progress_id' must be a string`);
|
|
14843
|
-
const handle = this.handles.get(id);
|
|
14844
|
-
if (!handle) return errResult(`${this.toolNames.update}: unknown progress_id (already terminated, or never started)`);
|
|
14845
|
-
if ("mode" in args && !isMode(args.mode)) {
|
|
14846
|
-
return errResult(
|
|
14847
|
-
`${this.toolNames.update}: 'mode' must be one of 'thinking', 'working', or 'waiting'`
|
|
14848
|
-
);
|
|
14849
|
-
}
|
|
14850
|
-
if (typeof args.step_total === "number" && typeof args.step_current !== "number") {
|
|
14851
|
-
return errResult(
|
|
14852
|
-
`${this.toolNames.update}: 'step_total' requires 'step_current'`
|
|
14853
|
-
);
|
|
14854
|
-
}
|
|
14855
|
-
const next = {};
|
|
14856
|
-
if (typeof args.step_label === "string") next.stepLabel = args.step_label;
|
|
14857
|
-
if (typeof args.step_current === "number") {
|
|
14858
|
-
next.stepNumber = {
|
|
14859
|
-
current: args.step_current,
|
|
14860
|
-
...typeof args.step_total === "number" ? { total: args.step_total } : {}
|
|
14861
|
-
};
|
|
14862
|
-
}
|
|
14863
|
-
if (isMode(args.mode)) next.mode = args.mode;
|
|
14864
|
-
if (typeof args.detail === "string") next.detail = args.detail;
|
|
14865
|
-
try {
|
|
14866
|
-
await handle.update(next);
|
|
14867
|
-
return okResult("ok");
|
|
14868
|
-
} catch (err) {
|
|
14869
|
-
return errResult(`${this.toolNames.update} failed: ${err.message ?? String(err)}`);
|
|
14870
|
-
}
|
|
14871
|
-
}
|
|
14872
|
-
async handleComplete(args) {
|
|
14873
|
-
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
14874
|
-
if (!id) return errResult(`${this.toolNames.complete}: 'progress_id' must be a string`);
|
|
14875
|
-
const handle = this.handles.get(id);
|
|
14876
|
-
if (!handle) return errResult(`${this.toolNames.complete}: unknown progress_id`);
|
|
14877
|
-
const summary = typeof args.summary === "string" ? args.summary : void 0;
|
|
14878
|
-
try {
|
|
14879
|
-
await handle.complete(summary);
|
|
14880
|
-
this.handles.delete(id);
|
|
14881
|
-
return okResult("ok");
|
|
14882
|
-
} catch (err) {
|
|
14883
|
-
return errResult(`${this.toolNames.complete} failed: ${err.message ?? String(err)}`);
|
|
14884
|
-
}
|
|
14885
|
-
}
|
|
14886
|
-
async handleFail(args) {
|
|
14887
|
-
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
14888
|
-
if (!id) return errResult(`${this.toolNames.fail}: 'progress_id' must be a string`);
|
|
14889
|
-
const reason = typeof args.reason === "string" ? args.reason : null;
|
|
14890
|
-
if (!reason) return errResult(`${this.toolNames.fail}: 'reason' must be a non-empty string`);
|
|
14891
|
-
const handle = this.handles.get(id);
|
|
14892
|
-
if (!handle) return errResult(`${this.toolNames.fail}: unknown progress_id`);
|
|
14893
|
-
try {
|
|
14894
|
-
await handle.fail(reason);
|
|
14895
|
-
this.handles.delete(id);
|
|
14896
|
-
return okResult("ok");
|
|
14897
|
-
} catch (err) {
|
|
14898
|
-
return errResult(`${this.toolNames.fail} failed: ${err.message ?? String(err)}`);
|
|
14899
|
-
}
|
|
14900
|
-
}
|
|
14901
|
-
};
|
|
14902
|
-
function okResult(text) {
|
|
14903
|
-
return { content: [{ type: "text", text }] };
|
|
14904
|
-
}
|
|
14905
|
-
function errResult(text) {
|
|
14906
|
-
return { content: [{ type: "text", text }], isError: true };
|
|
14907
|
-
}
|
|
14908
|
-
function isMode(value) {
|
|
14909
|
-
return value === "thinking" || value === "working" || value === "waiting";
|
|
14910
|
-
}
|
|
14911
|
-
|
|
14912
14450
|
// src/slack-thread-context.ts
|
|
14913
14451
|
var SLACK_AUTOLOAD_THREAD_LIMIT = 30;
|
|
14914
14452
|
var SLACK_AUTOLOAD_THREAD_MAX_CHARS = 4e3;
|
|
@@ -15122,7 +14660,7 @@ import {
|
|
|
15122
14660
|
} from "fs";
|
|
15123
14661
|
import { basename, join as join4, resolve as resolve2 } from "path";
|
|
15124
14662
|
import { homedir as homedir2 } from "os";
|
|
15125
|
-
import { createHash, randomUUID
|
|
14663
|
+
import { createHash, randomUUID } from "crypto";
|
|
15126
14664
|
|
|
15127
14665
|
// src/slack-thread-store.ts
|
|
15128
14666
|
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
@@ -16548,7 +16086,7 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
16548
16086
|
...payload.thread_ts ? { thread_ts: payload.thread_ts } : {}
|
|
16549
16087
|
}
|
|
16550
16088
|
};
|
|
16551
|
-
const tmpPath = `${flagPath}.${process.pid}.${
|
|
16089
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
16552
16090
|
writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
16553
16091
|
renameSync2(tmpPath, flagPath);
|
|
16554
16092
|
process.stderr.write(
|
|
@@ -16656,7 +16194,7 @@ async function handleRestartCommand(opts) {
|
|
|
16656
16194
|
message_ts: opts.ts
|
|
16657
16195
|
}
|
|
16658
16196
|
};
|
|
16659
|
-
const tmpPath = `${flagPath}.${process.pid}.${
|
|
16197
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
16660
16198
|
writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
16661
16199
|
renameSync2(tmpPath, flagPath);
|
|
16662
16200
|
process.stderr.write(
|
|
@@ -16826,7 +16364,6 @@ var mcp = new Server(
|
|
|
16826
16364
|
// 2048 chars, so anything appended late silently disappears.
|
|
16827
16365
|
"CRITICAL: every response to a Slack <channel> tag MUST go through slack.reply. Text in your session WITHOUT a slack.reply call never reaches the user \u2014 the message dies inside the agent process.",
|
|
16828
16366
|
`Inbound: <channel ... thread_ts="..." [thread_context="..."]>. Pass channel + thread_ts to slack.reply on threads. thread_context = thread pre-loaded; ground replies ONLY in it, never another channel's.`,
|
|
16829
|
-
"Long task (3+ tool calls or >5s)? slack_progress_start (channel + thread_ts), slack_progress_update between steps, slack_progress_complete/_fail to close. One anchor edits in place; avoids thread spam. Not for one-shots.",
|
|
16830
16367
|
"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 slack.download_attachment. Use that tool only for entries with `file_id` but NO `path` (PDF, docx, csv): pass file_id + channel verbatim, then Read the returned path. Single-image messages also get a top-level `image_path`. Don't surface internal file-handling errors that don't affect the answer.",
|
|
16831
16368
|
"Address users by user_name, never by raw user ID. In multi-participant threads the CURRENT speaker is the one on the latest <channel> tag.",
|
|
16832
16369
|
'Mentioned in a channel \u2192 respond in that thread. DM \u2192 respond directly. auto_followed="true" \u2192 only reply if useful, OR if your own bot user is @-mentioned (counts even in auto_followed).',
|
|
@@ -16836,31 +16373,8 @@ var mcp = new Server(
|
|
|
16836
16373
|
].join(" ")
|
|
16837
16374
|
}
|
|
16838
16375
|
);
|
|
16839
|
-
var progressRegistry = new ProgressToolRegistry({
|
|
16840
|
-
prefix: "slack",
|
|
16841
|
-
surfaceDescription: "a Slack thread",
|
|
16842
|
-
startArgsSchema: {
|
|
16843
|
-
properties: {
|
|
16844
|
-
channel: {
|
|
16845
|
-
type: "string",
|
|
16846
|
-
description: "Slack channel id (from the `channel` attribute on the inbound <channel> tag)."
|
|
16847
|
-
},
|
|
16848
|
-
thread_ts: {
|
|
16849
|
-
type: "string",
|
|
16850
|
-
description: "Thread `ts` so the anchor lands in the same thread as the request (from the `thread_ts` attribute on the inbound <channel> tag). Omit to post at channel-root."
|
|
16851
|
-
}
|
|
16852
|
-
},
|
|
16853
|
-
required: ["channel"]
|
|
16854
|
-
},
|
|
16855
|
-
createFlush: (args) => createSlackProgressFlush({
|
|
16856
|
-
botToken: BOT_TOKEN,
|
|
16857
|
-
channel: args.channel,
|
|
16858
|
-
threadTs: typeof args.thread_ts === "string" ? args.thread_ts : void 0
|
|
16859
|
-
})
|
|
16860
|
-
});
|
|
16861
16376
|
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16862
16377
|
tools: [
|
|
16863
|
-
...progressRegistry.getDefinitions(),
|
|
16864
16378
|
{
|
|
16865
16379
|
name: "slack.reply",
|
|
16866
16380
|
description: "Send a message back to a Slack channel or thread",
|
|
@@ -17111,8 +16625,6 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
17111
16625
|
if (isImpersonating() && !channelsEnabledOverride() && SLACK_EGRESS_TOOLS.has(name)) {
|
|
17112
16626
|
return buildImpersonationRefusal(name);
|
|
17113
16627
|
}
|
|
17114
|
-
const progressResult = await progressRegistry.handle(name, args ?? {});
|
|
17115
|
-
if (progressResult !== void 0) return progressResult;
|
|
17116
16628
|
if (name === "slack.reply") {
|
|
17117
16629
|
const { channel, text, thread_ts } = args;
|
|
17118
16630
|
if (channel && thread_ts) {
|
|
@@ -17633,15 +17145,15 @@ async function handleSendStructured(args) {
|
|
|
17633
17145
|
const { validateSlackBlocks: validateSlackBlocks2 } = runtime;
|
|
17634
17146
|
const { channel, blocks, text, thread_ts, interactive } = args;
|
|
17635
17147
|
if (typeof channel !== "string" || !channel) {
|
|
17636
|
-
return
|
|
17148
|
+
return errResult("channel is required");
|
|
17637
17149
|
}
|
|
17638
17150
|
if (typeof text !== "string" || !text) {
|
|
17639
|
-
return
|
|
17151
|
+
return errResult("text is required (used as fallback for push notifications and unfurls)");
|
|
17640
17152
|
}
|
|
17641
17153
|
noteThreadActivity(channel, thread_ts);
|
|
17642
17154
|
const validation = validateSlackBlocks2(blocks);
|
|
17643
17155
|
if (!validation.ok) {
|
|
17644
|
-
return
|
|
17156
|
+
return errResult(`Invalid blocks: ${validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
17645
17157
|
}
|
|
17646
17158
|
let effectiveBlocks = blocks;
|
|
17647
17159
|
let ratingCallbackId = null;
|
|
@@ -17649,13 +17161,13 @@ async function handleSendStructured(args) {
|
|
|
17649
17161
|
let ratingTokenised = [];
|
|
17650
17162
|
if (interactive) {
|
|
17651
17163
|
if (interactive.type !== "rate_run") {
|
|
17652
|
-
return
|
|
17164
|
+
return errResult(`Unsupported interactive.type '${interactive.type}'. Supported: rate_run.`);
|
|
17653
17165
|
}
|
|
17654
17166
|
if (typeof interactive.run_id !== "string" || !interactive.run_id) {
|
|
17655
|
-
return
|
|
17167
|
+
return errResult("interactive.run_id is required when interactive is set");
|
|
17656
17168
|
}
|
|
17657
17169
|
if (!interactiveHostAvailable()) {
|
|
17658
|
-
return
|
|
17170
|
+
return errResult(
|
|
17659
17171
|
"interactive=rate_run requires Block Kit plus host MCP wiring (AGT_HOST, AGT_API_KEY, AGT_AGENT_ID)."
|
|
17660
17172
|
);
|
|
17661
17173
|
}
|
|
@@ -17686,7 +17198,7 @@ async function handleSendStructured(args) {
|
|
|
17686
17198
|
effectiveBlocks = [...blocks, ratingBlock];
|
|
17687
17199
|
const finalValidation = runtime.validateSlackBlocks(effectiveBlocks);
|
|
17688
17200
|
if (!finalValidation.ok) {
|
|
17689
|
-
return
|
|
17201
|
+
return errResult(
|
|
17690
17202
|
`Invalid blocks (after appending rating row): ${finalValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
|
|
17691
17203
|
);
|
|
17692
17204
|
}
|
|
@@ -17707,7 +17219,7 @@ async function handleSendStructured(args) {
|
|
|
17707
17219
|
kindData: { run_id: interactive.run_id, scale }
|
|
17708
17220
|
});
|
|
17709
17221
|
} catch (err) {
|
|
17710
|
-
return
|
|
17222
|
+
return errResult(`Failed to register rating row: ${err.message}`);
|
|
17711
17223
|
}
|
|
17712
17224
|
}
|
|
17713
17225
|
const result = await postSlackMessageWithTs({
|
|
@@ -17717,7 +17229,7 @@ async function handleSendStructured(args) {
|
|
|
17717
17229
|
...thread_ts ? { thread_ts } : {}
|
|
17718
17230
|
});
|
|
17719
17231
|
if (!result.ok || !result.ts) {
|
|
17720
|
-
return
|
|
17232
|
+
return errResult(`Slack chat.postMessage failed: ${result.error ?? "unknown"}`);
|
|
17721
17233
|
}
|
|
17722
17234
|
if (ratingCallbackId) {
|
|
17723
17235
|
try {
|
|
@@ -17772,17 +17284,17 @@ async function handleSendStructured(args) {
|
|
|
17772
17284
|
}
|
|
17773
17285
|
async function handleAskUser(args) {
|
|
17774
17286
|
if (!askUserToolAvailable()) {
|
|
17775
|
-
return
|
|
17287
|
+
return errResult("slack.ask_user is disabled or the host MCP is missing AGT_HOST/AGT_API_KEY/AGT_AGENT_ID env wiring.");
|
|
17776
17288
|
}
|
|
17777
17289
|
const { randomUUID: rndUUID } = await import("crypto");
|
|
17778
17290
|
const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
|
|
17779
17291
|
const { validateAskUserOptions: validateAskUserOptions2, buildAskUserBlocks: buildAskUserBlocks2 } = runtime;
|
|
17780
17292
|
const { channel, question, options, timeout_seconds, thread_ts } = args;
|
|
17781
|
-
if (typeof channel !== "string" || !channel) return
|
|
17782
|
-
if (typeof question !== "string" || !question) return
|
|
17293
|
+
if (typeof channel !== "string" || !channel) return errResult("channel is required");
|
|
17294
|
+
if (typeof question !== "string" || !question) return errResult("question is required");
|
|
17783
17295
|
const optsValidation = validateAskUserOptions2(options);
|
|
17784
17296
|
if (!optsValidation.ok) {
|
|
17785
|
-
return
|
|
17297
|
+
return errResult(`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
17786
17298
|
}
|
|
17787
17299
|
const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
|
|
17788
17300
|
const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
|
|
@@ -17815,7 +17327,7 @@ async function handleAskUser(args) {
|
|
|
17815
17327
|
expiresAt
|
|
17816
17328
|
});
|
|
17817
17329
|
} catch (err) {
|
|
17818
|
-
return
|
|
17330
|
+
return errResult(`Failed to register pending interaction: ${err.message}`);
|
|
17819
17331
|
}
|
|
17820
17332
|
const slackResult = await postSlackMessageWithTs({
|
|
17821
17333
|
channel,
|
|
@@ -17825,7 +17337,7 @@ async function handleAskUser(args) {
|
|
|
17825
17337
|
...thread_ts ? { thread_ts } : {}
|
|
17826
17338
|
});
|
|
17827
17339
|
if (!slackResult.ok || !slackResult.ts) {
|
|
17828
|
-
return
|
|
17340
|
+
return errResult(`Slack chat.postMessage failed: ${slackResult.error ?? "unknown"}`);
|
|
17829
17341
|
}
|
|
17830
17342
|
try {
|
|
17831
17343
|
await runtime.updatePendingInteractionMessageTs(cfg, callbackId, slackResult.ts);
|
|
@@ -17872,7 +17384,7 @@ async function handleAskUser(args) {
|
|
|
17872
17384
|
}
|
|
17873
17385
|
async function handleChannelRequestInput(args) {
|
|
17874
17386
|
if (!askUserToolAvailable()) {
|
|
17875
|
-
return
|
|
17387
|
+
return errResult(
|
|
17876
17388
|
"channel_request_input is disabled or the host MCP is missing AGT_HOST/AGT_API_KEY/AGT_AGENT_ID env wiring."
|
|
17877
17389
|
);
|
|
17878
17390
|
}
|
|
@@ -17880,10 +17392,10 @@ async function handleChannelRequestInput(args) {
|
|
|
17880
17392
|
const { apiCall: apiCall2, waitForResolution: waitForResolution2, validateAskUserOptions: validateAskUserOptions2 } = runtime;
|
|
17881
17393
|
const { thread_id, prompt_text, options, schema, request_id, timeout_seconds } = args;
|
|
17882
17394
|
if (typeof thread_id !== "string" || !thread_id) {
|
|
17883
|
-
return
|
|
17395
|
+
return errResult("thread_id is required (shape: `<channel_id>:<thread_ts>`)");
|
|
17884
17396
|
}
|
|
17885
17397
|
if (typeof prompt_text !== "string" || !prompt_text) {
|
|
17886
|
-
return
|
|
17398
|
+
return errResult("prompt_text is required");
|
|
17887
17399
|
}
|
|
17888
17400
|
const optsValidation = validateAskUserOptions2(options, {
|
|
17889
17401
|
maxOptions: 4,
|
|
@@ -17891,25 +17403,25 @@ async function handleChannelRequestInput(args) {
|
|
|
17891
17403
|
maxValueChars: 2e3
|
|
17892
17404
|
});
|
|
17893
17405
|
if (!optsValidation.ok) {
|
|
17894
|
-
return
|
|
17406
|
+
return errResult(
|
|
17895
17407
|
`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
|
|
17896
17408
|
);
|
|
17897
17409
|
}
|
|
17898
17410
|
const optionsArr = options;
|
|
17899
17411
|
if (optionsArr.length < 2) {
|
|
17900
|
-
return
|
|
17412
|
+
return errResult("options must contain at least 2 entries (this is a picker, not a notification)");
|
|
17901
17413
|
}
|
|
17902
17414
|
if (!schema || typeof schema.type !== "string") {
|
|
17903
|
-
return
|
|
17415
|
+
return errResult("schema is required with shape { type: 'yes_no' | 'choice' }");
|
|
17904
17416
|
}
|
|
17905
17417
|
if (schema.type !== "yes_no" && schema.type !== "choice") {
|
|
17906
|
-
return
|
|
17418
|
+
return errResult("schema.type must be 'yes_no' or 'choice'");
|
|
17907
17419
|
}
|
|
17908
17420
|
if (schema.type === "yes_no" && optionsArr.length !== 2) {
|
|
17909
|
-
return
|
|
17421
|
+
return errResult("schema.type='yes_no' requires exactly 2 options");
|
|
17910
17422
|
}
|
|
17911
17423
|
if (request_id !== void 0 && (typeof request_id !== "string" || !request_id)) {
|
|
17912
|
-
return
|
|
17424
|
+
return errResult("request_id must be a non-empty string when provided");
|
|
17913
17425
|
}
|
|
17914
17426
|
const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
|
|
17915
17427
|
const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
|
|
@@ -17931,10 +17443,10 @@ async function handleChannelRequestInput(args) {
|
|
|
17931
17443
|
dispatchPayload = await res.json().catch(() => ({}));
|
|
17932
17444
|
if (!res.ok || !dispatchPayload.ok || !dispatchPayload.callback_id) {
|
|
17933
17445
|
const reason = dispatchPayload.reason ?? `http_${res.status}`;
|
|
17934
|
-
return
|
|
17446
|
+
return errResult(`channel_request_input dispatch failed: ${reason}`);
|
|
17935
17447
|
}
|
|
17936
17448
|
} catch (err) {
|
|
17937
|
-
return
|
|
17449
|
+
return errResult(
|
|
17938
17450
|
`channel_request_input dispatch failed: ${err.message}`
|
|
17939
17451
|
);
|
|
17940
17452
|
}
|
|
@@ -17972,7 +17484,7 @@ async function handleChannelRequestInput(args) {
|
|
|
17972
17484
|
]
|
|
17973
17485
|
};
|
|
17974
17486
|
}
|
|
17975
|
-
function
|
|
17487
|
+
function errResult(text) {
|
|
17976
17488
|
return { content: [{ type: "text", text }], isError: true };
|
|
17977
17489
|
}
|
|
17978
17490
|
var MAX_INBOUND_FILE_BYTES = 25 * 1024 * 1024;
|