@integrity-labs/agt-cli 0.27.163 → 0.27.165

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.
@@ -14344,6 +14344,110 @@ function decideInboundAccess(input) {
14344
14344
  return { kind: "admit" };
14345
14345
  }
14346
14346
 
14347
+ // src/watch-command.ts
14348
+ var WATCH_DEFAULT_DURATION_MS = 2 * 60 * 60 * 1e3;
14349
+ var WATCH_MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1e3;
14350
+ var WATCH_MIN_DURATION_MS = 5 * 60 * 1e3;
14351
+ function extractDriveFileId(s) {
14352
+ const trimmed = s.trim().replace(/^<|>$/g, "");
14353
+ const pathId = trimmed.match(/\/d\/([A-Za-z0-9_-]{10,})/);
14354
+ if (pathId?.[1]) return pathId[1];
14355
+ const idParam = trimmed.match(/[?&]id=([A-Za-z0-9_-]{10,})/);
14356
+ if (idParam?.[1]) return idParam[1];
14357
+ if (!/[/:?]/.test(trimmed) && /^[A-Za-z0-9_-]{20,}$/.test(trimmed)) return trimmed;
14358
+ return null;
14359
+ }
14360
+ function parseDurationToken(token) {
14361
+ const m = token.trim().match(/^(\d+)\s*([smhd])?$/i);
14362
+ if (!m?.[1]) return null;
14363
+ const n = Number.parseInt(m[1], 10);
14364
+ if (!Number.isFinite(n) || n <= 0) return null;
14365
+ const unit = (m[2] ?? "m").toLowerCase();
14366
+ const mult = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
14367
+ return n * mult;
14368
+ }
14369
+ function parseWatchArgs(args) {
14370
+ const parts = args.trim().split(/\s+/).filter(Boolean);
14371
+ const first = parts[0];
14372
+ if (!first) return { ok: false, error: "usage" };
14373
+ if (parts.length > 2) return { ok: false, error: "usage" };
14374
+ const fileId = extractDriveFileId(first);
14375
+ if (!fileId) return { ok: false, error: "bad-url" };
14376
+ let durationMs = WATCH_DEFAULT_DURATION_MS;
14377
+ const durationToken = parts[1];
14378
+ if (durationToken !== void 0) {
14379
+ const parsed = parseDurationToken(durationToken);
14380
+ if (parsed === null) return { ok: false, error: "bad-duration" };
14381
+ durationMs = Math.min(Math.max(parsed, WATCH_MIN_DURATION_MS), WATCH_MAX_DURATION_MS);
14382
+ }
14383
+ return { ok: true, value: { fileId, durationMs } };
14384
+ }
14385
+ function watchArgsFromText(strippedText, codeName, opts) {
14386
+ const forms = [];
14387
+ if (opts?.allowBare ?? true) forms.push("/watch");
14388
+ if (codeName) forms.push(`/watch-${codeName}`);
14389
+ for (const f of forms) {
14390
+ if (strippedText === f) return "";
14391
+ if (strippedText.startsWith(`${f} `)) return strippedText.slice(f.length + 1).trim();
14392
+ }
14393
+ return null;
14394
+ }
14395
+ async function postWatchTrigger(opts) {
14396
+ const doFetch = opts.fetchImpl ?? fetch;
14397
+ try {
14398
+ const res = await doFetch(`${opts.host}/host/triggers`, {
14399
+ method: "POST",
14400
+ headers: {
14401
+ "Content-Type": "application/json; charset=utf-8",
14402
+ Authorization: `Bearer ${opts.apiKey}`
14403
+ },
14404
+ body: JSON.stringify({
14405
+ agent_id: opts.agentId,
14406
+ provider: "gdrive_comments",
14407
+ config: { fileId: opts.fileId },
14408
+ expires_at: opts.expiresAtIso
14409
+ }),
14410
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
14411
+ });
14412
+ if (!res.ok) {
14413
+ let msg = `HTTP ${res.status}`;
14414
+ try {
14415
+ const j2 = await res.json();
14416
+ if (j2?.error) msg = j2.error;
14417
+ } catch {
14418
+ }
14419
+ return { ok: false, status: res.status, error: msg };
14420
+ }
14421
+ const j = await res.json().catch(() => ({}));
14422
+ return { ok: true, status: res.status, reused: !!j.reused, expiresAt: j.expires_at ?? null };
14423
+ } catch (err) {
14424
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
14425
+ }
14426
+ }
14427
+ function humanDuration(ms) {
14428
+ const mins = Math.round(ms / 6e4);
14429
+ if (mins % 1440 === 0) return `${mins / 1440}d`;
14430
+ if (mins % 60 === 0) return `${mins / 60}h`;
14431
+ return `${mins}m`;
14432
+ }
14433
+ function watchUsageText() {
14434
+ return "Usage: `/watch <google-doc-url> [duration]` \u2014 e.g. `/watch https://docs.google.com/document/d/\u2026/edit 2h`. Duration defaults to 2h (max 7d).";
14435
+ }
14436
+ function watchBadUrlText() {
14437
+ return "That doesn't look like a Google Doc link. Paste the doc's share URL (the part with `/d/<id>`).";
14438
+ }
14439
+ function watchBadDurationText() {
14440
+ return "I couldn't read that duration. Try `30m`, `2h`, or `1d` (max 7d).";
14441
+ }
14442
+ function watchErrorText() {
14443
+ return "\u274C I couldn't set up that watch just now. Please try again in a moment.";
14444
+ }
14445
+ function watchSuccessTextSlack(fileId, durationMs, reused) {
14446
+ const url = `https://docs.google.com/document/d/${fileId}/edit`;
14447
+ const verb = reused ? "Extended my watch on" : "Watching";
14448
+ return `\u{1F440} ${verb} <${url}|that doc> for new comments that mention me, for the next ${humanDuration(durationMs)}. I'll pause automatically when the window's up. (Make sure Google Drive is connected with comment access, or I won't see them.)`;
14449
+ }
14450
+
14347
14451
  // src/ack-reaction.ts
14348
14452
  import { readdirSync, readFileSync as readFileSync2 } from "fs";
14349
14453
  import { join as join2 } from "path";
@@ -17214,6 +17318,7 @@ function buildSlackHelpMessage(codeName) {
17214
17318
  `\u2022 \`${agentSlashCommand("/help")}\` (or type \`/help\`) \u2014 show this help`,
17215
17319
  `\u2022 \`${agentSlashCommand("/restart")}\` \u2014 restart this agent`,
17216
17320
  `\u2022 \`${agentSlashCommand("/status")}\` \u2014 this agent's model, session origin, uptime + connectivity`,
17321
+ "\u2022 `/watch <google-doc-url> [duration]` (type it in chat) \u2014 watch a Google Doc for comments that mention me (default 2h, max 7d; auto-pauses when the window ends). In a shared channel, address me as `/watch-<my-name>`.",
17217
17322
  "\u2022 `/kill` \u2014 silence all agents in this thread for 6h (use as a thread reply)",
17218
17323
  "\u2022 `/unkill` \u2014 clear a kill (use as a thread reply)",
17219
17324
  `\u2022 \`${agentSlashCommand("/investigate")}\` \u2014 live tail of this agent's terminal pane (DM only, allowlisted users; works while the channel process is alive \u2014 a wedged host still needs SSM diagnostics)`
@@ -17727,6 +17832,61 @@ async function denyUnauthorizedRestart(opts) {
17727
17832
  ...opts.threadTs ? { thread_ts: opts.threadTs } : {}
17728
17833
  });
17729
17834
  }
17835
+ async function handleWatchCommand(opts) {
17836
+ const codeName = AGENT_CODE_NAME ?? "unknown";
17837
+ const replyThread = opts.threadTs ?? opts.ts;
17838
+ const post = (text) => postSlackMessage({
17839
+ channel: opts.channel,
17840
+ text,
17841
+ ...replyThread ? { thread_ts: replyThread } : {}
17842
+ });
17843
+ const parsed = parseWatchArgs(watchArgsFromText(opts.rawText, AGENT_CODE_NAME) ?? "");
17844
+ if (!parsed.ok) {
17845
+ await post(
17846
+ parsed.error === "bad-url" ? watchBadUrlText() : parsed.error === "bad-duration" ? watchBadDurationText() : watchUsageText()
17847
+ );
17848
+ return;
17849
+ }
17850
+ if (!AGT_HOST || !AGT_API_KEY || !AGT_AGENT_ID) {
17851
+ process.stderr.write(`slack-channel(${codeName}): /watch missing AGT_* env \u2014 cannot create watch
17852
+ `);
17853
+ await post(watchErrorText());
17854
+ return;
17855
+ }
17856
+ const expiresAtIso = new Date(Date.now() + parsed.value.durationMs).toISOString();
17857
+ const res = await postWatchTrigger({
17858
+ host: AGT_HOST,
17859
+ apiKey: AGT_API_KEY,
17860
+ agentId: AGT_AGENT_ID,
17861
+ fileId: parsed.value.fileId,
17862
+ expiresAtIso
17863
+ });
17864
+ if (!res.ok) {
17865
+ process.stderr.write(
17866
+ `slack-channel(${codeName}): /watch create failed (status=${res.status ?? "?"}): ${res.error ?? "unknown"}
17867
+ `
17868
+ );
17869
+ await post(watchErrorText());
17870
+ return;
17871
+ }
17872
+ process.stderr.write(
17873
+ `slack-channel(${codeName}): /watch ${res.reused ? "extended" : "created"} for doc ${parsed.value.fileId.slice(0, 8)}\u2026 channel ${hashChannelId(opts.channel)}
17874
+ `
17875
+ );
17876
+ await post(watchSuccessTextSlack(parsed.value.fileId, parsed.value.durationMs, !!res.reused));
17877
+ }
17878
+ async function denyUnauthorizedWatch(opts) {
17879
+ const codeName = AGENT_CODE_NAME ?? "unknown";
17880
+ process.stderr.write(
17881
+ `slack-channel(${codeName}): /watch denied \u2014 sender not in SLACK_ALLOWED_USERS, channel ${hashChannelId(opts.channel)}
17882
+ `
17883
+ );
17884
+ await postSlackMessage({
17885
+ channel: opts.channel,
17886
+ text: `\u{1F6AB} \`/watch\` denied \u2014 your Slack user is not in the allowlist for \`${codeName}\`.`,
17887
+ ...opts.threadTs ? { thread_ts: opts.threadTs } : {}
17888
+ });
17889
+ }
17730
17890
  var trackedThreads = /* @__PURE__ */ new Map();
17731
17891
  var THREAD_STORE_PATH = resolveThreadStorePath();
17732
17892
  var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAYS);
@@ -19188,7 +19348,9 @@ async function connectSocketMode() {
19188
19348
  const helpSuffixed = agentSlashCommand("/help");
19189
19349
  const isRestartCommand = strippedText === "/restart" || strippedText.startsWith("/restart ") || strippedText === restartSuffixed || strippedText.startsWith(`${restartSuffixed} `);
19190
19350
  const isHelpCommand = strippedText === "/help" || strippedText.startsWith("/help ") || strippedText === helpSuffixed || strippedText.startsWith(`${helpSuffixed} `);
19191
- const command = isBot ? void 0 : isHelpCommand ? { command: "help", authorized: true } : isRestartCommand ? { command: "restart", authorized: isRestartSenderAllowed(getEffectiveAllowedUsers(), evt.user) } : void 0;
19351
+ const isWatchDm = evt.channel?.startsWith("D") ?? false;
19352
+ const isWatchCommand = !isBot && watchArgsFromText(strippedText, AGENT_CODE_NAME, { allowBare: isWatchDm }) !== null;
19353
+ const command = isBot ? void 0 : isHelpCommand ? { command: "help", authorized: true } : isWatchCommand ? { command: "watch", authorized: isRestartSenderAllowed(getEffectiveAllowedUsers(), evt.user) } : isRestartCommand ? { command: "restart", authorized: isRestartSenderAllowed(getEffectiveAllowedUsers(), evt.user) } : void 0;
19192
19354
  const slackHomeTeamId = process.env.SLACK_HOME_TEAM_ID;
19193
19355
  const sameOrg = !!slackHomeTeamId && evt.team === slackHomeTeamId;
19194
19356
  const access = decideInboundAccess({
@@ -19247,6 +19409,22 @@ async function connectSocketMode() {
19247
19409
  });
19248
19410
  return;
19249
19411
  }
19412
+ if (access.command === "watch") {
19413
+ if (!access.authorized) {
19414
+ await denyUnauthorizedWatch({
19415
+ channel: evt.channel ?? "",
19416
+ threadTs: evt.thread_ts
19417
+ });
19418
+ return;
19419
+ }
19420
+ await handleWatchCommand({
19421
+ channel: evt.channel ?? "",
19422
+ threadTs: evt.thread_ts,
19423
+ ts: evt.ts ?? "",
19424
+ rawText: strippedText
19425
+ });
19426
+ return;
19427
+ }
19250
19428
  if (access.command === "restart") {
19251
19429
  if (!access.authorized) {
19252
19430
  await denyUnauthorizedRestart({
@@ -14868,6 +14868,100 @@ function decideInboundAccess(input) {
14868
14868
  return { kind: "admit" };
14869
14869
  }
14870
14870
 
14871
+ // src/watch-command.ts
14872
+ var WATCH_DEFAULT_DURATION_MS = 2 * 60 * 60 * 1e3;
14873
+ var WATCH_MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1e3;
14874
+ var WATCH_MIN_DURATION_MS = 5 * 60 * 1e3;
14875
+ function extractDriveFileId(s) {
14876
+ const trimmed = s.trim().replace(/^<|>$/g, "");
14877
+ const pathId = trimmed.match(/\/d\/([A-Za-z0-9_-]{10,})/);
14878
+ if (pathId?.[1]) return pathId[1];
14879
+ const idParam = trimmed.match(/[?&]id=([A-Za-z0-9_-]{10,})/);
14880
+ if (idParam?.[1]) return idParam[1];
14881
+ if (!/[/:?]/.test(trimmed) && /^[A-Za-z0-9_-]{20,}$/.test(trimmed)) return trimmed;
14882
+ return null;
14883
+ }
14884
+ function parseDurationToken(token) {
14885
+ const m = token.trim().match(/^(\d+)\s*([smhd])?$/i);
14886
+ if (!m?.[1]) return null;
14887
+ const n = Number.parseInt(m[1], 10);
14888
+ if (!Number.isFinite(n) || n <= 0) return null;
14889
+ const unit = (m[2] ?? "m").toLowerCase();
14890
+ const mult = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
14891
+ return n * mult;
14892
+ }
14893
+ function parseWatchArgs(args) {
14894
+ const parts = args.trim().split(/\s+/).filter(Boolean);
14895
+ const first = parts[0];
14896
+ if (!first) return { ok: false, error: "usage" };
14897
+ if (parts.length > 2) return { ok: false, error: "usage" };
14898
+ const fileId = extractDriveFileId(first);
14899
+ if (!fileId) return { ok: false, error: "bad-url" };
14900
+ let durationMs = WATCH_DEFAULT_DURATION_MS;
14901
+ const durationToken = parts[1];
14902
+ if (durationToken !== void 0) {
14903
+ const parsed = parseDurationToken(durationToken);
14904
+ if (parsed === null) return { ok: false, error: "bad-duration" };
14905
+ durationMs = Math.min(Math.max(parsed, WATCH_MIN_DURATION_MS), WATCH_MAX_DURATION_MS);
14906
+ }
14907
+ return { ok: true, value: { fileId, durationMs } };
14908
+ }
14909
+ async function postWatchTrigger(opts) {
14910
+ const doFetch = opts.fetchImpl ?? fetch;
14911
+ try {
14912
+ const res = await doFetch(`${opts.host}/host/triggers`, {
14913
+ method: "POST",
14914
+ headers: {
14915
+ "Content-Type": "application/json; charset=utf-8",
14916
+ Authorization: `Bearer ${opts.apiKey}`
14917
+ },
14918
+ body: JSON.stringify({
14919
+ agent_id: opts.agentId,
14920
+ provider: "gdrive_comments",
14921
+ config: { fileId: opts.fileId },
14922
+ expires_at: opts.expiresAtIso
14923
+ }),
14924
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
14925
+ });
14926
+ if (!res.ok) {
14927
+ let msg = `HTTP ${res.status}`;
14928
+ try {
14929
+ const j2 = await res.json();
14930
+ if (j2?.error) msg = j2.error;
14931
+ } catch {
14932
+ }
14933
+ return { ok: false, status: res.status, error: msg };
14934
+ }
14935
+ const j = await res.json().catch(() => ({}));
14936
+ return { ok: true, status: res.status, reused: !!j.reused, expiresAt: j.expires_at ?? null };
14937
+ } catch (err) {
14938
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
14939
+ }
14940
+ }
14941
+ function humanDuration(ms) {
14942
+ const mins = Math.round(ms / 6e4);
14943
+ if (mins % 1440 === 0) return `${mins / 1440}d`;
14944
+ if (mins % 60 === 0) return `${mins / 60}h`;
14945
+ return `${mins}m`;
14946
+ }
14947
+ function watchUsageText() {
14948
+ return "Usage: `/watch <google-doc-url> [duration]` \u2014 e.g. `/watch https://docs.google.com/document/d/\u2026/edit 2h`. Duration defaults to 2h (max 7d).";
14949
+ }
14950
+ function watchBadUrlText() {
14951
+ return "That doesn't look like a Google Doc link. Paste the doc's share URL (the part with `/d/<id>`).";
14952
+ }
14953
+ function watchBadDurationText() {
14954
+ return "I couldn't read that duration. Try `30m`, `2h`, or `1d` (max 7d).";
14955
+ }
14956
+ function watchErrorText() {
14957
+ return "\u274C I couldn't set up that watch just now. Please try again in a moment.";
14958
+ }
14959
+ function watchSuccessTextPlain(fileId, durationMs, reused) {
14960
+ const url = `https://docs.google.com/document/d/${fileId}/edit`;
14961
+ const verb = reused ? "Extended my watch on" : "Watching";
14962
+ return `\u{1F440} ${verb} ${url} for new comments that mention me, for the next ${humanDuration(durationMs)}. I'll pause automatically when the window's up. (Make sure Google Drive is connected with comment access, or I won't see them.)`;
14963
+ }
14964
+
14871
14965
  // src/telegram-peer-rate-limiter.ts
14872
14966
  var SECOND_MS = 1e3;
14873
14967
  var MINUTE_MS = 60 * SECOND_MS;
@@ -16533,6 +16627,7 @@ function buildTelegramHelpMessage(codeName) {
16533
16627
  `_Type these in any chat where the bot is present (intercepted by the agent):_`,
16534
16628
  `\u2022 /help \u2014 show this help`,
16535
16629
  `\u2022 /status \u2014 this agent's model, session origin, uptime + connectivity`,
16630
+ `\u2022 /watch <google-doc-url> [duration] \u2014 watch a Google Doc for comments that mention me (default 2h, max 7d; auto-pauses when the window ends)`,
16536
16631
  `\u2022 /restart \u2014 restart this agent`,
16537
16632
  `\u2022 /investigate-${codeName} \u2014 live tail of this agent's terminal pane (DM only; team owners/admins and the agent's reports-to person)`
16538
16633
  ].join("\n");
@@ -16562,6 +16657,63 @@ async function handleHelpCommand(opts) {
16562
16657
  );
16563
16658
  }
16564
16659
  }
16660
+ async function handleWatchCommand(opts) {
16661
+ const reply = async (text) => {
16662
+ try {
16663
+ const resp = await telegramApiCall(
16664
+ "sendMessage",
16665
+ { chat_id: opts.chatId, text, reply_to_message_id: Number(opts.messageId) },
16666
+ 1e4
16667
+ );
16668
+ if (!resp.ok) {
16669
+ process.stderr.write(
16670
+ `telegram-channel(${AGENT_CODE_NAME}): /watch reply rejected (chat ${redactId(opts.chatId)}): ${resp.description ?? "unknown"}
16671
+ `
16672
+ );
16673
+ }
16674
+ } catch (err) {
16675
+ process.stderr.write(
16676
+ `telegram-channel(${AGENT_CODE_NAME}): /watch reply send failed: ${redactAugmentedPaths(err.message)}
16677
+ `
16678
+ );
16679
+ }
16680
+ };
16681
+ const args = opts.text.replace(/^\/watch(?:@[A-Za-z0-9_]{1,64})?\s*/i, "");
16682
+ const parsed = parseWatchArgs(args);
16683
+ if (!parsed.ok) {
16684
+ await reply(
16685
+ parsed.error === "bad-url" ? watchBadUrlText() : parsed.error === "bad-duration" ? watchBadDurationText() : watchUsageText()
16686
+ );
16687
+ return;
16688
+ }
16689
+ if (!AGT_HOST || !AGT_API_KEY || !AGT_AGENT_ID) {
16690
+ process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): /watch missing AGT_* env \u2014 cannot create watch
16691
+ `);
16692
+ await reply(watchErrorText());
16693
+ return;
16694
+ }
16695
+ const expiresAtIso = new Date(Date.now() + parsed.value.durationMs).toISOString();
16696
+ const res = await postWatchTrigger({
16697
+ host: AGT_HOST,
16698
+ apiKey: AGT_API_KEY,
16699
+ agentId: AGT_AGENT_ID,
16700
+ fileId: parsed.value.fileId,
16701
+ expiresAtIso
16702
+ });
16703
+ if (!res.ok) {
16704
+ process.stderr.write(
16705
+ `telegram-channel(${AGENT_CODE_NAME}): /watch create failed (status=${res.status ?? "?"}): ${res.error ?? "unknown"}
16706
+ `
16707
+ );
16708
+ await reply(watchErrorText());
16709
+ return;
16710
+ }
16711
+ process.stderr.write(
16712
+ `telegram-channel(${AGENT_CODE_NAME}): /watch ${res.reused ? "extended" : "created"} for doc ${parsed.value.fileId.slice(0, 8)}\u2026 chat ${redactId(opts.chatId)}
16713
+ `
16714
+ );
16715
+ await reply(watchSuccessTextPlain(parsed.value.fileId, parsed.value.durationMs, !!res.reused));
16716
+ }
16565
16717
  function buildTelegramStatusReply() {
16566
16718
  const state = readAgentSessionState(TELEGRAM_AGENT_DIR);
16567
16719
  return buildAgentConfigReport({
@@ -16967,6 +17119,10 @@ var STATUS_SYNTAX_RE = /^\/status(?:@([A-Za-z0-9_]{1,64}))?(?:\s|$)/;
16967
17119
  function isStatusSyntax(text) {
16968
17120
  return STATUS_SYNTAX_RE.test(text);
16969
17121
  }
17122
+ var WATCH_SYNTAX_RE = /^\/watch(?:@([A-Za-z0-9_]{1,64}))?(?:\s|$)/;
17123
+ function isWatchSyntax(text) {
17124
+ return WATCH_SYNTAX_RE.test(text);
17125
+ }
16970
17126
  function isRestartSyntax(text) {
16971
17127
  return RESTART_SYNTAX_RE.test(text);
16972
17128
  }
@@ -18208,7 +18364,7 @@ async function pollLoop() {
18208
18364
  const trimmedContent = content.trim();
18209
18365
  const isFromBot = !!msg.from?.is_bot;
18210
18366
  const nowMs = Date.now();
18211
- const telegramCommand = isHelpSyntax(trimmedContent) ? "help" : isStatusSyntax(trimmedContent) ? "status" : isRestartSyntax(trimmedContent) ? "restart" : isInvestigateSyntax(trimmedContent) ? "investigate" : void 0;
18367
+ const telegramCommand = isHelpSyntax(trimmedContent) ? "help" : isStatusSyntax(trimmedContent) ? "status" : isWatchSyntax(trimmedContent) ? "watch" : isRestartSyntax(trimmedContent) ? "restart" : isInvestigateSyntax(trimmedContent) ? "investigate" : void 0;
18212
18368
  let classification;
18213
18369
  let peerLeaf;
18214
18370
  if (isFromBot) {
@@ -18288,6 +18444,19 @@ async function pollLoop() {
18288
18444
  }
18289
18445
  continue;
18290
18446
  }
18447
+ if (access.kind === "command" && access.command === "watch") {
18448
+ const disposition = await classifyRestartCommand(
18449
+ trimmedContent.replace(/^\/watch(@[A-Za-z0-9_]{1,64})?/, "/restart$1")
18450
+ );
18451
+ if (disposition === "act") {
18452
+ await handleWatchCommand({
18453
+ chatId,
18454
+ messageId: String(msg.message_id),
18455
+ text: trimmedContent
18456
+ });
18457
+ }
18458
+ continue;
18459
+ }
18291
18460
  if (access.kind === "command" && access.command === "restart") {
18292
18461
  const disposition = await classifyRestartCommand(trimmedContent);
18293
18462
  if (disposition === "act") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.27.163",
3
+ "version": "0.27.165",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {