@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.
@@ -22,7 +22,7 @@ import {
22
22
  resolveChannels,
23
23
  resolveDmTarget,
24
24
  wrapScheduledTaskPrompt
25
- } from "../chunk-SOYIZEZG.js";
25
+ } from "../chunk-5A5EBN5L.js";
26
26
  import {
27
27
  findTaskByTemplate,
28
28
  getProjectDir,
@@ -2200,7 +2200,7 @@ function clearAgentCaches(agentId, codeName) {
2200
2200
  var cachedFrameworkVersion = null;
2201
2201
  var lastVersionCheckAt = 0;
2202
2202
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
2203
- var agtCliVersion = true ? "0.20.6" : "dev";
2203
+ var agtCliVersion = true ? "0.20.8" : "dev";
2204
2204
  function resolveBrewPath(execFileSync4) {
2205
2205
  try {
2206
2206
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -13861,6 +13861,438 @@ var StdioServerTransport = class {
13861
13861
  }
13862
13862
  };
13863
13863
 
13864
+ // src/direct-chat-progress.ts
13865
+ var MODE_EMOJI = {
13866
+ thinking: "\u{1F4AD}",
13867
+ working: "\u{1F6E0}\uFE0F",
13868
+ waiting: "\u23F3"
13869
+ };
13870
+ var TERMINAL_EMOJI = {
13871
+ completed: "\u2705",
13872
+ failed: "\u274C"
13873
+ };
13874
+ function formatWallClock(ms) {
13875
+ const d = new Date(ms);
13876
+ const hh = String(d.getUTCHours()).padStart(2, "0");
13877
+ const mm = String(d.getUTCMinutes()).padStart(2, "0");
13878
+ const ss = String(d.getUTCSeconds()).padStart(2, "0");
13879
+ return `${hh}:${mm}:${ss} UTC`;
13880
+ }
13881
+ function renderProgressMarkdown(state) {
13882
+ const lines = [];
13883
+ if (state.terminal) {
13884
+ const banner = state.terminal.kind === "completed" ? "Done" : "Failed";
13885
+ lines.push(`${TERMINAL_EMOJI[state.terminal.kind]} **${banner}** \u2014 ${state.initialLabel}`);
13886
+ if (state.terminal.message) lines.push(state.terminal.message);
13887
+ lines.push(`_completed: ${formatWallClock(state.lastChangeAt)}_`);
13888
+ return lines.join("\n");
13889
+ }
13890
+ lines.push(`${MODE_EMOJI[state.mode]} **Working on:** ${state.initialLabel}`);
13891
+ if (state.stepNumber || state.stepLabel) {
13892
+ const stepParts = [];
13893
+ if (state.stepNumber) {
13894
+ if (state.stepNumber.total != null) {
13895
+ stepParts.push(`**Step ${state.stepNumber.current} of ${state.stepNumber.total}**`);
13896
+ } else {
13897
+ stepParts.push(`**Step ${state.stepNumber.current}**`);
13898
+ }
13899
+ }
13900
+ if (state.stepLabel) stepParts.push(state.stepLabel);
13901
+ lines.push(stepParts.join(": "));
13902
+ }
13903
+ if (state.detail) lines.push(`_${state.detail}_`);
13904
+ lines.push(`_last update: ${formatWallClock(state.lastChangeAt)}_`);
13905
+ return lines.join("\n");
13906
+ }
13907
+ var DirectChatApiError = class extends Error {
13908
+ constructor(endpoint, apiError, status) {
13909
+ super(`/host/${endpoint} failed: ${apiError} (status=${status})`);
13910
+ this.endpoint = endpoint;
13911
+ this.apiError = apiError;
13912
+ this.status = status;
13913
+ this.name = "DirectChatApiError";
13914
+ }
13915
+ };
13916
+ function createDirectChatProgressFlush(opts) {
13917
+ const fetchImpl = opts.fetchImpl ?? fetch;
13918
+ const timeoutMs = opts.timeoutMs ?? 5e3;
13919
+ let anchorMessageId = null;
13920
+ async function apiCall(endpoint, body) {
13921
+ const token = await opts.getAuthToken();
13922
+ const controller = new AbortController();
13923
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
13924
+ let response;
13925
+ try {
13926
+ response = await fetchImpl(`${opts.agtHost}/host/${endpoint}`, {
13927
+ method: "POST",
13928
+ headers: {
13929
+ "Content-Type": "application/json",
13930
+ Authorization: `Bearer ${token}`
13931
+ },
13932
+ body: JSON.stringify(body),
13933
+ signal: controller.signal
13934
+ });
13935
+ } finally {
13936
+ clearTimeout(timer);
13937
+ }
13938
+ const parsed = await response.json().catch(() => ({ ok: false, error: "invalid_json" }));
13939
+ if (!response.ok || !parsed.ok) {
13940
+ throw new DirectChatApiError(
13941
+ endpoint,
13942
+ parsed.error ?? `http ${response.status}`,
13943
+ response.status
13944
+ );
13945
+ }
13946
+ return parsed;
13947
+ }
13948
+ return async function flush(state) {
13949
+ const content = renderProgressMarkdown(state);
13950
+ if (anchorMessageId == null) {
13951
+ const res = await apiCall("direct-chat/reply", {
13952
+ agent_id: opts.agentId,
13953
+ session_id: opts.sessionId,
13954
+ content
13955
+ });
13956
+ if (res.message_id) {
13957
+ anchorMessageId = res.message_id;
13958
+ opts.onAnchorPosted?.(res.message_id);
13959
+ }
13960
+ return;
13961
+ }
13962
+ try {
13963
+ await apiCall("direct-chat/update", {
13964
+ agent_id: opts.agentId,
13965
+ session_id: opts.sessionId,
13966
+ message_id: anchorMessageId,
13967
+ content
13968
+ });
13969
+ } catch (err) {
13970
+ if (opts.onApiError && err instanceof DirectChatApiError) {
13971
+ await opts.onApiError(err);
13972
+ return;
13973
+ }
13974
+ throw err;
13975
+ }
13976
+ };
13977
+ }
13978
+
13979
+ // src/progress-tools.ts
13980
+ import { randomUUID } from "crypto";
13981
+
13982
+ // src/progress-handle.ts
13983
+ async function startProgressHandle(opts) {
13984
+ const now = opts.now ?? (() => Date.now());
13985
+ const setTimer = opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
13986
+ const clearTimer = opts.clearTimer ?? ((id) => clearTimeout(id));
13987
+ const debounceMs = opts.debounceMs ?? 1e3;
13988
+ const onPostTerminalUpdate = opts.onPostTerminalUpdate ?? defaultPostTerminalWarn;
13989
+ const state = {
13990
+ initialLabel: opts.initialLabel,
13991
+ mode: opts.initialMode ?? "working",
13992
+ lastChangeAt: now()
13993
+ };
13994
+ let lastFlushAt = 0;
13995
+ let pendingTimer = null;
13996
+ let inFlight = null;
13997
+ let terminal = false;
13998
+ async function doFlush() {
13999
+ while (inFlight) await inFlight;
14000
+ if (pendingTimer != null) {
14001
+ clearTimer(pendingTimer);
14002
+ pendingTimer = null;
14003
+ }
14004
+ lastFlushAt = now();
14005
+ const promise = opts.flush(snapshotState());
14006
+ inFlight = promise.then(
14007
+ () => {
14008
+ inFlight = null;
14009
+ },
14010
+ (err) => {
14011
+ inFlight = null;
14012
+ throw err;
14013
+ }
14014
+ );
14015
+ await inFlight;
14016
+ }
14017
+ function snapshotState() {
14018
+ return {
14019
+ initialLabel: state.initialLabel,
14020
+ mode: state.mode,
14021
+ stepLabel: state.stepLabel,
14022
+ stepNumber: state.stepNumber ? { ...state.stepNumber } : void 0,
14023
+ detail: state.detail,
14024
+ lastChangeAt: state.lastChangeAt,
14025
+ terminal: state.terminal ? { ...state.terminal } : void 0
14026
+ };
14027
+ }
14028
+ function applyUpdate(next) {
14029
+ if (next.mode !== void 0) state.mode = next.mode;
14030
+ if (next.stepLabel !== void 0) state.stepLabel = next.stepLabel;
14031
+ if (next.stepNumber !== void 0) state.stepNumber = { ...next.stepNumber };
14032
+ if (next.detail !== void 0) state.detail = next.detail;
14033
+ state.lastChangeAt = now();
14034
+ }
14035
+ async function scheduleOrFlush() {
14036
+ const elapsed = now() - lastFlushAt;
14037
+ if (elapsed >= debounceMs) {
14038
+ await doFlush();
14039
+ return;
14040
+ }
14041
+ if (pendingTimer != null) return;
14042
+ const remaining = debounceMs - elapsed;
14043
+ pendingTimer = setTimer(() => {
14044
+ pendingTimer = null;
14045
+ void doFlush().catch(() => {
14046
+ });
14047
+ }, remaining);
14048
+ }
14049
+ lastFlushAt = now();
14050
+ await opts.flush(snapshotState());
14051
+ return {
14052
+ snapshot: snapshotState,
14053
+ async update(next) {
14054
+ if (terminal) {
14055
+ const peeked = snapshotState();
14056
+ Object.assign(peeked, {
14057
+ mode: next.mode ?? peeked.mode,
14058
+ stepLabel: next.stepLabel ?? peeked.stepLabel,
14059
+ stepNumber: next.stepNumber ?? peeked.stepNumber,
14060
+ detail: next.detail ?? peeked.detail
14061
+ });
14062
+ try {
14063
+ onPostTerminalUpdate(peeked);
14064
+ } catch (err) {
14065
+ console.warn(
14066
+ "[progress-handle] onPostTerminalUpdate threw \u2014 swallowed to keep update() non-throwing:",
14067
+ err
14068
+ );
14069
+ }
14070
+ return;
14071
+ }
14072
+ applyUpdate(next);
14073
+ await scheduleOrFlush();
14074
+ },
14075
+ async complete(summary) {
14076
+ if (terminal) return;
14077
+ terminal = true;
14078
+ state.terminal = { kind: "completed", message: summary };
14079
+ state.lastChangeAt = now();
14080
+ await doFlush();
14081
+ },
14082
+ async fail(reason) {
14083
+ if (terminal) return;
14084
+ terminal = true;
14085
+ state.terminal = { kind: "failed", message: reason };
14086
+ state.lastChangeAt = now();
14087
+ await doFlush();
14088
+ },
14089
+ isTerminal: () => terminal
14090
+ };
14091
+ }
14092
+ function defaultPostTerminalWarn(state) {
14093
+ console.warn(
14094
+ `[progress-handle] update() called after terminal transition (${state.terminal?.kind}) \u2014 ignored.`
14095
+ );
14096
+ }
14097
+
14098
+ // src/progress-tools.ts
14099
+ var ProgressToolRegistry = class {
14100
+ constructor(opts) {
14101
+ this.opts = opts;
14102
+ this.toolNames = {
14103
+ start: `${opts.prefix}_progress_start`,
14104
+ update: `${opts.prefix}_progress_update`,
14105
+ complete: `${opts.prefix}_progress_complete`,
14106
+ fail: `${opts.prefix}_progress_fail`
14107
+ };
14108
+ }
14109
+ handles = /* @__PURE__ */ new Map();
14110
+ toolNames;
14111
+ /** Tool definitions to splice into the MCP ListToolsRequest response. */
14112
+ getDefinitions() {
14113
+ const { surfaceDescription, startArgsSchema } = this.opts;
14114
+ const debounceMs = this.opts.debounceMs ?? 1e3;
14115
+ const debounceText = debounceMs === 1e3 ? "per second" : `every ${debounceMs}ms`;
14116
+ return [
14117
+ {
14118
+ name: this.toolNames.start,
14119
+ 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.`,
14120
+ inputSchema: {
14121
+ type: "object",
14122
+ properties: {
14123
+ label: {
14124
+ type: "string",
14125
+ description: 'Short top-line label for the task (e.g. "diagnose vigil"). Stays constant for the lifetime of this progress anchor.'
14126
+ },
14127
+ initial_mode: {
14128
+ type: "string",
14129
+ enum: ["thinking", "working", "waiting"],
14130
+ description: 'Initial mode emoji \u2014 defaults to "working".'
14131
+ },
14132
+ ...startArgsSchema.properties
14133
+ },
14134
+ required: ["label", ...startArgsSchema.required]
14135
+ }
14136
+ },
14137
+ {
14138
+ name: this.toolNames.update,
14139
+ 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.`,
14140
+ inputSchema: {
14141
+ type: "object",
14142
+ properties: {
14143
+ progress_id: { type: "string", description: "The progress_id returned from start." },
14144
+ step_label: { type: "string", description: 'Short label for the current step (e.g. "Querying CloudWatch logs").' },
14145
+ step_current: { type: "number", description: "1-based step counter \u2014 current." },
14146
+ step_total: { type: "number", description: "Total steps if known. Omit for open-ended progress." },
14147
+ mode: { type: "string", enum: ["thinking", "working", "waiting"] },
14148
+ detail: { type: "string", description: "Free-text detail line under the step label." }
14149
+ },
14150
+ required: ["progress_id"]
14151
+ }
14152
+ },
14153
+ {
14154
+ name: this.toolNames.complete,
14155
+ 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.`,
14156
+ inputSchema: {
14157
+ type: "object",
14158
+ properties: {
14159
+ progress_id: { type: "string" },
14160
+ summary: { type: "string", description: "Optional one-line summary of what was achieved." }
14161
+ },
14162
+ required: ["progress_id"]
14163
+ }
14164
+ },
14165
+ {
14166
+ name: this.toolNames.fail,
14167
+ 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.`,
14168
+ inputSchema: {
14169
+ type: "object",
14170
+ properties: {
14171
+ progress_id: { type: "string" },
14172
+ reason: { type: "string", description: "One-sentence failure reason." }
14173
+ },
14174
+ required: ["progress_id", "reason"]
14175
+ }
14176
+ }
14177
+ ];
14178
+ }
14179
+ /**
14180
+ * Dispatch a CallToolRequest. Returns `undefined` if the tool name
14181
+ * isn't one of ours (so the caller can keep walking its dispatch
14182
+ * chain). Returns a tool result otherwise.
14183
+ */
14184
+ async handle(name, args) {
14185
+ if (name === this.toolNames.start) return this.handleStart(args);
14186
+ if (name === this.toolNames.update) return this.handleUpdate(args);
14187
+ if (name === this.toolNames.complete) return this.handleComplete(args);
14188
+ if (name === this.toolNames.fail) return this.handleFail(args);
14189
+ return void 0;
14190
+ }
14191
+ /** Live count of in-flight handles. Exposed for tests + diagnostics. */
14192
+ size() {
14193
+ return this.handles.size;
14194
+ }
14195
+ async handleStart(args) {
14196
+ const label = typeof args.label === "string" ? args.label : null;
14197
+ if (!label) return errResult(`${this.toolNames.start}: 'label' must be a non-empty string`);
14198
+ if ("initial_mode" in args && !isMode(args.initial_mode)) {
14199
+ return errResult(
14200
+ `${this.toolNames.start}: 'initial_mode' must be one of 'thinking', 'working', or 'waiting'`
14201
+ );
14202
+ }
14203
+ for (const k of this.opts.startArgsSchema.required) {
14204
+ if (typeof args[k] !== "string" || args[k].length === 0) {
14205
+ return errResult(`${this.toolNames.start}: '${k}' must be a non-empty string`);
14206
+ }
14207
+ }
14208
+ const initialMode = isMode(args.initial_mode) ? args.initial_mode : void 0;
14209
+ const progressId = randomUUID();
14210
+ try {
14211
+ const flush = this.opts.createFlush(args);
14212
+ const handle = await startProgressHandle({
14213
+ initialLabel: label,
14214
+ initialMode,
14215
+ flush,
14216
+ debounceMs: this.opts.debounceMs ?? 1e3
14217
+ });
14218
+ this.handles.set(progressId, handle);
14219
+ return okResult(JSON.stringify({ progress_id: progressId }));
14220
+ } catch (err) {
14221
+ return errResult(`${this.toolNames.start} failed: ${err.message ?? String(err)}`);
14222
+ }
14223
+ }
14224
+ async handleUpdate(args) {
14225
+ const id = typeof args.progress_id === "string" ? args.progress_id : null;
14226
+ if (!id) return errResult(`${this.toolNames.update}: 'progress_id' must be a string`);
14227
+ const handle = this.handles.get(id);
14228
+ if (!handle) return errResult(`${this.toolNames.update}: unknown progress_id (already terminated, or never started)`);
14229
+ if ("mode" in args && !isMode(args.mode)) {
14230
+ return errResult(
14231
+ `${this.toolNames.update}: 'mode' must be one of 'thinking', 'working', or 'waiting'`
14232
+ );
14233
+ }
14234
+ if (typeof args.step_total === "number" && typeof args.step_current !== "number") {
14235
+ return errResult(
14236
+ `${this.toolNames.update}: 'step_total' requires 'step_current'`
14237
+ );
14238
+ }
14239
+ const next = {};
14240
+ if (typeof args.step_label === "string") next.stepLabel = args.step_label;
14241
+ if (typeof args.step_current === "number") {
14242
+ next.stepNumber = {
14243
+ current: args.step_current,
14244
+ ...typeof args.step_total === "number" ? { total: args.step_total } : {}
14245
+ };
14246
+ }
14247
+ if (isMode(args.mode)) next.mode = args.mode;
14248
+ if (typeof args.detail === "string") next.detail = args.detail;
14249
+ try {
14250
+ await handle.update(next);
14251
+ return okResult("ok");
14252
+ } catch (err) {
14253
+ return errResult(`${this.toolNames.update} failed: ${err.message ?? String(err)}`);
14254
+ }
14255
+ }
14256
+ async handleComplete(args) {
14257
+ const id = typeof args.progress_id === "string" ? args.progress_id : null;
14258
+ if (!id) return errResult(`${this.toolNames.complete}: 'progress_id' must be a string`);
14259
+ const handle = this.handles.get(id);
14260
+ if (!handle) return errResult(`${this.toolNames.complete}: unknown progress_id`);
14261
+ const summary = typeof args.summary === "string" ? args.summary : void 0;
14262
+ try {
14263
+ await handle.complete(summary);
14264
+ this.handles.delete(id);
14265
+ return okResult("ok");
14266
+ } catch (err) {
14267
+ return errResult(`${this.toolNames.complete} failed: ${err.message ?? String(err)}`);
14268
+ }
14269
+ }
14270
+ async handleFail(args) {
14271
+ const id = typeof args.progress_id === "string" ? args.progress_id : null;
14272
+ if (!id) return errResult(`${this.toolNames.fail}: 'progress_id' must be a string`);
14273
+ const reason = typeof args.reason === "string" ? args.reason : null;
14274
+ if (!reason) return errResult(`${this.toolNames.fail}: 'reason' must be a non-empty string`);
14275
+ const handle = this.handles.get(id);
14276
+ if (!handle) return errResult(`${this.toolNames.fail}: unknown progress_id`);
14277
+ try {
14278
+ await handle.fail(reason);
14279
+ this.handles.delete(id);
14280
+ return okResult("ok");
14281
+ } catch (err) {
14282
+ return errResult(`${this.toolNames.fail} failed: ${err.message ?? String(err)}`);
14283
+ }
14284
+ }
14285
+ };
14286
+ function okResult(text) {
14287
+ return { content: [{ type: "text", text }] };
14288
+ }
14289
+ function errResult(text) {
14290
+ return { content: [{ type: "text", text }], isError: true };
14291
+ }
14292
+ function isMode(value) {
14293
+ return value === "thinking" || value === "working" || value === "waiting";
14294
+ }
14295
+
13864
14296
  // src/direct-chat-channel.ts
13865
14297
  var AGT_HOST = process.env.AGT_HOST;
13866
14298
  var AGT_API_KEY = process.env.AGT_API_KEY;
@@ -13925,12 +14357,33 @@ var mcp = new Server(
13925
14357
  'Messages from the webapp Direct Chat arrive as <channel source="direct-chat" session_id="..." user="...">.',
13926
14358
  "Reply using the direct_chat.reply tool, passing the session_id from the tag.",
13927
14359
  "Always reply to every direct chat message \u2014 the user is waiting in the webapp.",
14360
+ "Long task (3+ tool calls or >5s)? Use direct_chat_progress_start (session_id), then direct_chat_progress_update between steps, then direct_chat_progress_complete or _fail. One anchor edits in place rather than spawning a new reply each step. Skip for one-shot Q&A.",
13928
14361
  "Keep replies concise and helpful. You have full access to all your MCP tools."
13929
14362
  ].join(" ")
13930
14363
  }
13931
14364
  );
14365
+ var progressRegistry = new ProgressToolRegistry({
14366
+ prefix: "direct_chat",
14367
+ surfaceDescription: "a direct-chat session",
14368
+ startArgsSchema: {
14369
+ properties: {
14370
+ session_id: {
14371
+ type: "string",
14372
+ description: 'Session id (from the `session_id` attribute on the inbound <channel source="direct-chat"> tag).'
14373
+ }
14374
+ },
14375
+ required: ["session_id"]
14376
+ },
14377
+ createFlush: (args) => createDirectChatProgressFlush({
14378
+ agtHost: AGT_HOST,
14379
+ agentId: AGT_AGENT_ID,
14380
+ sessionId: args.session_id,
14381
+ getAuthToken
14382
+ })
14383
+ });
13932
14384
  mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
13933
14385
  tools: [
14386
+ ...progressRegistry.getDefinitions(),
13934
14387
  {
13935
14388
  name: "direct_chat.reply",
13936
14389
  description: "Send a reply back to the webapp Direct Chat",
@@ -13953,6 +14406,8 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
13953
14406
  }));
13954
14407
  mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
13955
14408
  const { name, arguments: args } = req.params;
14409
+ const progressResult = await progressRegistry.handle(name, args ?? {});
14410
+ if (progressResult !== void 0) return progressResult;
13956
14411
  if (name === "direct_chat.reply") {
13957
14412
  const { session_id, content } = args;
13958
14413
  try {