@integrity-labs/agt-cli 0.28.6 → 0.28.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.
@@ -13971,9 +13971,49 @@ function buildImpersonationRefusal(toolName) {
13971
13971
  // src/channel-egress-tools.ts
13972
13972
  var DIRECT_CHAT_EGRESS_TOOLS = /* @__PURE__ */ new Set([
13973
13973
  "direct_chat.reply",
13974
+ "direct_chat.consume",
13974
13975
  "channel_request_input"
13975
13976
  ]);
13976
13977
 
13978
+ // src/direct-chat-claim-tracker.ts
13979
+ var DirectChatClaimTracker = class {
13980
+ bySession = /* @__PURE__ */ new Map();
13981
+ /** Record that `messageId` was claimed for `sessionId` (idempotent). */
13982
+ track(sessionId, messageId) {
13983
+ let set = this.bySession.get(sessionId);
13984
+ if (!set) {
13985
+ set = /* @__PURE__ */ new Set();
13986
+ this.bySession.set(sessionId, set);
13987
+ }
13988
+ set.add(messageId);
13989
+ }
13990
+ /**
13991
+ * The claimed-but-unconsumed message ids for a session, WITHOUT clearing them.
13992
+ * Peek-then-clear-on-success (rather than drain-up-front) so a failed
13993
+ * reply/consume POST doesn't lose the ids — they stay tracked for the retry.
13994
+ */
13995
+ peek(sessionId) {
13996
+ return [...this.bySession.get(sessionId) ?? []];
13997
+ }
13998
+ /**
13999
+ * Clear exactly the given ids for a session (call after a successful
14000
+ * reply/consume). Only the ids actually sent are cleared, so any message
14001
+ * claimed between peek and clear stays tracked for the next turn.
14002
+ */
14003
+ clear(sessionId, ids) {
14004
+ const set = this.bySession.get(sessionId);
14005
+ if (!set) return;
14006
+ for (const id of ids) set.delete(id);
14007
+ if (set.size === 0) this.bySession.delete(sessionId);
14008
+ }
14009
+ /** Total tracked message ids across all sessions (for bounding / diagnostics). */
14010
+ get size() {
14011
+ let n = 0;
14012
+ for (const set of this.bySession.values()) n += set.size;
14013
+ return n;
14014
+ }
14015
+ };
14016
+
13977
14017
  // src/mcp-spawn-lock.ts
13978
14018
  import {
13979
14019
  existsSync,
@@ -14044,12 +14084,52 @@ function readLockHolder(path) {
14044
14084
  }
14045
14085
 
14046
14086
  // src/direct-chat-channel.ts
14087
+ import { homedir as homedir2 } from "os";
14088
+ import { join as join3 } from "path";
14089
+ import { watch, mkdirSync as mkdirSync2 } from "fs";
14090
+
14091
+ // src/flags-cache-read.ts
14092
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
14047
14093
  import { homedir } from "os";
14048
14094
  import { join as join2 } from "path";
14095
+ function defaultFlagsCachePath() {
14096
+ return join2(homedir(), ".augmented", "flags-cache.json");
14097
+ }
14098
+ function envBoolean(raw) {
14099
+ if (raw === void 0) return void 0;
14100
+ const v = raw.trim().toLowerCase();
14101
+ if (v === "") return void 0;
14102
+ if (v === "1" || v === "true" || v === "yes" || v === "on") return true;
14103
+ if (v === "0" || v === "false" || v === "no" || v === "off") return false;
14104
+ return void 0;
14105
+ }
14106
+ function cachedBoolean(key, path) {
14107
+ try {
14108
+ if (!existsSync2(path)) return void 0;
14109
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
14110
+ if (!parsed || typeof parsed !== "object") return void 0;
14111
+ const flags = parsed.flags;
14112
+ if (!flags || typeof flags !== "object") return void 0;
14113
+ const value = flags[key];
14114
+ return typeof value === "boolean" ? value : void 0;
14115
+ } catch {
14116
+ return void 0;
14117
+ }
14118
+ }
14119
+ function resolveHostBooleanFlag(opts) {
14120
+ const env = opts.env ?? process.env;
14121
+ const envValue = envBoolean(env[opts.envVar]);
14122
+ if (envValue !== void 0) return envValue;
14123
+ const cached2 = cachedBoolean(opts.key, opts.cachePath ?? defaultFlagsCachePath());
14124
+ if (cached2 !== void 0) return cached2;
14125
+ return opts.defaultValue;
14126
+ }
14127
+
14128
+ // src/direct-chat-channel.ts
14049
14129
  var AGT_HOST = process.env.AGT_HOST;
14050
14130
  var AGT_API_KEY = process.env.AGT_API_KEY;
14051
14131
  var AGT_AGENT_ID = process.env.AGT_AGENT_ID;
14052
- var DIRECT_CHAT_AGENT_DIR = AGT_AGENT_ID ? join2(homedir(), ".augmented", AGT_AGENT_ID) : null;
14132
+ var DIRECT_CHAT_AGENT_DIR = AGT_AGENT_ID ? join3(homedir2(), ".augmented", AGT_AGENT_ID) : null;
14053
14133
  var inboundContextClient = createInboundContextClient({
14054
14134
  agtHost: AGT_HOST ?? null,
14055
14135
  agtApiKey: AGT_API_KEY ?? null,
@@ -14125,10 +14205,12 @@ var mcp = new Server(
14125
14205
  'Messages from the webapp Direct Chat arrive as <channel source="direct-chat" session_id="..." user="...">.',
14126
14206
  "Reply using the direct_chat.reply tool, passing the session_id from the tag.",
14127
14207
  "Always reply to every direct chat message \u2014 the user is waiting in the webapp.",
14208
+ "In the rare case you deliberately handle a message without replying (e.g. an internal command), call direct_chat.consume with the session_id so it is not treated as undelivered and redelivered.",
14128
14209
  "Keep replies concise and helpful. You have full access to all your MCP tools."
14129
14210
  ].join(" ")
14130
14211
  }
14131
14212
  );
14213
+ var claimTracker = new DirectChatClaimTracker();
14132
14214
  mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
14133
14215
  tools: [
14134
14216
  {
@@ -14185,6 +14267,20 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
14185
14267
  },
14186
14268
  required: ["session_id", "content"]
14187
14269
  }
14270
+ },
14271
+ {
14272
+ name: "direct_chat.consume",
14273
+ description: "Acknowledge direct-chat message(s) you handled WITHOUT sending a reply (e.g. a command that only updated state, or a message you deliberately ignored). Pass the session_id from the <channel> tag. If you send a direct_chat.reply you do NOT also need to consume \u2014 the reply already acknowledges the message. Without one of the two, a silently-handled message is treated as undelivered and may be redelivered.",
14274
+ inputSchema: {
14275
+ type: "object",
14276
+ properties: {
14277
+ session_id: {
14278
+ type: "string",
14279
+ description: "Session ID (from the session_id attribute in the <channel> tag)"
14280
+ }
14281
+ },
14282
+ required: ["session_id"]
14283
+ }
14188
14284
  }
14189
14285
  ]
14190
14286
  }));
@@ -14206,11 +14302,13 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
14206
14302
  }
14207
14303
  if (name === "direct_chat.reply") {
14208
14304
  const { session_id, content } = args;
14305
+ const message_ids = claimTracker.peek(session_id);
14209
14306
  try {
14210
14307
  const res = await apiPost("/host/direct-chat/reply", {
14211
14308
  agent_id: AGT_AGENT_ID,
14212
14309
  session_id,
14213
- content
14310
+ content,
14311
+ message_ids
14214
14312
  });
14215
14313
  const data = await res.json();
14216
14314
  if (!res.ok || data.error) {
@@ -14219,6 +14317,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
14219
14317
  isError: true
14220
14318
  };
14221
14319
  }
14320
+ claimTracker.clear(session_id, message_ids);
14222
14321
  return { content: [{ type: "text", text: "sent" }] };
14223
14322
  } catch (err) {
14224
14323
  return {
@@ -14227,9 +14326,80 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
14227
14326
  };
14228
14327
  }
14229
14328
  }
14329
+ if (name === "direct_chat.consume") {
14330
+ const { session_id } = args;
14331
+ const message_ids = claimTracker.peek(session_id);
14332
+ if (message_ids.length === 0) {
14333
+ return { content: [{ type: "text", text: "nothing to consume" }] };
14334
+ }
14335
+ try {
14336
+ const res = await apiPost("/host/direct-chat/consume", {
14337
+ agent_id: AGT_AGENT_ID,
14338
+ session_id,
14339
+ message_ids
14340
+ });
14341
+ const data = await res.json();
14342
+ if (!res.ok || data.error) {
14343
+ return {
14344
+ content: [{ type: "text", text: `Consume failed: ${data.error ?? res.statusText}` }],
14345
+ isError: true
14346
+ };
14347
+ }
14348
+ claimTracker.clear(session_id, message_ids);
14349
+ return { content: [{ type: "text", text: "consumed" }] };
14350
+ } catch (err) {
14351
+ return {
14352
+ content: [{ type: "text", text: `Failed: ${err.message}` }],
14353
+ isError: true
14354
+ };
14355
+ }
14356
+ }
14230
14357
  throw new Error(`Unknown tool: ${name}`);
14231
14358
  });
14232
14359
  await mcp.connect(new StdioServerTransport());
14360
+ var processedIds = /* @__PURE__ */ new Set();
14361
+ async function pollForMessages() {
14362
+ try {
14363
+ const res = await apiPost("/host/direct-chat/poll", {
14364
+ agent_id: AGT_AGENT_ID
14365
+ });
14366
+ if (!res.ok) return;
14367
+ const data = await res.json();
14368
+ for (const msg of data.messages ?? []) {
14369
+ if (processedIds.has(msg.id)) continue;
14370
+ processedIds.add(msg.id);
14371
+ if (processedIds.size > 500) {
14372
+ const ids = [...processedIds];
14373
+ for (let i = 0; i < 250; i++) processedIds.delete(ids[i]);
14374
+ }
14375
+ claimTracker.track(msg.session_id, msg.id);
14376
+ await mcp.notification({
14377
+ method: "notifications/claude/channel",
14378
+ params: {
14379
+ content: msg.content,
14380
+ meta: {
14381
+ session_id: msg.session_id,
14382
+ user: "webapp",
14383
+ source: "direct-chat"
14384
+ }
14385
+ }
14386
+ });
14387
+ inboundContextClient?.recordInbound({
14388
+ sourceIntegration: "direct-chat",
14389
+ sourceExternalId: msg.session_id
14390
+ });
14391
+ process.stderr.write(
14392
+ `direct-chat-channel: Injected message ${msg.id} (session=${msg.session_id})
14393
+ `
14394
+ );
14395
+ }
14396
+ } catch (err) {
14397
+ process.stderr.write(
14398
+ `direct-chat-channel: Poll error: ${err.message}
14399
+ `
14400
+ );
14401
+ }
14402
+ }
14233
14403
  var acquiredLockPath = null;
14234
14404
  {
14235
14405
  const lockResult = acquireMcpSpawnLock({
@@ -14246,16 +14416,71 @@ var acquiredLockPath = null;
14246
14416
  acquiredLockPath = lockResult.path;
14247
14417
  }
14248
14418
  }
14249
- process.stderr.write(
14250
- `direct-chat-channel: Started (agent=${AGT_AGENT_ID}, polling=disabled \u2014 using Realtime)
14419
+ var DOORBELL_ENABLED = resolveHostBooleanFlag({
14420
+ key: "direct-chat-doorbell",
14421
+ envVar: "AGT_DIRECT_CHAT_DOORBELL_ENABLED",
14422
+ defaultValue: false
14423
+ });
14424
+ var DOORBELL_FILE = "direct-chat-doorbell";
14425
+ var SAFETY_NET_POLL_MS = 3e4;
14426
+ var pollInFlight = false;
14427
+ async function pollOnce() {
14428
+ if (pollInFlight) return;
14429
+ pollInFlight = true;
14430
+ try {
14431
+ await pollForMessages();
14432
+ } finally {
14433
+ pollInFlight = false;
14434
+ }
14435
+ }
14436
+ var doorbellWatcher = null;
14437
+ var safetyNetTimer = null;
14438
+ if (DOORBELL_ENABLED && DIRECT_CHAT_AGENT_DIR) {
14439
+ try {
14440
+ mkdirSync2(DIRECT_CHAT_AGENT_DIR, { recursive: true });
14441
+ } catch {
14442
+ }
14443
+ let debounce = null;
14444
+ try {
14445
+ doorbellWatcher = watch(DIRECT_CHAT_AGENT_DIR, (_event, filename) => {
14446
+ if (filename !== DOORBELL_FILE) return;
14447
+ if (debounce) clearTimeout(debounce);
14448
+ debounce = setTimeout(() => {
14449
+ void pollOnce();
14450
+ }, 50);
14451
+ });
14452
+ } catch (err) {
14453
+ process.stderr.write(
14454
+ `direct-chat-channel: doorbell watch failed (${err.message}) \u2014 relying on safety-net poll
14251
14455
  `
14252
- );
14456
+ );
14457
+ }
14458
+ safetyNetTimer = setInterval(() => {
14459
+ void pollOnce();
14460
+ }, SAFETY_NET_POLL_MS);
14461
+ safetyNetTimer.unref?.();
14462
+ void pollOnce();
14463
+ process.stderr.write(
14464
+ `direct-chat-channel: Started (agent=${AGT_AGENT_ID}, delivery=doorbell+pull \u2014 watching ${DOORBELL_FILE}, safety-net ${SAFETY_NET_POLL_MS}ms)
14465
+ `
14466
+ );
14467
+ } else {
14468
+ process.stderr.write(
14469
+ `direct-chat-channel: Started (agent=${AGT_AGENT_ID}, polling=disabled \u2014 using Realtime)
14470
+ `
14471
+ );
14472
+ }
14253
14473
  var isShuttingDown = false;
14254
14474
  function shutdown(reason) {
14255
14475
  if (isShuttingDown) return;
14256
14476
  isShuttingDown = true;
14257
14477
  process.stderr.write(`direct-chat-channel: ${reason} \u2014 exiting
14258
14478
  `);
14479
+ try {
14480
+ doorbellWatcher?.close();
14481
+ } catch {
14482
+ }
14483
+ if (safetyNetTimer) clearInterval(safetyNetTimer);
14259
14484
  try {
14260
14485
  releaseMcpSpawnLock(acquiredLockPath);
14261
14486
  } catch {
@@ -23,8 +23,8 @@ import {
23
23
  stopPersistentSession,
24
24
  takeZombieDetection,
25
25
  writePersistentClaudeWrapper
26
- } from "./chunk-LIB6VTH3.js";
27
- import "./chunk-NS4G4HHD.js";
26
+ } from "./chunk-QG553V7F.js";
27
+ import "./chunk-CHUL4CPY.js";
28
28
  import "./chunk-XWVM4KPK.js";
29
29
  export {
30
30
  SEND_KEYS_ENTER_DELAY_MS,
@@ -52,4 +52,4 @@ export {
52
52
  takeZombieDetection,
53
53
  writePersistentClaudeWrapper
54
54
  };
55
- //# sourceMappingURL=persistent-session-DSG4HI4R.js.map
55
+ //# sourceMappingURL=persistent-session-S6H3P6WD.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-LIB6VTH3.js";
4
- import "./chunk-NS4G4HHD.js";
3
+ } from "./chunk-QG553V7F.js";
4
+ import "./chunk-CHUL4CPY.js";
5
5
  import "./chunk-XWVM4KPK.js";
6
6
 
7
7
  // src/lib/responsiveness-probe.ts
@@ -248,4 +248,4 @@ export {
248
248
  parkPendingInbound,
249
249
  readAndResetChannelDeflections
250
250
  };
251
- //# sourceMappingURL=responsiveness-probe-53KDTOUT.js.map
251
+ //# sourceMappingURL=responsiveness-probe-6BLLBJB4.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.6",
3
+ "version": "0.28.8",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {