harnery 0.41.0 → 0.43.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 (269) hide show
  1. package/dist/commander.d.ts +16 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +21 -0
  4. package/dist/commands/agents.d.ts.map +1 -1
  5. package/dist/commands/agents.js +63 -5
  6. package/dist/commands/artifacts.d.ts.map +1 -1
  7. package/dist/commands/artifacts.js +113 -12
  8. package/dist/commands/browse.d.ts +164 -0
  9. package/dist/commands/browse.d.ts.map +1 -1
  10. package/dist/commands/browse.js +263 -51
  11. package/dist/commands/claude-desktop.d.ts.map +1 -1
  12. package/dist/commands/claude-desktop.js +52 -0
  13. package/dist/commands/disk.d.ts +6 -0
  14. package/dist/commands/disk.d.ts.map +1 -0
  15. package/dist/commands/disk.js +80 -0
  16. package/dist/commands/doctor.d.ts +8 -0
  17. package/dist/commands/doctor.d.ts.map +1 -1
  18. package/dist/commands/doctor.js +48 -1
  19. package/dist/commands/files.d.ts +16 -0
  20. package/dist/commands/files.d.ts.map +1 -1
  21. package/dist/commands/files.js +54 -2
  22. package/dist/commands/init.d.ts +2 -2
  23. package/dist/commands/init.d.ts.map +1 -1
  24. package/dist/commands/init.js +43 -9
  25. package/dist/commands/rm.d.ts +4 -0
  26. package/dist/commands/rm.d.ts.map +1 -0
  27. package/dist/commands/rm.js +87 -0
  28. package/dist/commands/servers.d.ts +7 -0
  29. package/dist/commands/servers.d.ts.map +1 -0
  30. package/dist/commands/servers.js +394 -0
  31. package/dist/commands/tunnel.d.ts +65 -0
  32. package/dist/commands/tunnel.d.ts.map +1 -1
  33. package/dist/commands/tunnel.js +372 -103
  34. package/dist/commands/web-fetch.d.ts +3 -2
  35. package/dist/commands/web-fetch.d.ts.map +1 -1
  36. package/dist/commands/web-fetch.js +25 -19
  37. package/dist/commands/web.d.ts +7 -1
  38. package/dist/commands/web.d.ts.map +1 -1
  39. package/dist/commands/web.js +9 -3
  40. package/dist/core/adapters/profiles.d.ts +9 -0
  41. package/dist/core/adapters/profiles.d.ts.map +1 -1
  42. package/dist/core/adapters/profiles.js +22 -1
  43. package/dist/core/adapters/types.d.ts +9 -0
  44. package/dist/core/adapters/types.d.ts.map +1 -1
  45. package/dist/core/agents/cli.js +13 -6
  46. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  47. package/dist/core/agents/render/prompt-context.js +11 -5
  48. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  49. package/dist/core/agents/render/session-context.js +59 -13
  50. package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
  51. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  52. package/dist/core/agents/state/live-coordination-view.js +60 -0
  53. package/dist/core/agents/state/names.d.ts +43 -3
  54. package/dist/core/agents/state/names.d.ts.map +1 -1
  55. package/dist/core/agents/state/names.js +87 -3
  56. package/dist/core/artifacts/delivery-card.js +9 -2
  57. package/dist/core/artifacts/index.d.ts +140 -4
  58. package/dist/core/artifacts/index.d.ts.map +1 -1
  59. package/dist/core/artifacts/index.js +522 -49
  60. package/dist/core/config.d.ts +68 -11
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +128 -3
  63. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  64. package/dist/core/diagnostics/bundle.js +2 -2
  65. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  66. package/dist/core/events/v3/producers/recorder.js +16 -0
  67. package/dist/core/events/v3/projection.js +9 -1
  68. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
  69. package/dist/core/events/v3/support-storage/pack-reader.js +42 -16
  70. package/dist/core/hooks/adapter/events.d.ts +17 -0
  71. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  72. package/dist/core/hooks/adapter/events.js +5 -0
  73. package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
  74. package/dist/core/hooks/adapter/wiring.d.ts +37 -7
  75. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  76. package/dist/core/hooks/adapter/wiring.js +62 -4
  77. package/dist/core/hooks/cli.js +131 -23
  78. package/dist/core/hooks/fork-lineage.d.ts +40 -0
  79. package/dist/core/hooks/fork-lineage.d.ts.map +1 -0
  80. package/dist/core/hooks/fork-lineage.js +62 -0
  81. package/dist/core/hooks/resolve/transcript.d.ts +16 -0
  82. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  83. package/dist/core/hooks/resolve/transcript.js +84 -0
  84. package/dist/core/hooks/run-markers.d.ts +37 -0
  85. package/dist/core/hooks/run-markers.d.ts.map +1 -0
  86. package/dist/core/hooks/run-markers.js +109 -0
  87. package/dist/core/hooks/shell-rm.d.ts +2 -0
  88. package/dist/core/hooks/shell-rm.d.ts.map +1 -0
  89. package/dist/core/hooks/shell-rm.js +105 -0
  90. package/dist/core/hooks/shell-tokens.d.ts +12 -0
  91. package/dist/core/hooks/shell-tokens.d.ts.map +1 -0
  92. package/dist/core/hooks/shell-tokens.js +97 -0
  93. package/dist/core/hooks/shell-waiter.d.ts.map +1 -1
  94. package/dist/core/hooks/shell-waiter.js +3 -93
  95. package/dist/core/semantic/contract.d.ts +5 -5
  96. package/dist/core/servers/index.d.ts +211 -0
  97. package/dist/core/servers/index.d.ts.map +1 -0
  98. package/dist/core/servers/index.js +560 -0
  99. package/dist/core/servers/net.d.ts +58 -0
  100. package/dist/core/servers/net.d.ts.map +1 -0
  101. package/dist/core/servers/net.js +270 -0
  102. package/dist/core/servers/tunnels.d.ts +14 -0
  103. package/dist/core/servers/tunnels.d.ts.map +1 -0
  104. package/dist/core/servers/tunnels.js +33 -0
  105. package/dist/core/storage/builtins.js +1 -0
  106. package/dist/core/storage/process-log.d.ts +12 -0
  107. package/dist/core/storage/process-log.d.ts.map +1 -1
  108. package/dist/core/storage/process-log.js +10 -2
  109. package/dist/core/workflow/engine.d.ts.map +1 -1
  110. package/dist/core/workflow/engine.js +30 -1
  111. package/dist/core/workflow/sandbox-projection.d.ts +13 -2
  112. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
  113. package/dist/core/workflow/sandbox-projection.js +23 -0
  114. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  115. package/dist/core/workflow/spawn-claude.js +6 -1
  116. package/dist/core/workflow/spawn-codex.d.ts +2 -0
  117. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  118. package/dist/core/workflow/spawn-codex.js +20 -1
  119. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  120. package/dist/core/workflow/spawn-cursor.js +6 -1
  121. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  122. package/dist/core/workflow/spawn-opencode.js +4 -1
  123. package/dist/core/workflow/types.d.ts +47 -2
  124. package/dist/core/workflow/types.d.ts.map +1 -1
  125. package/dist/core/workflow/worker-access.d.ts +92 -0
  126. package/dist/core/workflow/worker-access.d.ts.map +1 -0
  127. package/dist/core/workflow/worker-access.js +450 -0
  128. package/dist/lib/browser/batch-steps.d.ts +18 -0
  129. package/dist/lib/browser/batch-steps.d.ts.map +1 -0
  130. package/dist/lib/browser/batch-steps.js +82 -0
  131. package/dist/lib/browser/collect/collector.d.ts +99 -0
  132. package/dist/lib/browser/collect/collector.d.ts.map +1 -0
  133. package/dist/lib/browser/collect/collector.js +202 -0
  134. package/dist/lib/browser/collect/fields.d.ts +31 -0
  135. package/dist/lib/browser/collect/fields.d.ts.map +1 -0
  136. package/dist/lib/browser/collect/fields.js +75 -0
  137. package/dist/lib/browser/collect/index.d.ts +6 -0
  138. package/dist/lib/browser/collect/index.d.ts.map +1 -0
  139. package/dist/lib/browser/collect/index.js +5 -0
  140. package/dist/lib/browser/collect/page-driver.d.ts +14 -0
  141. package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
  142. package/dist/lib/browser/collect/page-driver.js +306 -0
  143. package/dist/lib/browser/collect/presets.d.ts +52 -0
  144. package/dist/lib/browser/collect/presets.d.ts.map +1 -0
  145. package/dist/lib/browser/collect/presets.js +176 -0
  146. package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
  147. package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
  148. package/dist/lib/browser/collect/scroll-plan.js +118 -0
  149. package/dist/lib/browser/index.d.ts +1 -1
  150. package/dist/lib/browser/index.d.ts.map +1 -1
  151. package/dist/lib/browser/page-review-judge.d.ts +2 -0
  152. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  153. package/dist/lib/browser/page-review-judge.js +13 -0
  154. package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
  155. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  156. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  157. package/dist/lib/browser/qa-run.js +1 -0
  158. package/dist/lib/browser/windows-chrome.d.ts +59 -0
  159. package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
  160. package/dist/lib/browser/windows-chrome.js +179 -0
  161. package/dist/lib/claude-desktop-share.d.ts +136 -0
  162. package/dist/lib/claude-desktop-share.d.ts.map +1 -0
  163. package/dist/lib/claude-desktop-share.js +394 -0
  164. package/dist/lib/disk-usage.d.ts +50 -0
  165. package/dist/lib/disk-usage.d.ts.map +1 -0
  166. package/dist/lib/disk-usage.js +303 -0
  167. package/dist/lib/docs-lint.d.ts +6 -0
  168. package/dist/lib/docs-lint.d.ts.map +1 -1
  169. package/dist/lib/docs-lint.js +8 -3
  170. package/dist/lib/guarded-remove.d.ts +34 -0
  171. package/dist/lib/guarded-remove.d.ts.map +1 -0
  172. package/dist/lib/guarded-remove.js +310 -0
  173. package/dist/lib/instructions/templates.js +8 -8
  174. package/dist/lib/tunnel/allowlist.d.ts +44 -0
  175. package/dist/lib/tunnel/allowlist.d.ts.map +1 -0
  176. package/dist/lib/tunnel/allowlist.js +204 -0
  177. package/dist/lib/tunnel/current-address.d.ts +41 -0
  178. package/dist/lib/tunnel/current-address.d.ts.map +1 -0
  179. package/dist/lib/tunnel/current-address.js +123 -0
  180. package/dist/lib/tunnel/error-page.d.ts +1 -1
  181. package/dist/lib/tunnel/error-page.d.ts.map +1 -1
  182. package/dist/lib/tunnel/error-page.js +19 -8
  183. package/dist/lib/tunnel/gate.js +33 -7
  184. package/dist/lib/tunnel/path-scope.d.ts +22 -0
  185. package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
  186. package/dist/lib/tunnel/path-scope.js +62 -0
  187. package/dist/lib/tunnel/processes.d.ts +39 -0
  188. package/dist/lib/tunnel/processes.d.ts.map +1 -0
  189. package/dist/lib/tunnel/processes.js +121 -0
  190. package/dist/lib/tunnel/state.d.ts +33 -3
  191. package/dist/lib/tunnel/state.d.ts.map +1 -1
  192. package/dist/lib/tunnel/state.js +115 -30
  193. package/package.json +7 -1
  194. package/schemas/config.schema.json +91 -0
  195. package/src/commander.ts +51 -0
  196. package/src/commands/agents.ts +111 -11
  197. package/src/commands/artifacts.ts +171 -19
  198. package/src/commands/browse.ts +400 -57
  199. package/src/commands/claude-desktop.ts +61 -0
  200. package/src/commands/disk.ts +114 -0
  201. package/src/commands/doctor.ts +52 -1
  202. package/src/commands/files.ts +93 -2
  203. package/src/commands/init.ts +55 -5
  204. package/src/commands/rm.ts +115 -0
  205. package/src/commands/servers.ts +506 -0
  206. package/src/commands/tunnel.ts +494 -116
  207. package/src/commands/web-fetch.ts +30 -19
  208. package/src/commands/web.ts +9 -3
  209. package/src/core/adapters/profiles.ts +22 -1
  210. package/src/core/adapters/types.ts +10 -0
  211. package/src/core/agents/cli.ts +14 -6
  212. package/src/core/agents/render/prompt-context.ts +25 -8
  213. package/src/core/agents/render/session-context.ts +84 -16
  214. package/src/core/agents/state/live-coordination-view.ts +73 -0
  215. package/src/core/agents/state/names.ts +117 -6
  216. package/src/core/artifacts/delivery-card.ts +10 -2
  217. package/src/core/artifacts/index.ts +697 -44
  218. package/src/core/config.ts +183 -4
  219. package/src/core/diagnostics/bundle.ts +2 -1
  220. package/src/core/events/v3/producers/recorder.ts +16 -0
  221. package/src/core/events/v3/projection.ts +11 -1
  222. package/src/core/events/v3/support-storage/pack-reader.ts +45 -19
  223. package/src/core/hooks/adapter/events.ts +19 -0
  224. package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
  225. package/src/core/hooks/adapter/wiring.ts +116 -8
  226. package/src/core/hooks/cli.ts +152 -22
  227. package/src/core/hooks/fork-lineage.ts +82 -0
  228. package/src/core/hooks/resolve/transcript.ts +88 -0
  229. package/src/core/hooks/run-markers.ts +132 -0
  230. package/src/core/hooks/shell-rm.ts +103 -0
  231. package/src/core/hooks/shell-tokens.ts +96 -0
  232. package/src/core/hooks/shell-waiter.ts +4 -91
  233. package/src/core/servers/index.ts +712 -0
  234. package/src/core/servers/net.ts +283 -0
  235. package/src/core/servers/tunnels.ts +39 -0
  236. package/src/core/storage/builtins.ts +1 -0
  237. package/src/core/storage/process-log.ts +17 -2
  238. package/src/core/workflow/engine.ts +38 -1
  239. package/src/core/workflow/sandbox-projection.ts +38 -2
  240. package/src/core/workflow/spawn-claude.ts +5 -1
  241. package/src/core/workflow/spawn-codex.ts +18 -1
  242. package/src/core/workflow/spawn-cursor.ts +5 -1
  243. package/src/core/workflow/spawn-opencode.ts +4 -1
  244. package/src/core/workflow/types.ts +62 -2
  245. package/src/core/workflow/worker-access.ts +525 -0
  246. package/src/lib/browser/batch-steps.ts +79 -0
  247. package/src/lib/browser/collect/collector.ts +306 -0
  248. package/src/lib/browser/collect/fields.ts +87 -0
  249. package/src/lib/browser/collect/index.ts +47 -0
  250. package/src/lib/browser/collect/page-driver.ts +327 -0
  251. package/src/lib/browser/collect/presets.ts +237 -0
  252. package/src/lib/browser/collect/scroll-plan.ts +179 -0
  253. package/src/lib/browser/index.ts +1 -0
  254. package/src/lib/browser/page-review-judge.ts +16 -0
  255. package/src/lib/browser/qa-run-contracts.ts +11 -0
  256. package/src/lib/browser/qa-run.ts +1 -0
  257. package/src/lib/browser/windows-chrome.ts +227 -0
  258. package/src/lib/claude-desktop-share.ts +511 -0
  259. package/src/lib/disk-usage.ts +347 -0
  260. package/src/lib/docs-lint.ts +8 -3
  261. package/src/lib/guarded-remove.ts +376 -0
  262. package/src/lib/instructions/templates.ts +8 -8
  263. package/src/lib/tunnel/allowlist.ts +208 -0
  264. package/src/lib/tunnel/current-address.ts +158 -0
  265. package/src/lib/tunnel/error-page.ts +25 -9
  266. package/src/lib/tunnel/gate.ts +38 -9
  267. package/src/lib/tunnel/path-scope.ts +73 -0
  268. package/src/lib/tunnel/processes.ts +149 -0
  269. package/src/lib/tunnel/state.ts +143 -30
@@ -0,0 +1,103 @@
1
+ import { basename, shellCommandFromTool, tokenize } from "./shell-tokens.ts";
2
+
3
+ /**
4
+ * Deny a shell `rm` and name the guarded replacement. The instructions have
5
+ * said "use <bin> rm instead of shell rm" since the command shipped, and
6
+ * agents still reached for bare rm several times a day, often on unquoted
7
+ * variable paths that Claude Code then stops on for a human prompt. A
8
+ * repeated, machine-detectable failure belongs in a gate, not more prose.
9
+ *
10
+ * This is a guardrail against the reflex, not a security boundary: it reads
11
+ * command positions only (so `git rm`, `docker rm`, and quoted examples pass),
12
+ * plus the `xargs rm`, `find -exec rm`, and `bash -c '...rm...'` shapes.
13
+ * HARNERY_ALLOW_RM=1 on the command is the deliberate override.
14
+ */
15
+
16
+ const OVERRIDE = "HARNERY_ALLOW_RM=1";
17
+ const SEPARATORS = new Set([";", "|", "||", "&", "&&", "(", ")", "{", "}"]);
18
+ const KEYWORDS = new Set(["do", "if", "then", "elif", "else", "!", "while", "until", "time"]);
19
+ const WRAPPERS = new Set(["command", "exec", "builtin", "nohup", "sudo", "doas", "env", "nice"]);
20
+ // xargs options that consume the following token as their value.
21
+ const XARGS_VALUE_FLAGS = new Set(["-n", "-I", "-L", "-P", "-d", "-E", "-s", "-a"]);
22
+
23
+ function isRm(value: string, quoted: boolean): boolean {
24
+ return !quoted && basename(value) === "rm";
25
+ }
26
+
27
+ function invokesRm(source: string, depth = 0): boolean {
28
+ if (depth > 8) return false;
29
+ const tokens = tokenize(source);
30
+ if (tokens.some((t) => !t.quoted && t.value === OVERRIDE)) return false;
31
+ let start = true;
32
+ let afterWrapper = false;
33
+ for (let i = 0; i < tokens.length; i++) {
34
+ const token = tokens[i]!;
35
+ if (token.operator) {
36
+ if (SEPARATORS.has(token.value)) {
37
+ start = true;
38
+ afterWrapper = false;
39
+ }
40
+ continue;
41
+ }
42
+ if (!start) continue;
43
+ if (!token.quoted) {
44
+ if (KEYWORDS.has(token.value)) continue;
45
+ if (/^[A-Za-z_][A-Za-z_0-9]*=/.test(token.value)) continue;
46
+ if (afterWrapper && token.value.startsWith("-")) continue;
47
+ }
48
+ const name = basename(token.value);
49
+ if (WRAPPERS.has(name)) {
50
+ afterWrapper = true;
51
+ continue;
52
+ }
53
+ start = false;
54
+ afterWrapper = false;
55
+ if (isRm(token.value, token.quoted)) return true;
56
+ if (name === "xargs") {
57
+ for (let j = i + 1; j < tokens.length && !tokens[j]!.operator; j++) {
58
+ const arg = tokens[j]!;
59
+ if (arg.value.startsWith("-")) {
60
+ if (XARGS_VALUE_FLAGS.has(arg.value)) j++;
61
+ continue;
62
+ }
63
+ if (isRm(arg.value, arg.quoted)) return true;
64
+ break;
65
+ }
66
+ }
67
+ if (name === "find") {
68
+ for (let j = i + 1; j < tokens.length && !tokens[j]!.operator; j++) {
69
+ if (!/^-(?:exec|execdir|ok|okdir)$/.test(tokens[j]!.value)) continue;
70
+ const next = tokens[j + 1];
71
+ if (next && !next.operator && isRm(next.value, next.quoted)) return true;
72
+ }
73
+ }
74
+ if (["bash", "sh", "dash", "zsh", "ksh"].includes(name)) {
75
+ for (let j = i + 1; j < tokens.length && !tokens[j]!.operator; j++) {
76
+ if (/^-[a-z]*c[a-z]*$/.test(tokens[j]!.value)) {
77
+ const script = tokens[j + 1];
78
+ if (script && !script.operator && invokesRm(script.value, depth + 1)) return true;
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ }
84
+ return false;
85
+ }
86
+
87
+ export function shellRmReason(
88
+ toolName: unknown,
89
+ toolInput: unknown,
90
+ binName: string,
91
+ ): string | null {
92
+ const command = shellCommandFromTool(toolName, toolInput);
93
+ if (command === null || !invokesRm(command)) return null;
94
+ return (
95
+ `Shell rm blocked: it skips the resolved-path, Git-ownership, and peer-claim checks, and an ` +
96
+ `unquoted variable path can expand to the wrong target. Use the guarded paths instead: ` +
97
+ `\`${binName} rm --root <directory> <paths...>\` previews removal of untracked files (add ` +
98
+ `--yes to delete, -r for a directory); \`git rm\` removes tracked source; ` +
99
+ `\`${binName} artifacts discard <id>\` retires managed evidence. To replace a file, overwrite ` +
100
+ `it in place or \`mv\` the new file over the old one, so nothing needs deleting. If none of ` +
101
+ `these fit, prefix the command with ${OVERRIDE}.`
102
+ );
103
+ }
@@ -0,0 +1,96 @@
1
+ /** Shared, deliberately small shell lexer for PreToolUse command guards. */
2
+
3
+ export interface Token {
4
+ value: string;
5
+ quoted: boolean;
6
+ operator?: boolean;
7
+ }
8
+
9
+ /** Keep quoted arguments opaque, including examples and heredoc bodies. */
10
+ export function tokenize(source: string): Token[] {
11
+ const out: Token[] = [];
12
+ const heredocs: { end: string; tabs: boolean }[] = [];
13
+ for (let i = 0; i < source.length; ) {
14
+ const ch = source[i]!;
15
+ if (ch === "\\" && source[i + 1] === "\n") {
16
+ i += 2;
17
+ continue;
18
+ }
19
+ if (ch === "\n") {
20
+ out.push({ value: ";", quoted: false, operator: true });
21
+ i++;
22
+ for (const doc of heredocs.splice(0)) {
23
+ while (i < source.length) {
24
+ const end = source.indexOf("\n", i);
25
+ const stop = end === -1 ? source.length : end;
26
+ const line = source.slice(i, stop);
27
+ i = end === -1 ? source.length : end + 1;
28
+ if ((doc.tabs ? line.replace(/^\t+/, "") : line) === doc.end) break;
29
+ }
30
+ }
31
+ continue;
32
+ }
33
+ if (/\s/.test(ch)) {
34
+ i++;
35
+ continue;
36
+ }
37
+ if (ch === "#") {
38
+ const end = source.indexOf("\n", i);
39
+ i = end === -1 ? source.length : end;
40
+ continue;
41
+ }
42
+ if (";|&(){}<>".includes(ch)) {
43
+ const op = source.slice(i).match(/^(?:<<-?|>>|&&|\|\||[;|&(){}<>])/)![0];
44
+ out.push({ value: op, quoted: false, operator: true });
45
+ i += op.length;
46
+ continue;
47
+ }
48
+ let value = "";
49
+ let quoted = false;
50
+ while (i < source.length && !/[\s;|&(){}<>]/.test(source[i]!)) {
51
+ const next = source[i++]!;
52
+ if (next === "'" || next === '"' || next === "`") {
53
+ quoted = true;
54
+ while (i < source.length && source[i] !== next) {
55
+ if (source[i] === "\\" && next !== "'" && i + 1 < source.length) i++;
56
+ value += source[i++]!;
57
+ }
58
+ if (source[i] === next) i++;
59
+ } else if (next === "\\" && i < source.length) {
60
+ const escaped = source[i++]!;
61
+ if (escaped !== "\n") value += escaped;
62
+ } else {
63
+ value += next;
64
+ }
65
+ }
66
+ const previous = out.at(-1)?.value;
67
+ if (previous === "<<" || previous === "<<-") {
68
+ heredocs.push({ end: value, tabs: previous === "<<-" });
69
+ }
70
+ out.push({ value, quoted });
71
+ }
72
+ return out;
73
+ }
74
+
75
+ export function basename(value: string): string {
76
+ return value.split(/[\\/]/).at(-1) ?? value;
77
+ }
78
+
79
+ /** The command string of a Bash-style tool call, or null for any other tool. */
80
+ export function shellCommandFromTool(toolName: unknown, toolInput: unknown): string | null {
81
+ if (typeof toolName !== "string") return null;
82
+ const name = toolName.toLowerCase().replace(/^functions\./, "");
83
+ if (!["bash", "shell", "shell_command", "exec_command"].includes(name)) return null;
84
+ let input = toolInput;
85
+ if (typeof input === "string") {
86
+ try {
87
+ input = JSON.parse(input);
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+ if (!input || typeof input !== "object") return null;
93
+ const args = input as Record<string, unknown>;
94
+ const command = args.command ?? args.cmd;
95
+ return typeof command === "string" ? command : null;
96
+ }
@@ -1,3 +1,5 @@
1
+ import { basename, shellCommandFromTool, tokenize } from "./shell-tokens.ts";
2
+
1
3
  /** Recognize inline waiters; this is not a proof of arbitrary shell termination. */
2
4
  const REASON =
3
5
  "Inline shell waiter blocked: polling loops and delayed log-only checks can outlive the job " +
@@ -6,82 +8,6 @@ const REASON =
6
8
  "nonzero timeout exit, and producer-exit/failure detection. Do not create another waiter " +
7
9
  "to check progress or infer completion from a broad pgrep -f pattern.";
8
10
 
9
- interface Token {
10
- value: string;
11
- quoted: boolean;
12
- operator?: boolean;
13
- }
14
-
15
- /** Keep quoted arguments opaque, including examples and heredoc bodies. */
16
- function tokenize(source: string): Token[] {
17
- const out: Token[] = [];
18
- const heredocs: { end: string; tabs: boolean }[] = [];
19
- for (let i = 0; i < source.length; ) {
20
- const ch = source[i]!;
21
- if (ch === "\\" && source[i + 1] === "\n") {
22
- i += 2;
23
- continue;
24
- }
25
- if (ch === "\n") {
26
- out.push({ value: ";", quoted: false, operator: true });
27
- i++;
28
- for (const doc of heredocs.splice(0)) {
29
- while (i < source.length) {
30
- const end = source.indexOf("\n", i);
31
- const stop = end === -1 ? source.length : end;
32
- const line = source.slice(i, stop);
33
- i = end === -1 ? source.length : end + 1;
34
- if ((doc.tabs ? line.replace(/^\t+/, "") : line) === doc.end) break;
35
- }
36
- }
37
- continue;
38
- }
39
- if (/\s/.test(ch)) {
40
- i++;
41
- continue;
42
- }
43
- if (ch === "#") {
44
- const end = source.indexOf("\n", i);
45
- i = end === -1 ? source.length : end;
46
- continue;
47
- }
48
- if (";|&(){}<>".includes(ch)) {
49
- const op = source.slice(i).match(/^(?:<<-?|>>|&&|\|\||[;|&(){}<>])/)![0];
50
- out.push({ value: op, quoted: false, operator: true });
51
- i += op.length;
52
- continue;
53
- }
54
- let value = "";
55
- let quoted = false;
56
- while (i < source.length && !/[\s;|&(){}<>]/.test(source[i]!)) {
57
- const next = source[i++]!;
58
- if (next === "'" || next === '"' || next === "`") {
59
- quoted = true;
60
- while (i < source.length && source[i] !== next) {
61
- if (source[i] === "\\" && next !== "'" && i + 1 < source.length) i++;
62
- value += source[i++]!;
63
- }
64
- if (source[i] === next) i++;
65
- } else if (next === "\\" && i < source.length) {
66
- const escaped = source[i++]!;
67
- if (escaped !== "\n") value += escaped;
68
- } else {
69
- value += next;
70
- }
71
- }
72
- const previous = out.at(-1)?.value;
73
- if (previous === "<<" || previous === "<<-") {
74
- heredocs.push({ end: value, tabs: previous === "<<-" });
75
- }
76
- out.push({ value, quoted });
77
- }
78
- return out;
79
- }
80
-
81
- function basename(value: string): string {
82
- return value.split(/[\\/]/).at(-1) ?? value;
83
- }
84
-
85
11
  function isWaiter(source: string, depth = 0): boolean {
86
12
  if (depth > 8) return false;
87
13
  const tokens = tokenize(source);
@@ -148,19 +74,6 @@ function isWaiter(source: string, depth = 0): boolean {
148
74
  }
149
75
 
150
76
  export function shellWaiterReason(toolName: unknown, toolInput: unknown): string | null {
151
- if (typeof toolName !== "string") return null;
152
- const name = toolName.toLowerCase().replace(/^functions\./, "");
153
- if (!["bash", "shell", "shell_command", "exec_command"].includes(name)) return null;
154
- let input = toolInput;
155
- if (typeof input === "string") {
156
- try {
157
- input = JSON.parse(input);
158
- } catch {
159
- return null;
160
- }
161
- }
162
- if (!input || typeof input !== "object") return null;
163
- const args = input as Record<string, unknown>;
164
- const command = args.command ?? args.cmd;
165
- return typeof command === "string" && isWaiter(command) ? REASON : null;
77
+ const command = shellCommandFromTool(toolName, toolInput);
78
+ return command !== null && isWaiter(command) ? REASON : null;
166
79
  }