agent-yes 1.271.0 → 1.272.0

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.
Files changed (28) hide show
  1. package/dist/{SUPPORTED_CLIS-lreAQcHS.js → SUPPORTED_CLIS-C1rgBiLd.js} +3 -3
  2. package/dist/{SUPPORTED_CLIS-3wwg1X8T.js → SUPPORTED_CLIS-DFOStQkx.js} +2 -2
  3. package/dist/{agentShare-DnewJUOO.js → agentShare-CLhjMsHZ.js} +2 -2
  4. package/dist/{callback-D521tLFf.js → callback-BVKHRbUE.js} +3 -3
  5. package/dist/{callback-Besk3KFx.js → callback-C64-nP2U.js} +2 -2
  6. package/dist/cli.js +5 -5
  7. package/dist/{cmdPs-4An2s2t8.js → cmdPs-Cu__YsO7.js} +2 -2
  8. package/dist/index.js +2 -2
  9. package/dist/{notifyDaemon-kIOLWrEI.js → notifyDaemon-BmtjtW5L.js} +2 -2
  10. package/dist/{parentPingLoop-D2A9Fkhf.js → parentPingLoop-BtggDU06.js} +2 -2
  11. package/dist/{rustBinary-BK03gMMg.js → rustBinary-ISnNc7Uf.js} +2 -2
  12. package/dist/{schedule-BdK4RVQ5.js → schedule-D37Q9LGO.js} +4 -4
  13. package/dist/{serve-XWSfUL8i.js → serve-Dw0x9Of9.js} +17 -17
  14. package/dist/{setup-B-Cui8R6.js → setup-BmBjOyza.js} +2 -2
  15. package/dist/{subcommands-C3NO344d.js → subcommands-DKqbwkvb.js} +14 -14
  16. package/dist/{subcommands-ByZspogJ.js → subcommands-TAOmVCxo.js} +1 -1
  17. package/dist/{terminal-BxWNOBW4.js → terminal-BQ79zaFa.js} +2 -2
  18. package/dist/trayApp-BHx2FN2d.js +5 -0
  19. package/dist/{trayApp-CcQDIINw.js → trayApp-BsETHuMR.js} +2 -2
  20. package/dist/{ts-BG759sO1.js → ts-Bb-h7XQP.js} +3 -3
  21. package/dist/{versionChecker-DCtcFwZ7.js → versionChecker-D9LB-MW5.js} +2 -2
  22. package/dist/{widget-DobxsxXa.js → widget-gRjJprB5.js} +3 -3
  23. package/dist/{ws-DO1UpPDw.js → ws-BFli3FKi.js} +2 -2
  24. package/dist/{ws-Bk2cJphr.js → ws-CklRMQwW.js} +2 -2
  25. package/lab/ui/lab/index.md +16 -0
  26. package/lab/ui/lab/posts/2026-08-11-slack-term-safe-writes.md +240 -0
  27. package/package.json +1 -1
  28. package/dist/trayApp-XJit7gRr.js +0 -5
@@ -1,12 +1,12 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-DCtcFwZ7.js";
3
- import "./ts-BG759sO1.js";
2
+ import "./versionChecker-D9LB-MW5.js";
3
+ import "./ts-Bb-h7XQP.js";
4
4
  import "./todoAutomation-BETGOik9.js";
5
5
  import "./JsonlStore-CPGIR0Wy.js";
6
6
  import "./globalPidIndex-Dq4KY2IV.js";
7
7
  import "./pidStore-DJqSZPJi.js";
8
8
  import "./messageLog-BYFwioFY.js";
9
9
  import "./todoStore-Wk8U4zJ8.js";
10
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-3wwg1X8T.js";
10
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFOStQkx.js";
11
11
 
12
12
  export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-BG759sO1.js";
1
+ import { t as CLIS_CONFIG } from "./ts-Bb-h7XQP.js";
2
2
 
3
3
  //#region ts/SUPPORTED_CLIS.ts
4
4
  const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
5
5
 
6
6
  //#endregion
7
7
  export { SUPPORTED_CLIS as t };
8
- //# sourceMappingURL=SUPPORTED_CLIS-3wwg1X8T.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-DFOStQkx.js.map
@@ -3,7 +3,7 @@ import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
4
  import "./e2e-OLfcNxiv.js";
5
5
  import "./configShared-DYxlvrH0.js";
6
- import { B as resolveOne } from "./subcommands-C3NO344d.js";
6
+ import { B as resolveOne } from "./subcommands-DKqbwkvb.js";
7
7
  import "./notifyStore-Dus2D1XA.js";
8
8
  import "./webrtcLink-B72AQ2eb.js";
9
9
  import "./remotes-IzQHupeo.js";
@@ -230,4 +230,4 @@ function transformEvent(rawEvent, agentId, forwarded) {
230
230
 
231
231
  //#endregion
232
232
  export { createScopedShare, listShares, revokeAllShares, revokeShare };
233
- //# sourceMappingURL=agentShare-DnewJUOO.js.map
233
+ //# sourceMappingURL=agentShare-CLhjMsHZ.js.map
@@ -1,5 +1,5 @@
1
1
  import { t as agentYesHome } from "./agentYesHome-CtmsY6I-.js";
2
- import { B as resolveOne } from "./subcommands-C3NO344d.js";
2
+ import { B as resolveOne } from "./subcommands-DKqbwkvb.js";
3
3
  import { a as mintCapability, o as parseExpires } from "./callbackCore-Bbe7rpN2.js";
4
4
  import { randomBytes } from "node:crypto";
5
5
  import { chmodSync, readFileSync, renameSync, writeFileSync } from "node:fs";
@@ -72,7 +72,7 @@ async function resolveBase(flag) {
72
72
  if (flag) return flag;
73
73
  if (process.env.AGENT_YES_CALLBACK_BASE) return process.env.AGENT_YES_CALLBACK_BASE;
74
74
  try {
75
- const { resolveLocalServeUrl } = await import("./serve-XWSfUL8i.js");
75
+ const { resolveLocalServeUrl } = await import("./serve-Dw0x9Of9.js");
76
76
  const url = await resolveLocalServeUrl();
77
77
  if (url) return url;
78
78
  } catch {}
@@ -187,4 +187,4 @@ async function cmdCallback(rest) {
187
187
 
188
188
  //#endregion
189
189
  export { loadOrCreateCallbackSecret as i, isCallbackRevoked as n, loadCallbackSecretReadOnly as r, cmdCallback as t };
190
- //# sourceMappingURL=callback-D521tLFf.js.map
190
+ //# sourceMappingURL=callback-BVKHRbUE.js.map
@@ -3,11 +3,11 @@ import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
4
  import "./e2e-OLfcNxiv.js";
5
5
  import "./configShared-DYxlvrH0.js";
6
- import "./subcommands-C3NO344d.js";
6
+ import "./subcommands-DKqbwkvb.js";
7
7
  import "./notifyStore-Dus2D1XA.js";
8
8
  import "./webrtcLink-B72AQ2eb.js";
9
9
  import "./remotes-IzQHupeo.js";
10
10
  import "./callbackCore-Bbe7rpN2.js";
11
- import { i as loadOrCreateCallbackSecret, n as isCallbackRevoked, r as loadCallbackSecretReadOnly, t as cmdCallback } from "./callback-D521tLFf.js";
11
+ import { i as loadOrCreateCallbackSecret, n as isCallbackRevoked, r as loadCallbackSecretReadOnly, t as cmdCallback } from "./callback-BVKHRbUE.js";
12
12
 
13
13
  export { cmdCallback };
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
  import { t as invokedCliName } from "./invokedCli-uqM2YYA7.js";
3
3
  import { n as logger } from "./logger-CDIsZ-Pp.js";
4
- import { i as versionString, n as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-DCtcFwZ7.js";
5
- import { r as getRustBinary } from "./rustBinary-BK03gMMg.js";
4
+ import { i as versionString, n as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-D9LB-MW5.js";
5
+ import { r as getRustBinary } from "./rustBinary-ISnNc7Uf.js";
6
6
  import { argv } from "process";
7
7
  import { spawn } from "child_process";
8
8
  import ms from "ms";
@@ -295,7 +295,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
295
295
  const rawArg = process.argv[2];
296
296
  const managerCommands = !invokedCliName(process.argv);
297
297
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
298
- const { isSubcommand, runSubcommand, cmdHelp, isUnknownManagerToken, isBareManagerInvocation } = await import("./subcommands-ByZspogJ.js");
298
+ const { isSubcommand, runSubcommand, cmdHelp, isUnknownManagerToken, isBareManagerInvocation } = await import("./subcommands-TAOmVCxo.js");
299
299
  if (isHelpFlag && process.argv.length === 3) {
300
300
  await cmdHelp(managerCommands);
301
301
  process.exit(0);
@@ -309,7 +309,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
309
309
  process.exit(code ?? 0);
310
310
  }
311
311
  {
312
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-lreAQcHS.js");
312
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-C1rgBiLd.js");
313
313
  if (isUnknownManagerToken(rawArg, managerCommands, SUPPORTED_CLIS)) {
314
314
  process.stderr.write(`ay: unknown subcommand or CLI '${rawArg}'.\n See 'ay help' for subcommands. To run an agent, name a CLI:\n 'ay <cli> …' (e.g. 'ay claude …') or 'ay --cli <cli> …'.\n`);
315
315
  process.exit(1);
@@ -360,7 +360,7 @@ if (config.useRust) {
360
360
  }
361
361
  }
362
362
  if (rustBinary) {
363
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-lreAQcHS.js");
363
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-C1rgBiLd.js");
364
364
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
365
365
  if (config.verbose) {
366
366
  console.log(`[rust] Using binary: ${rustBinary}`);
@@ -3,7 +3,7 @@ import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
4
  import "./e2e-OLfcNxiv.js";
5
5
  import "./configShared-DYxlvrH0.js";
6
- import { O as listRecords, d as deriveLiveState, et as buildAgentForest, tt as flattenForest } from "./subcommands-C3NO344d.js";
6
+ import { O as listRecords, d as deriveLiveState, et as buildAgentForest, tt as flattenForest } from "./subcommands-DKqbwkvb.js";
7
7
  import "./notifyStore-Dus2D1XA.js";
8
8
  import "./webrtcLink-B72AQ2eb.js";
9
9
  import "./remotes-IzQHupeo.js";
@@ -574,4 +574,4 @@ function readPpidSync(pid) {
574
574
 
575
575
  //#endregion
576
576
  export { cmdPs };
577
- //# sourceMappingURL=cmdPs-4An2s2t8.js.map
577
+ //# sourceMappingURL=cmdPs-Cu__YsO7.js.map
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-DCtcFwZ7.js";
3
- import { a as hasTodoStore, i as answerAsk, l as AgentContext, n as agentYes, o as listAsks, r as config, s as listAsksForProject, t as CLIS_CONFIG } from "./ts-BG759sO1.js";
2
+ import "./versionChecker-D9LB-MW5.js";
3
+ import { a as hasTodoStore, i as answerAsk, l as AgentContext, n as agentYes, o as listAsks, r as config, s as listAsksForProject, t as CLIS_CONFIG } from "./ts-Bb-h7XQP.js";
4
4
  import { a as renderTree, c as describeBlock, i as renderDigest, l as monitorHint, o as unblockedTasks, r as openBlockers, t as reconcileTodos, u as removeControlCharacters } from "./todoAutomation-BETGOik9.js";
5
5
  import "./JsonlStore-CPGIR0Wy.js";
6
6
  import "./globalPidIndex-Dq4KY2IV.js";
@@ -3,7 +3,7 @@ import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
4
  import "./e2e-OLfcNxiv.js";
5
5
  import "./configShared-DYxlvrH0.js";
6
- import { L as renderLogTailLines, O as listRecords, d as deriveLiveState, x as isPidAlive } from "./subcommands-C3NO344d.js";
6
+ import { L as renderLogTailLines, O as listRecords, d as deriveLiveState, x as isPidAlive } from "./subcommands-DKqbwkvb.js";
7
7
  import { f as readInbox, g as daemonLockOwnerPath, h as daemonLockDir, i as gcInboxes, l as listInboxParents, m as shouldStealLock, n as appendEvent, s as hostId, u as liveWatchers, x as notifyDir } from "./notifyStore-Dus2D1XA.js";
8
8
  import "./webrtcLink-B72AQ2eb.js";
9
9
  import "./remotes-IzQHupeo.js";
@@ -863,4 +863,4 @@ async function ensureDaemon() {
863
863
 
864
864
  //#endregion
865
865
  export { daemonStatus, ensureDaemon, requestDaemonStop, runDaemon };
866
- //# sourceMappingURL=notifyDaemon-kIOLWrEI.js.map
866
+ //# sourceMappingURL=notifyDaemon-BmtjtW5L.js.map
@@ -338,7 +338,7 @@ async function isParentWatching(parentWrapperPid, parentAgentPid, selfAgentPid,
338
338
  logger.debug("[parent-ping] watcher lookup failed:", error);
339
339
  }
340
340
  try {
341
- const { lastReadAt, READ_WINDOW_MS } = await import("./subcommands-ByZspogJ.js");
341
+ const { lastReadAt, READ_WINDOW_MS } = await import("./subcommands-TAOmVCxo.js");
342
342
  const last = await lastReadAt(`agent:${parentAgentPid}`, selfAgentPid);
343
343
  if (last !== null && now - last <= READ_WINDOW_MS) return true;
344
344
  } catch (error) {
@@ -466,4 +466,4 @@ function startParentPingLoop(opts) {
466
466
 
467
467
  //#endregion
468
468
  export { startParentPingLoop };
469
- //# sourceMappingURL=parentPingLoop-D2A9Fkhf.js.map
469
+ //# sourceMappingURL=parentPingLoop-BtggDU06.js.map
@@ -1,4 +1,4 @@
1
- import { r as getInstalledPackage } from "./versionChecker-DCtcFwZ7.js";
1
+ import { r as getInstalledPackage } from "./versionChecker-D9LB-MW5.js";
2
2
  import { execFileSync } from "child_process";
3
3
  import { existsSync, mkdirSync, unlinkSync } from "fs";
4
4
  import { chmod, copyFile } from "fs/promises";
@@ -243,4 +243,4 @@ async function getRustBinary(options = {}) {
243
243
 
244
244
  //#endregion
245
245
  export { findTrayLauncher as n, getRustBinary as r, findSpawnHiddenLauncher as t };
246
- //# sourceMappingURL=rustBinary-BK03gMMg.js.map
246
+ //# sourceMappingURL=rustBinary-ISnNc7Uf.js.map
@@ -1,13 +1,13 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-DCtcFwZ7.js";
3
- import "./ts-BG759sO1.js";
2
+ import "./versionChecker-D9LB-MW5.js";
3
+ import "./ts-Bb-h7XQP.js";
4
4
  import "./todoAutomation-BETGOik9.js";
5
5
  import "./JsonlStore-CPGIR0Wy.js";
6
6
  import "./globalPidIndex-Dq4KY2IV.js";
7
7
  import "./pidStore-DJqSZPJi.js";
8
8
  import "./messageLog-BYFwioFY.js";
9
9
  import "./todoStore-Wk8U4zJ8.js";
10
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-3wwg1X8T.js";
10
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFOStQkx.js";
11
11
  import { f as resolveSpawnCwd } from "./workspaceConfig-EK7XNsgR.js";
12
12
  import { n as liveEnv, t as ensureNodeRuntime } from "./nodeRuntime-CKc1BN5J.js";
13
13
  import { createHash } from "node:crypto";
@@ -154,4 +154,4 @@ async function cmdSchedule(rest) {
154
154
 
155
155
  //#endregion
156
156
  export { cmdSchedule };
157
- //# sourceMappingURL=schedule-BdK4RVQ5.js.map
157
+ //# sourceMappingURL=schedule-D37Q9LGO.js.map
@@ -1,7 +1,7 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import { r as getInstalledPackage } from "./versionChecker-DCtcFwZ7.js";
3
- import { t as findSpawnHiddenLauncher } from "./rustBinary-BK03gMMg.js";
4
- import { c as permissionBadge, i as answerAsk, o as listAsks, u as CLAUDE_SESSION_PIN_ENV } from "./ts-BG759sO1.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-D9LB-MW5.js";
3
+ import { t as findSpawnHiddenLauncher } from "./rustBinary-ISnNc7Uf.js";
4
+ import { c as permissionBadge, i as answerAsk, o as listAsks, u as CLAUDE_SESSION_PIN_ENV } from "./ts-Bb-h7XQP.js";
5
5
  import { t as agentYesHome$1 } from "./agentYesHome-CtmsY6I-.js";
6
6
  import { u as removeControlCharacters } from "./todoAutomation-BETGOik9.js";
7
7
  import "./JsonlStore-CPGIR0Wy.js";
@@ -12,18 +12,18 @@ import { t as pgidForWrapper } from "./reaper-C4BvNylH.js";
12
12
  import "./todoStore-Wk8U4zJ8.js";
13
13
  import "./e2e-OLfcNxiv.js";
14
14
  import "./configShared-DYxlvrH0.js";
15
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-3wwg1X8T.js";
16
- import { $ as TYPING_BADGE, B as resolveOne, F as recentMessageEdges, G as snapshotStatus, I as recentReadEdges, L as renderLogTailLines, M as readLogForRender, N as readNotes, O as listRecords, P as readPtysize, Q as withIpcLock, R as renderRawLog, T as isUserTyping, Z as writeToIpc, f as deriveLiveStatus, g as extractTaskCounts, h as extractNeedsInput, l as controlCodeFromName, p as extractBadges, z as renderRawLogLines } from "./subcommands-C3NO344d.js";
15
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFOStQkx.js";
16
+ import { $ as TYPING_BADGE, B as resolveOne, F as recentMessageEdges, G as snapshotStatus, I as recentReadEdges, L as renderLogTailLines, M as readLogForRender, N as readNotes, O as listRecords, P as readPtysize, Q as withIpcLock, R as renderRawLog, T as isUserTyping, Z as writeToIpc, f as deriveLiveStatus, g as extractTaskCounts, h as extractNeedsInput, l as controlCodeFromName, p as extractBadges, z as renderRawLogLines } from "./subcommands-DKqbwkvb.js";
17
17
  import { c as isTransientLockMkdirError } from "./notifyStore-Dus2D1XA.js";
18
18
  import "./webrtcLink-B72AQ2eb.js";
19
19
  import "./remotes-IzQHupeo.js";
20
20
  import { i as frameVisitorMessage, s as verifyCapability, t as MAX_CALLBACK_MSG_BYTES } from "./callbackCore-Bbe7rpN2.js";
21
- import { n as isCallbackRevoked, r as loadCallbackSecretReadOnly } from "./callback-D521tLFf.js";
21
+ import { n as isCallbackRevoked, r as loadCallbackSecretReadOnly } from "./callback-BVKHRbUE.js";
22
22
  import { a as getProvisionRoot, d as isProvisionAllowed, f as resolveSpawnCwd, i as getProvisionHook, l as hasProvisionHook, o as getSpawnHook, t as allowsSkipPermissions, u as hasSpawnHook } from "./workspaceConfig-EK7XNsgR.js";
23
23
  import { n as liveEnv, t as ensureNodeRuntime } from "./nodeRuntime-CKc1BN5J.js";
24
- import { i as importProvisionModule } from "./ws-DO1UpPDw.js";
24
+ import { i as importProvisionModule } from "./ws-BFli3FKi.js";
25
25
  import { r as spawnRejectionReason } from "./spawnGate-CS3oFdJ9.js";
26
- import { i as launchTray } from "./trayApp-CcQDIINw.js";
26
+ import { i as launchTray } from "./trayApp-BsETHuMR.js";
27
27
  import yargs from "yargs";
28
28
  import { appendFile, mkdir, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from "fs/promises";
29
29
  import { arch, cpus, freemem, homedir, hostname, loadavg, platform, totalmem, uptime, userInfo } from "os";
@@ -2436,7 +2436,7 @@ Options:
2436
2436
  }
2437
2437
  });
2438
2438
  if (req.method === "GET" && p === "/api/ws") {
2439
- const ws = await import("./ws-Bk2cJphr.js");
2439
+ const ws = await import("./ws-CklRMQwW.js");
2440
2440
  try {
2441
2441
  await ws.loadProvision();
2442
2442
  } catch (e) {
@@ -2456,7 +2456,7 @@ Options:
2456
2456
  if (req.method === "GET" && p === "/api/ws/status") {
2457
2457
  const dirRaw = url.searchParams.get("path");
2458
2458
  if (!dirRaw) return new Response("missing ?path=<workspace dir>", { status: 400 });
2459
- const ws = await import("./ws-Bk2cJphr.js");
2459
+ const ws = await import("./ws-CklRMQwW.js");
2460
2460
  let prov;
2461
2461
  try {
2462
2462
  prov = await ws.loadProvision();
@@ -2478,7 +2478,7 @@ Options:
2478
2478
  }
2479
2479
  }
2480
2480
  if (req.method === "GET" && p === "/api/ws/repos") {
2481
- const ws = await import("./ws-Bk2cJphr.js");
2481
+ const ws = await import("./ws-CklRMQwW.js");
2482
2482
  try {
2483
2483
  await ws.loadProvision();
2484
2484
  } catch (e) {
@@ -2559,7 +2559,7 @@ Options:
2559
2559
  const m = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(repoParam);
2560
2560
  if (!m || m[1] === "." || m[1] === ".." || m[2] === "." || m[2] === "..") return new Response("missing/invalid ?repo=<owner>/<repo>", { status: 400 });
2561
2561
  const [, owner, repo] = m;
2562
- const ws = await import("./ws-Bk2cJphr.js");
2562
+ const ws = await import("./ws-CklRMQwW.js");
2563
2563
  let prov;
2564
2564
  try {
2565
2565
  prov = await ws.loadProvision();
@@ -3470,7 +3470,7 @@ Options:
3470
3470
  const perm = body.perm ?? "r";
3471
3471
  if (perm !== "r" && perm !== "rw") return new Response(`invalid perm ${perm} (want r or rw)`, { status: 400 });
3472
3472
  try {
3473
- const { createScopedShare } = await import("./agentShare-DnewJUOO.js");
3473
+ const { createScopedShare } = await import("./agentShare-CLhjMsHZ.js");
3474
3474
  const share = await createScopedShare({
3475
3475
  agent: body.agent,
3476
3476
  perm,
@@ -3485,12 +3485,12 @@ Options:
3485
3485
  }
3486
3486
  }
3487
3487
  if (req.method === "GET" && p === "/api/shares") {
3488
- const { listShares } = await import("./agentShare-DnewJUOO.js");
3488
+ const { listShares } = await import("./agentShare-CLhjMsHZ.js");
3489
3489
  return Response.json(listShares());
3490
3490
  }
3491
3491
  const revokeM = /^\/api\/share\/([^/]+)$/.exec(p);
3492
3492
  if (req.method === "DELETE" && revokeM) {
3493
- const { revokeShare } = await import("./agentShare-DnewJUOO.js");
3493
+ const { revokeShare } = await import("./agentShare-CLhjMsHZ.js");
3494
3494
  const ok = revokeShare(decodeURIComponent(revokeM[1]));
3495
3495
  return new Response(ok ? "revoked" : "no such share", { status: ok ? 200 : 404 });
3496
3496
  }
@@ -3795,7 +3795,7 @@ Options:
3795
3795
  const shutdown = (resolve) => {
3796
3796
  if (heartbeat) clearInterval(heartbeat);
3797
3797
  closeShare?.();
3798
- import("./agentShare-DnewJUOO.js").then((m) => m.revokeAllShares()).catch(() => {});
3798
+ import("./agentShare-CLhjMsHZ.js").then((m) => m.revokeAllShares()).catch(() => {});
3799
3799
  server?.stop();
3800
3800
  Promise.resolve(releaseHostLock?.()).catch(() => {}).then(resolve);
3801
3801
  };
@@ -3808,4 +3808,4 @@ Options:
3808
3808
 
3809
3809
  //#endregion
3810
3810
  export { cmdServe, loadTokenReadOnly, mintScopedTermToken, resolveDaemonHttpBase, resolveLocalServeUrl };
3811
- //# sourceMappingURL=serve-XWSfUL8i.js.map
3811
+ //# sourceMappingURL=serve-Dw0x9Of9.js.map
@@ -40,7 +40,7 @@ async function cmdSetup(rest) {
40
40
  } catch {}
41
41
  if (noShare) return 0;
42
42
  process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
43
- const { cmdServe } = await import("./serve-XWSfUL8i.js");
43
+ const { cmdServe } = await import("./serve-Dw0x9Of9.js");
44
44
  return cmdServe([
45
45
  "install",
46
46
  "--share",
@@ -50,4 +50,4 @@ async function cmdSetup(rest) {
50
50
 
51
51
  //#endregion
52
52
  export { cmdSetup };
53
- //# sourceMappingURL=setup-B-Cui8R6.js.map
53
+ //# sourceMappingURL=setup-BmBjOyza.js.map
@@ -907,7 +907,7 @@ async function runSubcommand(argv) {
907
907
  switch (sub) {
908
908
  case "ls":
909
909
  case "list": return await cmdLs(rest);
910
- case "ps": return await (await import("./cmdPs-4An2s2t8.js")).cmdPs(rest);
910
+ case "ps": return await (await import("./cmdPs-Cu__YsO7.js")).cmdPs(rest);
911
911
  case "status": return await cmdStatus(rest);
912
912
  case "whoami": return await cmdWhoami(rest);
913
913
  case "result": return await cmdResult(rest);
@@ -954,35 +954,35 @@ async function runSubcommand(argv) {
954
954
  return cmdCh(rest);
955
955
  }
956
956
  case "term": {
957
- const { cmdTerm } = await import("./terminal-BxWNOBW4.js");
957
+ const { cmdTerm } = await import("./terminal-BQ79zaFa.js");
958
958
  return cmdTerm(rest);
959
959
  }
960
960
  case "widget": {
961
- const { cmdWidget } = await import("./widget-DobxsxXa.js");
961
+ const { cmdWidget } = await import("./widget-gRjJprB5.js");
962
962
  return cmdWidget(rest);
963
963
  }
964
964
  case "mint": {
965
- const { cmdMint } = await import("./widget-DobxsxXa.js");
965
+ const { cmdMint } = await import("./widget-gRjJprB5.js");
966
966
  return cmdMint(rest);
967
967
  }
968
968
  case "serve": {
969
- const { cmdServe } = await import("./serve-XWSfUL8i.js");
969
+ const { cmdServe } = await import("./serve-Dw0x9Of9.js");
970
970
  return cmdServe(rest);
971
971
  }
972
972
  case "tray": {
973
- const { cmdTray } = await import("./trayApp-XJit7gRr.js");
973
+ const { cmdTray } = await import("./trayApp-BHx2FN2d.js");
974
974
  return cmdTray(rest);
975
975
  }
976
976
  case "setup": {
977
- const { cmdSetup } = await import("./setup-B-Cui8R6.js");
977
+ const { cmdSetup } = await import("./setup-BmBjOyza.js");
978
978
  return cmdSetup(rest);
979
979
  }
980
980
  case "ws": {
981
- const { cmdWs } = await import("./ws-Bk2cJphr.js");
981
+ const { cmdWs } = await import("./ws-CklRMQwW.js");
982
982
  return cmdWs(rest);
983
983
  }
984
984
  case "schedule": {
985
- const { cmdSchedule } = await import("./schedule-BdK4RVQ5.js");
985
+ const { cmdSchedule } = await import("./schedule-D37Q9LGO.js");
986
986
  return cmdSchedule(rest);
987
987
  }
988
988
  case "remote": {
@@ -994,7 +994,7 @@ async function runSubcommand(argv) {
994
994
  return cmdExpose(rest);
995
995
  }
996
996
  case "callback": {
997
- const { cmdCallback } = await import("./callback-Besk3KFx.js");
997
+ const { cmdCallback } = await import("./callback-C64-nP2U.js");
998
998
  return cmdCallback(rest);
999
999
  }
1000
1000
  case "reap":
@@ -3500,7 +3500,7 @@ async function cmdAttach(rest) {
3500
3500
  const winsizeDir = path.join(ayHome, "winsize");
3501
3501
  await mkdir(winsizeDir, { recursive: true });
3502
3502
  const winsizePath = path.join(winsizeDir, String(record.pid));
3503
- const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-XWSfUL8i.js");
3503
+ const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-Dw0x9Of9.js");
3504
3504
  const daemonBase = await resolveDaemonHttpBase().catch(() => null);
3505
3505
  const daemonToken = daemonBase ? await loadTokenReadOnly().catch(() => null) ?? "" : "";
3506
3506
  const capViewer = `attach:${process.pid}`;
@@ -4210,7 +4210,7 @@ async function cmdNotify(rest) {
4210
4210
  }
4211
4211
  const ensure = async () => {
4212
4212
  if (!argv["ensure-daemon"]) return;
4213
- const { ensureDaemon } = await import("./notifyDaemon-kIOLWrEI.js");
4213
+ const { ensureDaemon } = await import("./notifyDaemon-BmtjtW5L.js");
4214
4214
  await ensureDaemon().catch(() => null);
4215
4215
  };
4216
4216
  await heartbeatWatcher(parent, selfStartedAt);
@@ -4284,7 +4284,7 @@ async function cmdNotifyCursor(args) {
4284
4284
  }
4285
4285
  async function cmdNotifyd(rest) {
4286
4286
  const sub = rest[0] ?? "status";
4287
- const daemon = await import("./notifyDaemon-kIOLWrEI.js");
4287
+ const daemon = await import("./notifyDaemon-BmtjtW5L.js");
4288
4288
  switch (sub) {
4289
4289
  case "run": return daemon.runDaemon();
4290
4290
  case "once": return daemon.runDaemon({ once: true });
@@ -4311,4 +4311,4 @@ async function cmdNotifyd(rest) {
4311
4311
 
4312
4312
  //#endregion
4313
4313
  export { TYPING_BADGE as $, menuSelectKeys as A, resolveOne as B, isSubcommand as C, lastStdinAt as D, lastReadAt as E, recentMessageEdges as F, snapshotStatus as G, resolveResumeArgs as H, recentReadEdges as I, submitAndConfirm as J, stdinActivityPath as K, renderLogTailLines as L, readLogForRender as M, readNotes as N, listRecords as O, readPtysize as P, withIpcLock as Q, renderRawLog as R, isSlashCommand as S, isUserTyping as T, restartHintLines as U, resolveReadWindow as V, runSubcommand as W, writeKeysPaced as X, waitForLogQuiet as Y, writeToIpc as Z, finalizedLines as _, TYPING_WINDOW_MS as a, isExitRequest as b, cmdHelp as c, deriveLiveState as d, buildAgentForest as et, deriveLiveStatus as f, extractTaskCounts as g, extractNeedsInput as h, READ_WINDOW_MS as i, readAgentPtysize as j, matchKeyword as k, controlCodeFromName as l, extractMenu as m, MAX_RENDER_BYTES as n, backoffWhileTyping as o, extractBadges as p, stopTipForCli as q, READ_PAGE_DEFAULT as r, cmdDeepseek as s, GRACEFUL_EXIT_COMMANDS as t, flattenForest as tt, cursorAbs as u, isAgentStuck as v, isUnknownManagerToken as w, isPidAlive as x, isBareManagerInvocation as y, renderRawLogLines as z };
4314
- //# sourceMappingURL=subcommands-C3NO344d.js.map
4314
+ //# sourceMappingURL=subcommands-DKqbwkvb.js.map
@@ -3,7 +3,7 @@ import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
4
  import "./e2e-OLfcNxiv.js";
5
5
  import "./configShared-DYxlvrH0.js";
6
- import { A as menuSelectKeys, B as resolveOne, C as isSubcommand, D as lastStdinAt, E as lastReadAt, F as recentMessageEdges, G as snapshotStatus, H as resolveResumeArgs, I as recentReadEdges, J as submitAndConfirm, K as stdinActivityPath, L as renderLogTailLines, M as readLogForRender, N as readNotes, O as listRecords, P as readPtysize, R as renderRawLog, S as isSlashCommand, T as isUserTyping, U as restartHintLines, V as resolveReadWindow, W as runSubcommand, X as writeKeysPaced, Y as waitForLogQuiet, Z as writeToIpc, _ as finalizedLines, a as TYPING_WINDOW_MS, b as isExitRequest, c as cmdHelp, d as deriveLiveState, f as deriveLiveStatus, g as extractTaskCounts, h as extractNeedsInput, i as READ_WINDOW_MS, j as readAgentPtysize, k as matchKeyword, l as controlCodeFromName, m as extractMenu, n as MAX_RENDER_BYTES, o as backoffWhileTyping, p as extractBadges, q as stopTipForCli, r as READ_PAGE_DEFAULT, s as cmdDeepseek, t as GRACEFUL_EXIT_COMMANDS, u as cursorAbs, v as isAgentStuck, w as isUnknownManagerToken, x as isPidAlive, y as isBareManagerInvocation, z as renderRawLogLines } from "./subcommands-C3NO344d.js";
6
+ import { A as menuSelectKeys, B as resolveOne, C as isSubcommand, D as lastStdinAt, E as lastReadAt, F as recentMessageEdges, G as snapshotStatus, H as resolveResumeArgs, I as recentReadEdges, J as submitAndConfirm, K as stdinActivityPath, L as renderLogTailLines, M as readLogForRender, N as readNotes, O as listRecords, P as readPtysize, R as renderRawLog, S as isSlashCommand, T as isUserTyping, U as restartHintLines, V as resolveReadWindow, W as runSubcommand, X as writeKeysPaced, Y as waitForLogQuiet, Z as writeToIpc, _ as finalizedLines, a as TYPING_WINDOW_MS, b as isExitRequest, c as cmdHelp, d as deriveLiveState, f as deriveLiveStatus, g as extractTaskCounts, h as extractNeedsInput, i as READ_WINDOW_MS, j as readAgentPtysize, k as matchKeyword, l as controlCodeFromName, m as extractMenu, n as MAX_RENDER_BYTES, o as backoffWhileTyping, p as extractBadges, q as stopTipForCli, r as READ_PAGE_DEFAULT, s as cmdDeepseek, t as GRACEFUL_EXIT_COMMANDS, u as cursorAbs, v as isAgentStuck, w as isUnknownManagerToken, x as isPidAlive, y as isBareManagerInvocation, z as renderRawLogLines } from "./subcommands-DKqbwkvb.js";
7
7
  import "./notifyStore-Dus2D1XA.js";
8
8
  import "./webrtcLink-B72AQ2eb.js";
9
9
  import "./remotes-IzQHupeo.js";
@@ -98,7 +98,7 @@ async function cmdTermMint(args) {
98
98
  if (flags.ro && flags.interactive) throw new Error("--ro and --interactive are mutually exclusive");
99
99
  const ttlSec = parseTtlSec(typeof flags.ttl === "string" ? flags.ttl : "15m");
100
100
  const canSend = flags.interactive === true;
101
- const { mintScopedTermToken } = await import("./serve-XWSfUL8i.js");
101
+ const { mintScopedTermToken } = await import("./serve-Dw0x9Of9.js");
102
102
  const r = await mintScopedTermToken(pid, {
103
103
  ttlSec,
104
104
  canSend
@@ -135,4 +135,4 @@ async function cmdTerm(args) {
135
135
 
136
136
  //#endregion
137
137
  export { cmdTerm };
138
- //# sourceMappingURL=terminal-BxWNOBW4.js.map
138
+ //# sourceMappingURL=terminal-BQ79zaFa.js.map
@@ -0,0 +1,5 @@
1
+ import "./versionChecker-D9LB-MW5.js";
2
+ import "./rustBinary-ISnNc7Uf.js";
3
+ import { a as trayHiddenMarker, i as launchTray, n as hasDesktop, r as isTrayHidden, t as cmdTray } from "./trayApp-BsETHuMR.js";
4
+
5
+ export { cmdTray };
@@ -1,4 +1,4 @@
1
- import { n as findTrayLauncher } from "./rustBinary-BK03gMMg.js";
1
+ import { n as findTrayLauncher } from "./rustBinary-ISnNc7Uf.js";
2
2
  import { t as agentYesHome } from "./agentYesHome-CtmsY6I-.js";
3
3
  import { existsSync } from "node:fs";
4
4
  import path from "node:path";
@@ -97,4 +97,4 @@ async function cmdTray(args) {
97
97
 
98
98
  //#endregion
99
99
  export { trayHiddenMarker as a, launchTray as i, hasDesktop as n, isTrayHidden as r, cmdTray as t };
100
- //# sourceMappingURL=trayApp-CcQDIINw.js.map
100
+ //# sourceMappingURL=trayApp-BsETHuMR.js.map
@@ -1,6 +1,6 @@
1
1
  import { n as __esmMin, r as __exportAll } from "./chunk-cZBX9HZv.js";
2
2
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-DCtcFwZ7.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-D9LB-MW5.js";
4
4
  import { t as agentYesHome } from "./agentYesHome-CtmsY6I-.js";
5
5
  import { u as removeControlCharacters } from "./todoAutomation-BETGOik9.js";
6
6
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-DBoONoM-.js";
@@ -2074,7 +2074,7 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
2074
2074
  }, 100);
2075
2075
  const cleanupHeartbeat = () => clearInterval(heartbeatInterval);
2076
2076
  shell.onExit(cleanupHeartbeat);
2077
- const { startParentPingLoop } = await import("./parentPingLoop-D2A9Fkhf.js");
2077
+ const { startParentPingLoop } = await import("./parentPingLoop-BtggDU06.js");
2078
2078
  const parentPingLoop = startParentPingLoop({
2079
2079
  parentPid,
2080
2080
  selfWrapperPid: process.pid,
@@ -2338,4 +2338,4 @@ function sleep(ms) {
2338
2338
 
2339
2339
  //#endregion
2340
2340
  export { hasTodoStore as a, permissionBadge as c, answerAsk as i, AgentContext as l, agentYes as n, listAsks as o, config as r, listAsksForProject as s, CLIS_CONFIG as t, CLAUDE_SESSION_PIN_ENV as u };
2341
- //# sourceMappingURL=ts-BG759sO1.js.map
2341
+ //# sourceMappingURL=ts-Bb-h7XQP.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "agent-yes";
10
- var version = "1.271.0";
10
+ var version = "1.272.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -215,4 +215,4 @@ async function displayVersion() {
215
215
 
216
216
  //#endregion
217
217
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
218
- //# sourceMappingURL=versionChecker-DCtcFwZ7.js.map
218
+ //# sourceMappingURL=versionChecker-D9LB-MW5.js.map
@@ -28,7 +28,7 @@ function parseFlags(args, known) {
28
28
  }
29
29
  /** Resolve the local daemon base URL + token (flags override discovery). */
30
30
  async function daemonTarget(flags) {
31
- const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-XWSfUL8i.js");
31
+ const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-Dw0x9Of9.js");
32
32
  const base = typeof flags.base === "string" ? flags.base : await resolveDaemonHttpBase();
33
33
  if (!base) throw new Error("no running ay serve daemon found — start `ay serve` or pass --base <url> (e.g. http://127.0.0.1:PORT)");
34
34
  const token = typeof flags.token === "string" ? flags.token : await loadTokenReadOnly() ?? "";
@@ -186,7 +186,7 @@ async function cmdMint(args) {
186
186
  const bad = caps.filter((c) => !KNOWN_CAPS.has(c));
187
187
  if (bad.length) throw new Error(`unknown cap(s): ${bad.join(", ")} (valid: ${[...KNOWN_CAPS].join(", ")})`);
188
188
  const ttlSec = parseTtlSec(typeof flags.ttl === "string" ? flags.ttl : "15m");
189
- const { mintScopedTermToken } = await import("./serve-XWSfUL8i.js");
189
+ const { mintScopedTermToken } = await import("./serve-Dw0x9Of9.js");
190
190
  const r = await mintScopedTermToken(target, {
191
191
  ttlSec,
192
192
  caps
@@ -202,4 +202,4 @@ async function cmdMint(args) {
202
202
 
203
203
  //#endregion
204
204
  export { cmdMint, cmdWidget };
205
- //# sourceMappingURL=widget-DobxsxXa.js.map
205
+ //# sourceMappingURL=widget-gRjJprB5.js.map
@@ -1,4 +1,4 @@
1
- import { O as listRecords } from "./subcommands-C3NO344d.js";
1
+ import { O as listRecords } from "./subcommands-DKqbwkvb.js";
2
2
  import { a as getProvisionRoot } from "./workspaceConfig-EK7XNsgR.js";
3
3
  import { existsSync } from "fs";
4
4
  import { lstat, opendir } from "fs/promises";
@@ -424,4 +424,4 @@ async function cmdWs(args) {
424
424
 
425
425
  //#endregion
426
426
  export { isPathInside as a, walkWorkspaces as c, importProvisionModule as i, workspaceStatus as l, cmdWs as n, loadProvision as o, collectWorkspaces as r, resolveOperand as s, WS_JSON_SCHEMA as t };
427
- //# sourceMappingURL=ws-DO1UpPDw.js.map
427
+ //# sourceMappingURL=ws-BFli3FKi.js.map
@@ -3,10 +3,10 @@ import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
4
  import "./e2e-OLfcNxiv.js";
5
5
  import "./configShared-DYxlvrH0.js";
6
- import "./subcommands-C3NO344d.js";
6
+ import "./subcommands-DKqbwkvb.js";
7
7
  import "./notifyStore-Dus2D1XA.js";
8
8
  import "./webrtcLink-B72AQ2eb.js";
9
9
  import "./remotes-IzQHupeo.js";
10
- import { a as isPathInside, c as walkWorkspaces, i as importProvisionModule, l as workspaceStatus, n as cmdWs, o as loadProvision, r as collectWorkspaces, s as resolveOperand, t as WS_JSON_SCHEMA } from "./ws-DO1UpPDw.js";
10
+ import { a as isPathInside, c as walkWorkspaces, i as importProvisionModule, l as workspaceStatus, n as cmdWs, o as loadProvision, r as collectWorkspaces, s as resolveOperand, t as WS_JSON_SCHEMA } from "./ws-BFli3FKi.js";
11
11
 
12
12
  export { WS_JSON_SCHEMA, cmdWs, collectWorkspaces, isPathInside, loadProvision, walkWorkspaces, workspaceStatus };
@@ -79,6 +79,22 @@ machine, and any one person's attention.** The long version is a post below —
79
79
  address space, a bus that humans are also on, and delegation with accountability, and it is honest
80
80
  about the four places the current system is still cluttered.
81
81
 
82
+ ## The ecosystem
83
+
84
+ agent-yes is the fleet. Around it sit tools that give the fleet somewhere to *reach* — each a
85
+ standalone CLI in its own right, each usable without the others:
86
+
87
+ | | |
88
+ | --- | --- |
89
+ | **[agent-yes](https://github.com/snomiao/agent-yes)** | run, watch, message and delegate to a fleet of AI coding agents |
90
+ | **[slack-term](https://github.com/snomiao/slack-term)** | the human-comms surface — read and write Slack from a terminal, with a confirm gate built for operators who are sometimes machines |
91
+
92
+ The connective tissue is a shared stance rather than a shared library: file-based state over
93
+ daemons, one command surface across two runtimes, gates on the irreversible and warnings on the
94
+ merely unwise, and a hard rule that no real operational data lands in a public repo. Both projects
95
+ arrived at the same interrupt-safety reasoning against completely different substrates — one over
96
+ `pids.jsonl` and a FIFO, the other over Slack reactions.
97
+
82
98
  ---
83
99
 
84
100
  ## Notes
@@ -0,0 +1,240 @@
1
+ ---
2
+ title: A confirm code that goes stale — how slack-term makes agents safe to give a voice
3
+ tag: Ecosystem
4
+ summary: slack-term is the agent-yes fleet's human-comms surface. Its confirm gate hashes the destination, the actor and the conversation's current last message — so it expires the moment anything that mattered moves. Plus what it gets right, and where it is thinner than its README claims.
5
+ ---
6
+
7
+ <p class="lede">An agent that can only read is a curiosity. An agent that can write into the
8
+ channel where your colleagues work is either infrastructure or a hazard, and which one it is
9
+ comes down to a handful of design decisions. <a
10
+ href="https://github.com/snomiao/slack-term">slack-term</a> is the Slack CLI the agent-yes fleet
11
+ uses to talk to humans, and it is worth reading for one idea in particular: a confirmation code
12
+ that <b>goes stale when the conversation moves</b>.</p>
13
+
14
+ ## Where it sits in the ecosystem
15
+
16
+ agent-yes gives a fleet of agents an address space — they can be listed, tailed, messaged and
17
+ delegated to. But a fleet that can only talk to itself is a closed loop. The parts of the system
18
+ that face *humans* are the ones that decide whether any of it is useful: `ay callback` puts a reply
19
+ box on a published report, `ay ch` gives humans and agents one channel, and slack-term takes the
20
+ fleet into the place work already happens.
21
+
22
+ That last one carries a different risk profile from the rest. A callback widget is send-only into a
23
+ terminal you own. Posting to `#general` is irreversible, public, and attributed to *you*. So the
24
+ interesting engineering is not the Slack API wrapper — it is everything built around the moment of
25
+ writing.
26
+
27
+ ## The gate: hash the write, not the intent
28
+
29
+ Every gated write — `send`, `edit`, `delete`, `upload`, `schedule`, `channel create`, `drafts` —
30
+ runs twice. The first run previews and prints a 4-hex code; the second run must present it:
31
+
32
+ ```
33
+ $ slack send "#general" "deploying v2 now"
34
+ From: @alice (U00000001) — Acme
35
+ To: #general
36
+ ...preview of the channel's recent messages...
37
+ Rerun with --code=3f9c
38
+
39
+ $ slack send "#general" "deploying v2 now" --code=3f9c
40
+ ```
41
+
42
+ The code is not a nonce and there is no server, no session, and no stored state. It is
43
+ `sha256(parts.join("\n")).slice(0, 4)` — recomputed from scratch on both runs. What goes into
44
+ `parts` is the whole design (`ts/cli.ts:1433`):
45
+
46
+ ```js
47
+ const code = safetyCode(channelId, threadTs ?? "", lastText, message, self?.userId ?? "");
48
+ ```
49
+
50
+ Read that as a list of things that must not have changed between preview and confirm:
51
+
52
+ - **`channelId` + `threadTs`** — a code minted for one destination cannot confirm a send to another.
53
+ - **`self.userId`** — the acting identity. Preview as one profile, switch, confirm → the code is
54
+ dead. Every gate prints a `From: @handle (Uxxxx) — Workspace` line naming who it will act as, and
55
+ binds that identity into the code, so the label cannot drift from the act.
56
+ - **`lastText`** — **the destination's current last message.**
57
+
58
+ That third one is the good idea. If anyone posts between your preview and your confirm, the code
59
+ you were given no longer verifies, and you are shown the conversation again before you can proceed.
60
+ It is a time-of-check/time-of-use defense built out of content rather than a clock: **no timestamp,
61
+ no expiry, no session — freshness is derived from the thing that would make you change your mind.**
62
+ For `edit` and `delete`, the same trick binds the target's *current remote text*, so you cannot
63
+ edit-over something that was rewritten while you were reading it.
64
+
65
+ ## Gates block, guards warn
66
+
67
+ Four heuristics fire around a send, and not one of them can stop it:
68
+
69
+ | | |
70
+ | --- | --- |
71
+ | **near-duplicate** | character-bigram Dice similarity over normalized text; warns at ≥0.82 against messages already in the thread |
72
+ | **unreplied** | the destination's last message is already yours — you are about to double-post, so it prints a ready-to-paste `slack edit` command for that message instead |
73
+ | **self-DM** | DMing yourself with your own user token delivers but never notifies |
74
+ | **mentions** | `@handle` tokens that could not be resolved will post as plain text and notify nobody |
75
+
76
+ All four are `console.error` and fall through. The only hard stop in the whole path is a missing or
77
+ wrong `--code`.
78
+
79
+ That split is worth stating as a rule, because it is what keeps the system coherent: **irreversible
80
+ things get a gate; advisory things get a line on stderr.** Heuristics that can block teach people to
81
+ reach for a bypass flag — and the moment a bypass flag exists, it will eventually be used on the
82
+ real gate too. Because the fuzzy signals stay advisory, the repo ships **no `--yes`, no `--force`,
83
+ no `SKIP_CONFIRM`** at all.
84
+
85
+ ## The honest part: it is a review gate, not an authorization gate
86
+
87
+ Here is the thing the project is refreshingly clear-eyed about. The gate holds no secret. The
88
+ expected code is deterministic, and the CLI prints it straight back to you on a wrong-code retry:
89
+
90
+ ```
91
+ Code mismatch (got 0000, expected 3f9c)
92
+ Rerun with --code=3f9c
93
+ ```
94
+
95
+ Any script can defeat that in two lines — and the repo's own test suite does exactly that, regexing
96
+ `--code=([0-9a-f]{4})` out of stderr at roughly 19 call sites. The Rust implementation says so in
97
+ its own source: *"Always print the hash so scripts can capture and rerun."*
98
+
99
+ So the gate is not a permission boundary. It is a **forced-review step**: it makes the default path
100
+ one where a human or an agent must look at a rendered preview of exactly what will be posted, where,
101
+ and as whom, before anything happens. An agent determined to route around it can. The value is that
102
+ nothing does so *by accident* — and accident, not malice, is what actually posts to the wrong
103
+ channel at 3am.
104
+
105
+ Calling that out is better engineering than pretending otherwise, because a team that believes a
106
+ review gate is an authorization gate will eventually put something behind it that needed a real one.
107
+
108
+ ## Display names are attacker-controlled
109
+
110
+ Anyone in a Slack workspace can set their own display name, and that name gets drawn into your
111
+ terminal — inside the confirm preview you are about to make a decision from.
112
+ `stripTerminalControls()` (`ts/cli.ts:811`) removes ANSI CSI sequences, all C0/C1 control characters
113
+ including a lone ESC, **and Unicode bidi controls** (LRM/RLM, LRE/RLE/PDF/LRO/RLO, LRI/RLI/FSI/PDI).
114
+
115
+ Stripping ANSI is common. Remembering that a right-to-left override can visually reorder the line
116
+ that is supposed to be telling you what you are about to send is not. Sanitizing *the preview
117
+ itself* follows from taking the gate seriously: a preview that can be made to lie is worse than no
118
+ preview, because it is trusted.
119
+
120
+ The coverage is deliberate but not yet total — it is applied to the sender label and the mention
121
+ previews; the gate's own quoted message previews and channel labels still render Slack-controlled
122
+ text unstripped.
123
+
124
+ ## A denylist stored as hashes
125
+
126
+ The repo is public and its test fixtures come from real Slack recordings, so it treats its own
127
+ corpus as a leak risk. `.githooks/denylist.sha256` holds ~26 SHA-256 hashes and no plaintext, and
128
+ says why in the file:
129
+
130
+ > Stored as hashes on purpose: this repo is public, so it must not spell out the real names and IDs
131
+ > it is protecting.
132
+
133
+ A plaintext list of the names you are protecting is itself the disclosure. There is a second-order
134
+ reason too: plaintext would be rewritten by any future history scrub, silently inverting what the
135
+ hook blocks.
136
+
137
+ Two modes, and the docs are explicit about why both must exist:
138
+
139
+ | mode | sees | role |
140
+ | --- | --- | --- |
141
+ | default | the **staged diff** — added lines only | commit hook |
142
+ | `--all` | **every tracked file** | `prepublishOnly` + CI |
143
+
144
+ The commit hook is *structurally blind* to data already sitting in the tree: committed once, never
145
+ re-added by any later diff, therefore invisible to every future staged scan. That blind spot is not
146
+ hypothetical — it is how real workspace data once reached npm, and the whole-tree gate exists
147
+ specifically to close it. One scanner, three enforcement points (hook, CI, publish), no
148
+ reimplementation per venue.
149
+
150
+ The placeholder scheme deserves a mention because it is *machine-decidable* rather than a style
151
+ guide. A real Slack ID is a type letter followed by 8+ `[A-Z0-9]` **whose tail mixes letters and
152
+ digits**; a `0000` run separately marks an ID as fake. Testing the tail rather than the whole string
153
+ is load-bearing: the leading `C` of `C00000001` would otherwise count as "the letter" and make every
154
+ placeholder read as real. It scans fixture **filenames** as well as bodies.
155
+
156
+ ## Built so an agent is a good citizen
157
+
158
+ The affordances that only make sense when the operator is a machine:
159
+
160
+ - **Reaction-as-ack is deliberately ungated** — "a reaction is trivial and fully reversible, and the
161
+ whole point is a lightweight ack." The cheap polite action stays frictionless while the loud one
162
+ stays gated. 👀 for *seen*, ✅ for *done*, ⏳ for *working on it* — a thread that does not grow.
163
+ - **`--unreplied`** — "only conversations whose last word isn't mine." Whose ball it is, computed
164
+ rather than guessed.
165
+ - **`[+N replies]`** — Slack's history endpoint returns thread parents but no replies, so a naive
166
+ renderer hides entire conversations. Marking them means a reader is never misled into thinking it
167
+ has read everything.
168
+ - **stdout is data, stderr is commentary** in the streaming path, so `slack tail "#general" | grep
169
+ deploy` sees messages and not chrome.
170
+ - **Output is valid input** — a printed permalink or ISO stamp can be pasted straight back as a
171
+ target, with strict parsers that throw rather than guess.
172
+ - **`SKILL.md` ships in the npm tarball** with agent-skill frontmatter, so it triggers on how a user
173
+ *talks* ("any mentions?", "DM @person") rather than on the tool's name.
174
+
175
+ Then there is `ts/todo.ts`, which encodes task state entirely in Slack reactions — 📌 marker,
176
+ ✅/🚫/👀/⏳ progress, stacking reason flags — with no external store, so humans see the same state in
177
+ the Slack UI that the agent queries. Two comments in that file are the best writing in either repo.
178
+
179
+ The first records a **measured negative result**:
180
+
181
+ > the obvious alternative — posting a machine-readable token like `todo:v1 blocked-on=@alice` — does
182
+ > NOT work: Slack's full-text index splits on punctuation, so a quoted search for "blocked-on"
183
+ > returns 0 hits (measured), and "1.91" matches 1.91.0.
184
+
185
+ The second explains an ordering invariant:
186
+
187
+ > **ORDER IS THE WHOLE POINT**: `reactions.add` runs FIRST, then removes. Never invert this.
188
+ > Removing first leaves a window with zero progress reactions; a crash, a 429 or a Ctrl-C inside
189
+ > that window drops the task out of every `todo ls` query permanently, with nothing left to point at
190
+ > it.
191
+
192
+ Add-first's worst case is a message carrying two progress reactions — harmless, *because the
193
+ invariant is "at least one, readers collapse by priority" rather than "exactly one."* Choosing the
194
+ weaker invariant is what makes the operation safe to interrupt. Removes are awaited serially because
195
+ `Promise.all` would drop the ordering guarantee. That is the same reasoning agent-yes applies to its
196
+ own file-based fabric, arrived at independently against a completely different substrate.
197
+
198
+ ## Where it is thinner than it looks
199
+
200
+ A fair reading has to include these.
201
+
202
+ **Credentials at rest are unhardened.** `~/.config/slack-cli/profiles.json` holds tokens and session
203
+ cookies and is written with `writeFileSync` and no `mode` argument, so it lands at the process umask
204
+ — typically world-readable. There is no `chmod` call anywhere in `ts/` or `rs/`. For a project this
205
+ careful about every other boundary, this is the odd one out.
206
+
207
+ **"Verified byte-for-byte" oversells the parity test.** The mechanism is real and well-built —
208
+ byte-identical stdout from both runtimes against a mock server, with an isolated `HOME` so no real
209
+ profile bleeds in — but it covers **2 of 23 commands** (`news`, `search`), and it *skips silently*
210
+ when the Rust binary or the fixtures are absent. A gate that skips by default is not a gate. Worse,
211
+ the one thing that most needs to match across implementations — the write gate — is the thing that
212
+ matches least: the Rust `send` uses `--confirm` rather than `--code`, binds neither the thread nor
213
+ the acting identity, and collapses to `sha256(message)` when its context fetch comes back empty.
214
+
215
+ The two runtimes are not really peers; one is the product and the other is a frozen subset. Compare
216
+ agent-yes, which has the same TS/Rust split and solves it with the opposite polarity: it does not
217
+ diff behavior, it *pins the surface* — a test parses both `ts/subcommands.ts` and `rs/src/cli.rs` as
218
+ text and fails when the subcommand lists disagree, and everything else lives in a parity table
219
+ allowed to say "not planned." Enforce the surface, negotiate the behavior.
220
+
221
+ **`SKILL.md` still points agents at a deprecated package.** It says to install `@snomiao/slack`; the
222
+ README marks that deprecated in favour of `slack-term`. Of the three, this is the one that actively
223
+ misleads, because SKILL.md is the file an agent reads.
224
+
225
+ ## What carries over
226
+
227
+ Three ideas here are worth stealing regardless of Slack:
228
+
229
+ 1. **Bind your confirmation to the state that would change your mind.** Not a nonce, not a
230
+ timestamp — the destination, the actor, and the conversation's current last message. It goes
231
+ stale exactly when it should.
232
+ 2. **Gates block, guards warn — and never mix them.** One hard stop, uniformly applied, is
233
+ comprehensible and needs no bypass flag. The moment a fuzzy heuristic can block, someone builds a
234
+ `--force`, and then it is on the real gate too.
235
+ 3. **Say which one you built.** A forced-review step and an authorization boundary look similar from
236
+ the outside and fail very differently. Naming it correctly is what stops someone putting
237
+ something behind it that needed the other kind.
238
+
239
+ <p class="note">Related: <a href="./2026-08-11-an-address-space-for-agents">an address space for
240
+ agents</a> — why a fleet needs a bus that humans are on too.</p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.271.0",
3
+ "version": "1.272.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
@@ -1,5 +0,0 @@
1
- import "./versionChecker-DCtcFwZ7.js";
2
- import "./rustBinary-BK03gMMg.js";
3
- import { a as trayHiddenMarker, i as launchTray, n as hasDesktop, r as isTrayHidden, t as cmdTray } from "./trayApp-CcQDIINw.js";
4
-
5
- export { cmdTray };