@integrity-labs/agt-cli 0.20.6 → 0.20.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 +3 -3
- package/dist/{chunk-SOYIZEZG.js → chunk-5A5EBN5L.js} +62 -2
- package/dist/chunk-5A5EBN5L.js.map +1 -0
- package/dist/lib/manager-worker.js +2 -2
- package/mcp/direct-chat-channel.js +455 -0
- package/mcp/slack-channel.js +501 -19
- package/package.json +1 -1
- package/dist/chunk-SOYIZEZG.js.map +0 -1
package/mcp/slack-channel.js
CHANGED
|
@@ -14323,6 +14323,462 @@ async function isThreadKilled(opts) {
|
|
|
14323
14323
|
}
|
|
14324
14324
|
}
|
|
14325
14325
|
|
|
14326
|
+
// src/slack-progress.ts
|
|
14327
|
+
var MODE_EMOJI = {
|
|
14328
|
+
thinking: "\u{1F4AD}",
|
|
14329
|
+
working: "\u{1F6E0}\uFE0F",
|
|
14330
|
+
waiting: "\u23F3"
|
|
14331
|
+
};
|
|
14332
|
+
var TERMINAL_EMOJI = {
|
|
14333
|
+
completed: "\u2705",
|
|
14334
|
+
failed: "\u274C"
|
|
14335
|
+
};
|
|
14336
|
+
function formatWallClock(ms) {
|
|
14337
|
+
const d = new Date(ms);
|
|
14338
|
+
const hh = String(d.getUTCHours()).padStart(2, "0");
|
|
14339
|
+
const mm = String(d.getUTCMinutes()).padStart(2, "0");
|
|
14340
|
+
const ss = String(d.getUTCSeconds()).padStart(2, "0");
|
|
14341
|
+
return `${hh}:${mm}:${ss} UTC`;
|
|
14342
|
+
}
|
|
14343
|
+
function renderHeaderLine(state) {
|
|
14344
|
+
if (state.terminal) {
|
|
14345
|
+
return `${TERMINAL_EMOJI[state.terminal.kind]} *${state.terminal.kind === "completed" ? "Done" : "Failed"}* \u2014 ${state.initialLabel}`;
|
|
14346
|
+
}
|
|
14347
|
+
return `${MODE_EMOJI[state.mode]} *Working on:* ${state.initialLabel}`;
|
|
14348
|
+
}
|
|
14349
|
+
function renderStepLine(state) {
|
|
14350
|
+
if (state.terminal) {
|
|
14351
|
+
return state.terminal.message ? state.terminal.message : void 0;
|
|
14352
|
+
}
|
|
14353
|
+
const parts = [];
|
|
14354
|
+
if (state.stepNumber) {
|
|
14355
|
+
if (state.stepNumber.total != null) {
|
|
14356
|
+
parts.push(`*Step ${state.stepNumber.current} of ${state.stepNumber.total}*`);
|
|
14357
|
+
} else {
|
|
14358
|
+
parts.push(`*Step ${state.stepNumber.current}*`);
|
|
14359
|
+
}
|
|
14360
|
+
}
|
|
14361
|
+
if (state.stepLabel) parts.push(state.stepLabel);
|
|
14362
|
+
return parts.length > 0 ? parts.join(": ") : void 0;
|
|
14363
|
+
}
|
|
14364
|
+
function renderProgressBlocks(state) {
|
|
14365
|
+
const header = renderHeaderLine(state);
|
|
14366
|
+
const step = renderStepLine(state);
|
|
14367
|
+
const footer = state.terminal ? `_${state.terminal.kind === "completed" ? "completed" : "failed"}: ${formatWallClock(state.lastChangeAt)}_` : `_last update: ${formatWallClock(state.lastChangeAt)}_`;
|
|
14368
|
+
const lines = [header];
|
|
14369
|
+
if (step) lines.push(step);
|
|
14370
|
+
if (state.detail) lines.push(`_${state.detail}_`);
|
|
14371
|
+
lines.push(footer);
|
|
14372
|
+
const fallbackText = state.terminal ? `${state.terminal.kind === "completed" ? "Done" : "Failed"} \u2014 ${state.initialLabel}` : `Working on: ${state.initialLabel}`;
|
|
14373
|
+
return {
|
|
14374
|
+
text: fallbackText,
|
|
14375
|
+
blocks: [
|
|
14376
|
+
{
|
|
14377
|
+
type: "section",
|
|
14378
|
+
text: { type: "mrkdwn", text: lines.join("\n") }
|
|
14379
|
+
}
|
|
14380
|
+
]
|
|
14381
|
+
};
|
|
14382
|
+
}
|
|
14383
|
+
var SLACK_POST_URL = "https://slack.com/api/chat.postMessage";
|
|
14384
|
+
var SLACK_UPDATE_URL = "https://slack.com/api/chat.update";
|
|
14385
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
14386
|
+
var SlackApiError = class extends Error {
|
|
14387
|
+
constructor(endpoint, slackError, status) {
|
|
14388
|
+
super(`Slack ${endpoint} failed: ${slackError} (status=${status})`);
|
|
14389
|
+
this.endpoint = endpoint;
|
|
14390
|
+
this.slackError = slackError;
|
|
14391
|
+
this.status = status;
|
|
14392
|
+
this.name = "SlackApiError";
|
|
14393
|
+
}
|
|
14394
|
+
};
|
|
14395
|
+
function createSlackProgressFlush(opts) {
|
|
14396
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
14397
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
14398
|
+
let anchorTs = null;
|
|
14399
|
+
async function slackCall(endpoint, body) {
|
|
14400
|
+
const url = endpoint === "chat.postMessage" ? SLACK_POST_URL : SLACK_UPDATE_URL;
|
|
14401
|
+
const controller = new AbortController();
|
|
14402
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
14403
|
+
let response;
|
|
14404
|
+
try {
|
|
14405
|
+
response = await fetchImpl(url, {
|
|
14406
|
+
method: "POST",
|
|
14407
|
+
headers: {
|
|
14408
|
+
"Content-Type": "application/json",
|
|
14409
|
+
Authorization: `Bearer ${opts.botToken}`
|
|
14410
|
+
},
|
|
14411
|
+
body: JSON.stringify(body),
|
|
14412
|
+
signal: controller.signal
|
|
14413
|
+
});
|
|
14414
|
+
} finally {
|
|
14415
|
+
clearTimeout(timer);
|
|
14416
|
+
}
|
|
14417
|
+
const parsed = await response.json().catch(() => ({ ok: false, error: "invalid_json" }));
|
|
14418
|
+
if (!parsed.ok) {
|
|
14419
|
+
throw new SlackApiError(endpoint, parsed.error ?? "unknown", response.status);
|
|
14420
|
+
}
|
|
14421
|
+
return parsed;
|
|
14422
|
+
}
|
|
14423
|
+
return async function flush(state) {
|
|
14424
|
+
const payload = renderProgressBlocks(state);
|
|
14425
|
+
if (anchorTs == null) {
|
|
14426
|
+
const body = {
|
|
14427
|
+
channel: opts.channel,
|
|
14428
|
+
text: payload.text,
|
|
14429
|
+
blocks: payload.blocks
|
|
14430
|
+
};
|
|
14431
|
+
if (opts.threadTs) body.thread_ts = opts.threadTs;
|
|
14432
|
+
try {
|
|
14433
|
+
const res = await slackCall("chat.postMessage", body);
|
|
14434
|
+
if (!res.ts) {
|
|
14435
|
+
throw new SlackApiError(
|
|
14436
|
+
"chat.postMessage",
|
|
14437
|
+
"missing ts in successful response",
|
|
14438
|
+
200
|
|
14439
|
+
);
|
|
14440
|
+
}
|
|
14441
|
+
anchorTs = res.ts;
|
|
14442
|
+
opts.onAnchorPosted?.(res.ts);
|
|
14443
|
+
} catch (err) {
|
|
14444
|
+
throw err;
|
|
14445
|
+
}
|
|
14446
|
+
return;
|
|
14447
|
+
}
|
|
14448
|
+
try {
|
|
14449
|
+
await slackCall("chat.update", {
|
|
14450
|
+
channel: opts.channel,
|
|
14451
|
+
ts: anchorTs,
|
|
14452
|
+
text: payload.text,
|
|
14453
|
+
blocks: payload.blocks
|
|
14454
|
+
});
|
|
14455
|
+
} catch (err) {
|
|
14456
|
+
if (opts.onApiError && err instanceof SlackApiError) {
|
|
14457
|
+
await opts.onApiError(err);
|
|
14458
|
+
return;
|
|
14459
|
+
}
|
|
14460
|
+
throw err;
|
|
14461
|
+
}
|
|
14462
|
+
};
|
|
14463
|
+
}
|
|
14464
|
+
|
|
14465
|
+
// src/progress-tools.ts
|
|
14466
|
+
import { randomUUID } from "crypto";
|
|
14467
|
+
|
|
14468
|
+
// src/progress-handle.ts
|
|
14469
|
+
async function startProgressHandle(opts) {
|
|
14470
|
+
const now = opts.now ?? (() => Date.now());
|
|
14471
|
+
const setTimer = opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
14472
|
+
const clearTimer = opts.clearTimer ?? ((id) => clearTimeout(id));
|
|
14473
|
+
const debounceMs = opts.debounceMs ?? 1e3;
|
|
14474
|
+
const onPostTerminalUpdate = opts.onPostTerminalUpdate ?? defaultPostTerminalWarn;
|
|
14475
|
+
const state = {
|
|
14476
|
+
initialLabel: opts.initialLabel,
|
|
14477
|
+
mode: opts.initialMode ?? "working",
|
|
14478
|
+
lastChangeAt: now()
|
|
14479
|
+
};
|
|
14480
|
+
let lastFlushAt = 0;
|
|
14481
|
+
let pendingTimer = null;
|
|
14482
|
+
let inFlight = null;
|
|
14483
|
+
let terminal = false;
|
|
14484
|
+
async function doFlush() {
|
|
14485
|
+
while (inFlight) await inFlight;
|
|
14486
|
+
if (pendingTimer != null) {
|
|
14487
|
+
clearTimer(pendingTimer);
|
|
14488
|
+
pendingTimer = null;
|
|
14489
|
+
}
|
|
14490
|
+
lastFlushAt = now();
|
|
14491
|
+
const promise = opts.flush(snapshotState());
|
|
14492
|
+
inFlight = promise.then(
|
|
14493
|
+
() => {
|
|
14494
|
+
inFlight = null;
|
|
14495
|
+
},
|
|
14496
|
+
(err) => {
|
|
14497
|
+
inFlight = null;
|
|
14498
|
+
throw err;
|
|
14499
|
+
}
|
|
14500
|
+
);
|
|
14501
|
+
await inFlight;
|
|
14502
|
+
}
|
|
14503
|
+
function snapshotState() {
|
|
14504
|
+
return {
|
|
14505
|
+
initialLabel: state.initialLabel,
|
|
14506
|
+
mode: state.mode,
|
|
14507
|
+
stepLabel: state.stepLabel,
|
|
14508
|
+
stepNumber: state.stepNumber ? { ...state.stepNumber } : void 0,
|
|
14509
|
+
detail: state.detail,
|
|
14510
|
+
lastChangeAt: state.lastChangeAt,
|
|
14511
|
+
terminal: state.terminal ? { ...state.terminal } : void 0
|
|
14512
|
+
};
|
|
14513
|
+
}
|
|
14514
|
+
function applyUpdate(next) {
|
|
14515
|
+
if (next.mode !== void 0) state.mode = next.mode;
|
|
14516
|
+
if (next.stepLabel !== void 0) state.stepLabel = next.stepLabel;
|
|
14517
|
+
if (next.stepNumber !== void 0) state.stepNumber = { ...next.stepNumber };
|
|
14518
|
+
if (next.detail !== void 0) state.detail = next.detail;
|
|
14519
|
+
state.lastChangeAt = now();
|
|
14520
|
+
}
|
|
14521
|
+
async function scheduleOrFlush() {
|
|
14522
|
+
const elapsed = now() - lastFlushAt;
|
|
14523
|
+
if (elapsed >= debounceMs) {
|
|
14524
|
+
await doFlush();
|
|
14525
|
+
return;
|
|
14526
|
+
}
|
|
14527
|
+
if (pendingTimer != null) return;
|
|
14528
|
+
const remaining = debounceMs - elapsed;
|
|
14529
|
+
pendingTimer = setTimer(() => {
|
|
14530
|
+
pendingTimer = null;
|
|
14531
|
+
void doFlush().catch(() => {
|
|
14532
|
+
});
|
|
14533
|
+
}, remaining);
|
|
14534
|
+
}
|
|
14535
|
+
lastFlushAt = now();
|
|
14536
|
+
await opts.flush(snapshotState());
|
|
14537
|
+
return {
|
|
14538
|
+
snapshot: snapshotState,
|
|
14539
|
+
async update(next) {
|
|
14540
|
+
if (terminal) {
|
|
14541
|
+
const peeked = snapshotState();
|
|
14542
|
+
Object.assign(peeked, {
|
|
14543
|
+
mode: next.mode ?? peeked.mode,
|
|
14544
|
+
stepLabel: next.stepLabel ?? peeked.stepLabel,
|
|
14545
|
+
stepNumber: next.stepNumber ?? peeked.stepNumber,
|
|
14546
|
+
detail: next.detail ?? peeked.detail
|
|
14547
|
+
});
|
|
14548
|
+
try {
|
|
14549
|
+
onPostTerminalUpdate(peeked);
|
|
14550
|
+
} catch (err) {
|
|
14551
|
+
console.warn(
|
|
14552
|
+
"[progress-handle] onPostTerminalUpdate threw \u2014 swallowed to keep update() non-throwing:",
|
|
14553
|
+
err
|
|
14554
|
+
);
|
|
14555
|
+
}
|
|
14556
|
+
return;
|
|
14557
|
+
}
|
|
14558
|
+
applyUpdate(next);
|
|
14559
|
+
await scheduleOrFlush();
|
|
14560
|
+
},
|
|
14561
|
+
async complete(summary) {
|
|
14562
|
+
if (terminal) return;
|
|
14563
|
+
terminal = true;
|
|
14564
|
+
state.terminal = { kind: "completed", message: summary };
|
|
14565
|
+
state.lastChangeAt = now();
|
|
14566
|
+
await doFlush();
|
|
14567
|
+
},
|
|
14568
|
+
async fail(reason) {
|
|
14569
|
+
if (terminal) return;
|
|
14570
|
+
terminal = true;
|
|
14571
|
+
state.terminal = { kind: "failed", message: reason };
|
|
14572
|
+
state.lastChangeAt = now();
|
|
14573
|
+
await doFlush();
|
|
14574
|
+
},
|
|
14575
|
+
isTerminal: () => terminal
|
|
14576
|
+
};
|
|
14577
|
+
}
|
|
14578
|
+
function defaultPostTerminalWarn(state) {
|
|
14579
|
+
console.warn(
|
|
14580
|
+
`[progress-handle] update() called after terminal transition (${state.terminal?.kind}) \u2014 ignored.`
|
|
14581
|
+
);
|
|
14582
|
+
}
|
|
14583
|
+
|
|
14584
|
+
// src/progress-tools.ts
|
|
14585
|
+
var ProgressToolRegistry = class {
|
|
14586
|
+
constructor(opts) {
|
|
14587
|
+
this.opts = opts;
|
|
14588
|
+
this.toolNames = {
|
|
14589
|
+
start: `${opts.prefix}_progress_start`,
|
|
14590
|
+
update: `${opts.prefix}_progress_update`,
|
|
14591
|
+
complete: `${opts.prefix}_progress_complete`,
|
|
14592
|
+
fail: `${opts.prefix}_progress_fail`
|
|
14593
|
+
};
|
|
14594
|
+
}
|
|
14595
|
+
handles = /* @__PURE__ */ new Map();
|
|
14596
|
+
toolNames;
|
|
14597
|
+
/** Tool definitions to splice into the MCP ListToolsRequest response. */
|
|
14598
|
+
getDefinitions() {
|
|
14599
|
+
const { surfaceDescription, startArgsSchema } = this.opts;
|
|
14600
|
+
const debounceMs = this.opts.debounceMs ?? 1e3;
|
|
14601
|
+
const debounceText = debounceMs === 1e3 ? "per second" : `every ${debounceMs}ms`;
|
|
14602
|
+
return [
|
|
14603
|
+
{
|
|
14604
|
+
name: this.toolNames.start,
|
|
14605
|
+
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.`,
|
|
14606
|
+
inputSchema: {
|
|
14607
|
+
type: "object",
|
|
14608
|
+
properties: {
|
|
14609
|
+
label: {
|
|
14610
|
+
type: "string",
|
|
14611
|
+
description: 'Short top-line label for the task (e.g. "diagnose vigil"). Stays constant for the lifetime of this progress anchor.'
|
|
14612
|
+
},
|
|
14613
|
+
initial_mode: {
|
|
14614
|
+
type: "string",
|
|
14615
|
+
enum: ["thinking", "working", "waiting"],
|
|
14616
|
+
description: 'Initial mode emoji \u2014 defaults to "working".'
|
|
14617
|
+
},
|
|
14618
|
+
...startArgsSchema.properties
|
|
14619
|
+
},
|
|
14620
|
+
required: ["label", ...startArgsSchema.required]
|
|
14621
|
+
}
|
|
14622
|
+
},
|
|
14623
|
+
{
|
|
14624
|
+
name: this.toolNames.update,
|
|
14625
|
+
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.`,
|
|
14626
|
+
inputSchema: {
|
|
14627
|
+
type: "object",
|
|
14628
|
+
properties: {
|
|
14629
|
+
progress_id: { type: "string", description: "The progress_id returned from start." },
|
|
14630
|
+
step_label: { type: "string", description: 'Short label for the current step (e.g. "Querying CloudWatch logs").' },
|
|
14631
|
+
step_current: { type: "number", description: "1-based step counter \u2014 current." },
|
|
14632
|
+
step_total: { type: "number", description: "Total steps if known. Omit for open-ended progress." },
|
|
14633
|
+
mode: { type: "string", enum: ["thinking", "working", "waiting"] },
|
|
14634
|
+
detail: { type: "string", description: "Free-text detail line under the step label." }
|
|
14635
|
+
},
|
|
14636
|
+
required: ["progress_id"]
|
|
14637
|
+
}
|
|
14638
|
+
},
|
|
14639
|
+
{
|
|
14640
|
+
name: this.toolNames.complete,
|
|
14641
|
+
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.`,
|
|
14642
|
+
inputSchema: {
|
|
14643
|
+
type: "object",
|
|
14644
|
+
properties: {
|
|
14645
|
+
progress_id: { type: "string" },
|
|
14646
|
+
summary: { type: "string", description: "Optional one-line summary of what was achieved." }
|
|
14647
|
+
},
|
|
14648
|
+
required: ["progress_id"]
|
|
14649
|
+
}
|
|
14650
|
+
},
|
|
14651
|
+
{
|
|
14652
|
+
name: this.toolNames.fail,
|
|
14653
|
+
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.`,
|
|
14654
|
+
inputSchema: {
|
|
14655
|
+
type: "object",
|
|
14656
|
+
properties: {
|
|
14657
|
+
progress_id: { type: "string" },
|
|
14658
|
+
reason: { type: "string", description: "One-sentence failure reason." }
|
|
14659
|
+
},
|
|
14660
|
+
required: ["progress_id", "reason"]
|
|
14661
|
+
}
|
|
14662
|
+
}
|
|
14663
|
+
];
|
|
14664
|
+
}
|
|
14665
|
+
/**
|
|
14666
|
+
* Dispatch a CallToolRequest. Returns `undefined` if the tool name
|
|
14667
|
+
* isn't one of ours (so the caller can keep walking its dispatch
|
|
14668
|
+
* chain). Returns a tool result otherwise.
|
|
14669
|
+
*/
|
|
14670
|
+
async handle(name, args) {
|
|
14671
|
+
if (name === this.toolNames.start) return this.handleStart(args);
|
|
14672
|
+
if (name === this.toolNames.update) return this.handleUpdate(args);
|
|
14673
|
+
if (name === this.toolNames.complete) return this.handleComplete(args);
|
|
14674
|
+
if (name === this.toolNames.fail) return this.handleFail(args);
|
|
14675
|
+
return void 0;
|
|
14676
|
+
}
|
|
14677
|
+
/** Live count of in-flight handles. Exposed for tests + diagnostics. */
|
|
14678
|
+
size() {
|
|
14679
|
+
return this.handles.size;
|
|
14680
|
+
}
|
|
14681
|
+
async handleStart(args) {
|
|
14682
|
+
const label = typeof args.label === "string" ? args.label : null;
|
|
14683
|
+
if (!label) return errResult(`${this.toolNames.start}: 'label' must be a non-empty string`);
|
|
14684
|
+
if ("initial_mode" in args && !isMode(args.initial_mode)) {
|
|
14685
|
+
return errResult(
|
|
14686
|
+
`${this.toolNames.start}: 'initial_mode' must be one of 'thinking', 'working', or 'waiting'`
|
|
14687
|
+
);
|
|
14688
|
+
}
|
|
14689
|
+
for (const k of this.opts.startArgsSchema.required) {
|
|
14690
|
+
if (typeof args[k] !== "string" || args[k].length === 0) {
|
|
14691
|
+
return errResult(`${this.toolNames.start}: '${k}' must be a non-empty string`);
|
|
14692
|
+
}
|
|
14693
|
+
}
|
|
14694
|
+
const initialMode = isMode(args.initial_mode) ? args.initial_mode : void 0;
|
|
14695
|
+
const progressId = randomUUID();
|
|
14696
|
+
try {
|
|
14697
|
+
const flush = this.opts.createFlush(args);
|
|
14698
|
+
const handle = await startProgressHandle({
|
|
14699
|
+
initialLabel: label,
|
|
14700
|
+
initialMode,
|
|
14701
|
+
flush,
|
|
14702
|
+
debounceMs: this.opts.debounceMs ?? 1e3
|
|
14703
|
+
});
|
|
14704
|
+
this.handles.set(progressId, handle);
|
|
14705
|
+
return okResult(JSON.stringify({ progress_id: progressId }));
|
|
14706
|
+
} catch (err) {
|
|
14707
|
+
return errResult(`${this.toolNames.start} failed: ${err.message ?? String(err)}`);
|
|
14708
|
+
}
|
|
14709
|
+
}
|
|
14710
|
+
async handleUpdate(args) {
|
|
14711
|
+
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
14712
|
+
if (!id) return errResult(`${this.toolNames.update}: 'progress_id' must be a string`);
|
|
14713
|
+
const handle = this.handles.get(id);
|
|
14714
|
+
if (!handle) return errResult(`${this.toolNames.update}: unknown progress_id (already terminated, or never started)`);
|
|
14715
|
+
if ("mode" in args && !isMode(args.mode)) {
|
|
14716
|
+
return errResult(
|
|
14717
|
+
`${this.toolNames.update}: 'mode' must be one of 'thinking', 'working', or 'waiting'`
|
|
14718
|
+
);
|
|
14719
|
+
}
|
|
14720
|
+
if (typeof args.step_total === "number" && typeof args.step_current !== "number") {
|
|
14721
|
+
return errResult(
|
|
14722
|
+
`${this.toolNames.update}: 'step_total' requires 'step_current'`
|
|
14723
|
+
);
|
|
14724
|
+
}
|
|
14725
|
+
const next = {};
|
|
14726
|
+
if (typeof args.step_label === "string") next.stepLabel = args.step_label;
|
|
14727
|
+
if (typeof args.step_current === "number") {
|
|
14728
|
+
next.stepNumber = {
|
|
14729
|
+
current: args.step_current,
|
|
14730
|
+
...typeof args.step_total === "number" ? { total: args.step_total } : {}
|
|
14731
|
+
};
|
|
14732
|
+
}
|
|
14733
|
+
if (isMode(args.mode)) next.mode = args.mode;
|
|
14734
|
+
if (typeof args.detail === "string") next.detail = args.detail;
|
|
14735
|
+
try {
|
|
14736
|
+
await handle.update(next);
|
|
14737
|
+
return okResult("ok");
|
|
14738
|
+
} catch (err) {
|
|
14739
|
+
return errResult(`${this.toolNames.update} failed: ${err.message ?? String(err)}`);
|
|
14740
|
+
}
|
|
14741
|
+
}
|
|
14742
|
+
async handleComplete(args) {
|
|
14743
|
+
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
14744
|
+
if (!id) return errResult(`${this.toolNames.complete}: 'progress_id' must be a string`);
|
|
14745
|
+
const handle = this.handles.get(id);
|
|
14746
|
+
if (!handle) return errResult(`${this.toolNames.complete}: unknown progress_id`);
|
|
14747
|
+
const summary = typeof args.summary === "string" ? args.summary : void 0;
|
|
14748
|
+
try {
|
|
14749
|
+
await handle.complete(summary);
|
|
14750
|
+
this.handles.delete(id);
|
|
14751
|
+
return okResult("ok");
|
|
14752
|
+
} catch (err) {
|
|
14753
|
+
return errResult(`${this.toolNames.complete} failed: ${err.message ?? String(err)}`);
|
|
14754
|
+
}
|
|
14755
|
+
}
|
|
14756
|
+
async handleFail(args) {
|
|
14757
|
+
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
14758
|
+
if (!id) return errResult(`${this.toolNames.fail}: 'progress_id' must be a string`);
|
|
14759
|
+
const reason = typeof args.reason === "string" ? args.reason : null;
|
|
14760
|
+
if (!reason) return errResult(`${this.toolNames.fail}: 'reason' must be a non-empty string`);
|
|
14761
|
+
const handle = this.handles.get(id);
|
|
14762
|
+
if (!handle) return errResult(`${this.toolNames.fail}: unknown progress_id`);
|
|
14763
|
+
try {
|
|
14764
|
+
await handle.fail(reason);
|
|
14765
|
+
this.handles.delete(id);
|
|
14766
|
+
return okResult("ok");
|
|
14767
|
+
} catch (err) {
|
|
14768
|
+
return errResult(`${this.toolNames.fail} failed: ${err.message ?? String(err)}`);
|
|
14769
|
+
}
|
|
14770
|
+
}
|
|
14771
|
+
};
|
|
14772
|
+
function okResult(text) {
|
|
14773
|
+
return { content: [{ type: "text", text }] };
|
|
14774
|
+
}
|
|
14775
|
+
function errResult(text) {
|
|
14776
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
14777
|
+
}
|
|
14778
|
+
function isMode(value) {
|
|
14779
|
+
return value === "thinking" || value === "working" || value === "waiting";
|
|
14780
|
+
}
|
|
14781
|
+
|
|
14326
14782
|
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
14327
14783
|
import process2 from "process";
|
|
14328
14784
|
|
|
@@ -14436,7 +14892,7 @@ import {
|
|
|
14436
14892
|
} from "fs";
|
|
14437
14893
|
import { basename, join as join2, resolve as resolve2 } from "path";
|
|
14438
14894
|
import { homedir as homedir2 } from "os";
|
|
14439
|
-
import { createHash, randomUUID } from "crypto";
|
|
14895
|
+
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
14440
14896
|
|
|
14441
14897
|
// src/slack-thread-store.ts
|
|
14442
14898
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -15754,7 +16210,7 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
15754
16210
|
...payload.thread_ts ? { thread_ts: payload.thread_ts } : {}
|
|
15755
16211
|
}
|
|
15756
16212
|
};
|
|
15757
|
-
const tmpPath = `${flagPath}.${process.pid}.${
|
|
16213
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
15758
16214
|
writeFileSync2(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
15759
16215
|
renameSync(tmpPath, flagPath);
|
|
15760
16216
|
process.stderr.write(
|
|
@@ -15862,7 +16318,7 @@ async function handleRestartCommand(opts) {
|
|
|
15862
16318
|
message_ts: opts.ts
|
|
15863
16319
|
}
|
|
15864
16320
|
};
|
|
15865
|
-
const tmpPath = `${flagPath}.${process.pid}.${
|
|
16321
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
15866
16322
|
writeFileSync2(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
15867
16323
|
renameSync(tmpPath, flagPath);
|
|
15868
16324
|
process.stderr.write(
|
|
@@ -16011,6 +16467,7 @@ var mcp = new Server(
|
|
|
16011
16467
|
// 2048 chars, so anything appended late silently disappears.
|
|
16012
16468
|
"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.",
|
|
16013
16469
|
'Messages from Slack arrive as <channel source="slack" user="<slack-id>" user_name="<display-name>" channel="..." thread_ts="...">. Pass channel + thread_ts from the tag to slack.reply; always include thread_ts on threaded replies.',
|
|
16470
|
+
"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.",
|
|
16014
16471
|
selfIdentityInstruction,
|
|
16015
16472
|
"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.",
|
|
16016
16473
|
"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.",
|
|
@@ -16021,8 +16478,31 @@ var mcp = new Server(
|
|
|
16021
16478
|
].join(" ")
|
|
16022
16479
|
}
|
|
16023
16480
|
);
|
|
16481
|
+
var progressRegistry = new ProgressToolRegistry({
|
|
16482
|
+
prefix: "slack",
|
|
16483
|
+
surfaceDescription: "a Slack thread",
|
|
16484
|
+
startArgsSchema: {
|
|
16485
|
+
properties: {
|
|
16486
|
+
channel: {
|
|
16487
|
+
type: "string",
|
|
16488
|
+
description: "Slack channel id (from the `channel` attribute on the inbound <channel> tag)."
|
|
16489
|
+
},
|
|
16490
|
+
thread_ts: {
|
|
16491
|
+
type: "string",
|
|
16492
|
+
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."
|
|
16493
|
+
}
|
|
16494
|
+
},
|
|
16495
|
+
required: ["channel"]
|
|
16496
|
+
},
|
|
16497
|
+
createFlush: (args) => createSlackProgressFlush({
|
|
16498
|
+
botToken: BOT_TOKEN,
|
|
16499
|
+
channel: args.channel,
|
|
16500
|
+
threadTs: typeof args.thread_ts === "string" ? args.thread_ts : void 0
|
|
16501
|
+
})
|
|
16502
|
+
});
|
|
16024
16503
|
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16025
16504
|
tools: [
|
|
16505
|
+
...progressRegistry.getDefinitions(),
|
|
16026
16506
|
{
|
|
16027
16507
|
name: "slack.reply",
|
|
16028
16508
|
description: "Send a message back to a Slack channel or thread",
|
|
@@ -16221,6 +16701,8 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
16221
16701
|
}));
|
|
16222
16702
|
mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
16223
16703
|
const { name, arguments: args } = req.params;
|
|
16704
|
+
const progressResult = await progressRegistry.handle(name, args ?? {});
|
|
16705
|
+
if (progressResult !== void 0) return progressResult;
|
|
16224
16706
|
if (name === "slack.reply") {
|
|
16225
16707
|
const { channel, text, thread_ts } = args;
|
|
16226
16708
|
if (channel && thread_ts) {
|
|
@@ -16757,15 +17239,15 @@ async function handleSendStructured(args) {
|
|
|
16757
17239
|
const { validateSlackBlocks: validateSlackBlocks2 } = runtime;
|
|
16758
17240
|
const { channel, blocks, text, thread_ts, interactive } = args;
|
|
16759
17241
|
if (typeof channel !== "string" || !channel) {
|
|
16760
|
-
return
|
|
17242
|
+
return errResult2("channel is required");
|
|
16761
17243
|
}
|
|
16762
17244
|
if (typeof text !== "string" || !text) {
|
|
16763
|
-
return
|
|
17245
|
+
return errResult2("text is required (used as fallback for push notifications and unfurls)");
|
|
16764
17246
|
}
|
|
16765
17247
|
noteThreadActivity(channel, thread_ts);
|
|
16766
17248
|
const validation = validateSlackBlocks2(blocks);
|
|
16767
17249
|
if (!validation.ok) {
|
|
16768
|
-
return
|
|
17250
|
+
return errResult2(`Invalid blocks: ${validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
16769
17251
|
}
|
|
16770
17252
|
let effectiveBlocks = blocks;
|
|
16771
17253
|
let ratingCallbackId = null;
|
|
@@ -16773,13 +17255,13 @@ async function handleSendStructured(args) {
|
|
|
16773
17255
|
let ratingTokenised = [];
|
|
16774
17256
|
if (interactive) {
|
|
16775
17257
|
if (interactive.type !== "rate_run") {
|
|
16776
|
-
return
|
|
17258
|
+
return errResult2(`Unsupported interactive.type '${interactive.type}'. Supported: rate_run.`);
|
|
16777
17259
|
}
|
|
16778
17260
|
if (typeof interactive.run_id !== "string" || !interactive.run_id) {
|
|
16779
|
-
return
|
|
17261
|
+
return errResult2("interactive.run_id is required when interactive is set");
|
|
16780
17262
|
}
|
|
16781
17263
|
if (!interactiveHostAvailable()) {
|
|
16782
|
-
return
|
|
17264
|
+
return errResult2(
|
|
16783
17265
|
"interactive=rate_run requires Block Kit plus host MCP wiring (AGT_HOST, AGT_API_KEY, AGT_AGENT_ID)."
|
|
16784
17266
|
);
|
|
16785
17267
|
}
|
|
@@ -16810,7 +17292,7 @@ async function handleSendStructured(args) {
|
|
|
16810
17292
|
effectiveBlocks = [...blocks, ratingBlock];
|
|
16811
17293
|
const finalValidation = runtime.validateSlackBlocks(effectiveBlocks);
|
|
16812
17294
|
if (!finalValidation.ok) {
|
|
16813
|
-
return
|
|
17295
|
+
return errResult2(
|
|
16814
17296
|
`Invalid blocks (after appending rating row): ${finalValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
|
|
16815
17297
|
);
|
|
16816
17298
|
}
|
|
@@ -16831,7 +17313,7 @@ async function handleSendStructured(args) {
|
|
|
16831
17313
|
kindData: { run_id: interactive.run_id, scale }
|
|
16832
17314
|
});
|
|
16833
17315
|
} catch (err) {
|
|
16834
|
-
return
|
|
17316
|
+
return errResult2(`Failed to register rating row: ${err.message}`);
|
|
16835
17317
|
}
|
|
16836
17318
|
}
|
|
16837
17319
|
const result = await postSlackMessageWithTs({
|
|
@@ -16841,7 +17323,7 @@ async function handleSendStructured(args) {
|
|
|
16841
17323
|
...thread_ts ? { thread_ts } : {}
|
|
16842
17324
|
});
|
|
16843
17325
|
if (!result.ok || !result.ts) {
|
|
16844
|
-
return
|
|
17326
|
+
return errResult2(`Slack chat.postMessage failed: ${result.error ?? "unknown"}`);
|
|
16845
17327
|
}
|
|
16846
17328
|
if (ratingCallbackId) {
|
|
16847
17329
|
try {
|
|
@@ -16896,17 +17378,17 @@ async function handleSendStructured(args) {
|
|
|
16896
17378
|
}
|
|
16897
17379
|
async function handleAskUser(args) {
|
|
16898
17380
|
if (!askUserToolAvailable()) {
|
|
16899
|
-
return
|
|
17381
|
+
return errResult2("slack.ask_user is disabled or the host MCP is missing AGT_HOST/AGT_API_KEY/AGT_AGENT_ID env wiring.");
|
|
16900
17382
|
}
|
|
16901
17383
|
const { randomUUID: rndUUID } = await import("crypto");
|
|
16902
17384
|
const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
|
|
16903
17385
|
const { validateAskUserOptions: validateAskUserOptions2, buildAskUserBlocks: buildAskUserBlocks2 } = runtime;
|
|
16904
17386
|
const { channel, question, options, timeout_seconds, thread_ts } = args;
|
|
16905
|
-
if (typeof channel !== "string" || !channel) return
|
|
16906
|
-
if (typeof question !== "string" || !question) return
|
|
17387
|
+
if (typeof channel !== "string" || !channel) return errResult2("channel is required");
|
|
17388
|
+
if (typeof question !== "string" || !question) return errResult2("question is required");
|
|
16907
17389
|
const optsValidation = validateAskUserOptions2(options);
|
|
16908
17390
|
if (!optsValidation.ok) {
|
|
16909
|
-
return
|
|
17391
|
+
return errResult2(`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
16910
17392
|
}
|
|
16911
17393
|
const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
|
|
16912
17394
|
const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
|
|
@@ -16939,7 +17421,7 @@ async function handleAskUser(args) {
|
|
|
16939
17421
|
expiresAt
|
|
16940
17422
|
});
|
|
16941
17423
|
} catch (err) {
|
|
16942
|
-
return
|
|
17424
|
+
return errResult2(`Failed to register pending interaction: ${err.message}`);
|
|
16943
17425
|
}
|
|
16944
17426
|
const slackResult = await postSlackMessageWithTs({
|
|
16945
17427
|
channel,
|
|
@@ -16949,7 +17431,7 @@ async function handleAskUser(args) {
|
|
|
16949
17431
|
...thread_ts ? { thread_ts } : {}
|
|
16950
17432
|
});
|
|
16951
17433
|
if (!slackResult.ok || !slackResult.ts) {
|
|
16952
|
-
return
|
|
17434
|
+
return errResult2(`Slack chat.postMessage failed: ${slackResult.error ?? "unknown"}`);
|
|
16953
17435
|
}
|
|
16954
17436
|
try {
|
|
16955
17437
|
await runtime.updatePendingInteractionMessageTs(cfg, callbackId, slackResult.ts);
|
|
@@ -16994,7 +17476,7 @@ async function handleAskUser(args) {
|
|
|
16994
17476
|
]
|
|
16995
17477
|
};
|
|
16996
17478
|
}
|
|
16997
|
-
function
|
|
17479
|
+
function errResult2(text) {
|
|
16998
17480
|
return { content: [{ type: "text", text }], isError: true };
|
|
16999
17481
|
}
|
|
17000
17482
|
var MAX_INBOUND_FILE_BYTES = 25 * 1024 * 1024;
|