harnery 0.41.0 → 0.42.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 (212) hide show
  1. package/dist/commander.js +3 -0
  2. package/dist/commands/agents.d.ts.map +1 -1
  3. package/dist/commands/agents.js +9 -1
  4. package/dist/commands/artifacts.d.ts.map +1 -1
  5. package/dist/commands/artifacts.js +113 -12
  6. package/dist/commands/browse.d.ts +164 -0
  7. package/dist/commands/browse.d.ts.map +1 -1
  8. package/dist/commands/browse.js +263 -51
  9. package/dist/commands/claude-desktop.d.ts.map +1 -1
  10. package/dist/commands/claude-desktop.js +52 -0
  11. package/dist/commands/disk.d.ts +6 -0
  12. package/dist/commands/disk.d.ts.map +1 -0
  13. package/dist/commands/disk.js +80 -0
  14. package/dist/commands/doctor.d.ts +8 -0
  15. package/dist/commands/doctor.d.ts.map +1 -1
  16. package/dist/commands/doctor.js +48 -1
  17. package/dist/commands/files.d.ts +16 -0
  18. package/dist/commands/files.d.ts.map +1 -1
  19. package/dist/commands/files.js +54 -2
  20. package/dist/commands/init.d.ts +2 -2
  21. package/dist/commands/init.d.ts.map +1 -1
  22. package/dist/commands/init.js +43 -9
  23. package/dist/commands/rm.d.ts +4 -0
  24. package/dist/commands/rm.d.ts.map +1 -0
  25. package/dist/commands/rm.js +87 -0
  26. package/dist/commands/servers.d.ts +7 -0
  27. package/dist/commands/servers.d.ts.map +1 -0
  28. package/dist/commands/servers.js +394 -0
  29. package/dist/commands/tunnel.d.ts.map +1 -1
  30. package/dist/commands/tunnel.js +82 -2
  31. package/dist/core/adapters/profiles.d.ts +9 -0
  32. package/dist/core/adapters/profiles.d.ts.map +1 -1
  33. package/dist/core/adapters/profiles.js +22 -1
  34. package/dist/core/adapters/types.d.ts +9 -0
  35. package/dist/core/adapters/types.d.ts.map +1 -1
  36. package/dist/core/agents/cli.js +13 -6
  37. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  38. package/dist/core/agents/render/session-context.js +2 -0
  39. package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
  40. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  41. package/dist/core/agents/state/live-coordination-view.js +60 -0
  42. package/dist/core/artifacts/delivery-card.js +9 -2
  43. package/dist/core/artifacts/index.d.ts +140 -4
  44. package/dist/core/artifacts/index.d.ts.map +1 -1
  45. package/dist/core/artifacts/index.js +522 -49
  46. package/dist/core/config.d.ts +68 -11
  47. package/dist/core/config.d.ts.map +1 -1
  48. package/dist/core/config.js +128 -3
  49. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  50. package/dist/core/diagnostics/bundle.js +2 -2
  51. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  52. package/dist/core/events/v3/producers/recorder.js +16 -0
  53. package/dist/core/events/v3/projection.js +9 -1
  54. package/dist/core/hooks/adapter/events.d.ts +17 -0
  55. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  56. package/dist/core/hooks/adapter/events.js +5 -0
  57. package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
  58. package/dist/core/hooks/adapter/wiring.d.ts +37 -7
  59. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  60. package/dist/core/hooks/adapter/wiring.js +62 -4
  61. package/dist/core/hooks/cli.js +45 -6
  62. package/dist/core/hooks/run-markers.d.ts +37 -0
  63. package/dist/core/hooks/run-markers.d.ts.map +1 -0
  64. package/dist/core/hooks/run-markers.js +109 -0
  65. package/dist/core/servers/index.d.ts +211 -0
  66. package/dist/core/servers/index.d.ts.map +1 -0
  67. package/dist/core/servers/index.js +560 -0
  68. package/dist/core/servers/net.d.ts +58 -0
  69. package/dist/core/servers/net.d.ts.map +1 -0
  70. package/dist/core/servers/net.js +270 -0
  71. package/dist/core/servers/tunnels.d.ts +14 -0
  72. package/dist/core/servers/tunnels.d.ts.map +1 -0
  73. package/dist/core/servers/tunnels.js +33 -0
  74. package/dist/core/storage/builtins.js +1 -0
  75. package/dist/core/workflow/engine.d.ts.map +1 -1
  76. package/dist/core/workflow/engine.js +30 -1
  77. package/dist/core/workflow/sandbox-projection.d.ts +13 -2
  78. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
  79. package/dist/core/workflow/sandbox-projection.js +23 -0
  80. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  81. package/dist/core/workflow/spawn-claude.js +6 -1
  82. package/dist/core/workflow/spawn-codex.d.ts +2 -0
  83. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  84. package/dist/core/workflow/spawn-codex.js +20 -1
  85. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  86. package/dist/core/workflow/spawn-cursor.js +6 -1
  87. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  88. package/dist/core/workflow/spawn-opencode.js +4 -1
  89. package/dist/core/workflow/types.d.ts +47 -2
  90. package/dist/core/workflow/types.d.ts.map +1 -1
  91. package/dist/core/workflow/worker-access.d.ts +92 -0
  92. package/dist/core/workflow/worker-access.d.ts.map +1 -0
  93. package/dist/core/workflow/worker-access.js +450 -0
  94. package/dist/lib/browser/batch-steps.d.ts +18 -0
  95. package/dist/lib/browser/batch-steps.d.ts.map +1 -0
  96. package/dist/lib/browser/batch-steps.js +82 -0
  97. package/dist/lib/browser/collect/collector.d.ts +99 -0
  98. package/dist/lib/browser/collect/collector.d.ts.map +1 -0
  99. package/dist/lib/browser/collect/collector.js +202 -0
  100. package/dist/lib/browser/collect/fields.d.ts +31 -0
  101. package/dist/lib/browser/collect/fields.d.ts.map +1 -0
  102. package/dist/lib/browser/collect/fields.js +75 -0
  103. package/dist/lib/browser/collect/index.d.ts +6 -0
  104. package/dist/lib/browser/collect/index.d.ts.map +1 -0
  105. package/dist/lib/browser/collect/index.js +5 -0
  106. package/dist/lib/browser/collect/page-driver.d.ts +14 -0
  107. package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
  108. package/dist/lib/browser/collect/page-driver.js +306 -0
  109. package/dist/lib/browser/collect/presets.d.ts +52 -0
  110. package/dist/lib/browser/collect/presets.d.ts.map +1 -0
  111. package/dist/lib/browser/collect/presets.js +176 -0
  112. package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
  113. package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
  114. package/dist/lib/browser/collect/scroll-plan.js +118 -0
  115. package/dist/lib/browser/index.d.ts +1 -1
  116. package/dist/lib/browser/index.d.ts.map +1 -1
  117. package/dist/lib/browser/page-review-judge.d.ts +2 -0
  118. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  119. package/dist/lib/browser/page-review-judge.js +13 -0
  120. package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
  121. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  122. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  123. package/dist/lib/browser/qa-run.js +1 -0
  124. package/dist/lib/browser/windows-chrome.d.ts +59 -0
  125. package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
  126. package/dist/lib/browser/windows-chrome.js +179 -0
  127. package/dist/lib/claude-desktop-share.d.ts +136 -0
  128. package/dist/lib/claude-desktop-share.d.ts.map +1 -0
  129. package/dist/lib/claude-desktop-share.js +394 -0
  130. package/dist/lib/disk-usage.d.ts +50 -0
  131. package/dist/lib/disk-usage.d.ts.map +1 -0
  132. package/dist/lib/disk-usage.js +303 -0
  133. package/dist/lib/docs-lint.d.ts +6 -0
  134. package/dist/lib/docs-lint.d.ts.map +1 -1
  135. package/dist/lib/docs-lint.js +8 -3
  136. package/dist/lib/guarded-remove.d.ts +34 -0
  137. package/dist/lib/guarded-remove.d.ts.map +1 -0
  138. package/dist/lib/guarded-remove.js +310 -0
  139. package/dist/lib/instructions/templates.js +8 -8
  140. package/dist/lib/tunnel/error-page.d.ts +1 -1
  141. package/dist/lib/tunnel/error-page.d.ts.map +1 -1
  142. package/dist/lib/tunnel/error-page.js +19 -8
  143. package/dist/lib/tunnel/gate.js +20 -1
  144. package/dist/lib/tunnel/path-scope.d.ts +22 -0
  145. package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
  146. package/dist/lib/tunnel/path-scope.js +62 -0
  147. package/dist/lib/tunnel/state.d.ts +5 -0
  148. package/dist/lib/tunnel/state.d.ts.map +1 -1
  149. package/dist/lib/tunnel/state.js +17 -3
  150. package/package.json +6 -1
  151. package/schemas/config.schema.json +91 -0
  152. package/src/commander.ts +19 -0
  153. package/src/commands/agents.ts +14 -0
  154. package/src/commands/artifacts.ts +171 -19
  155. package/src/commands/browse.ts +400 -57
  156. package/src/commands/claude-desktop.ts +61 -0
  157. package/src/commands/disk.ts +114 -0
  158. package/src/commands/doctor.ts +52 -1
  159. package/src/commands/files.ts +93 -2
  160. package/src/commands/init.ts +55 -5
  161. package/src/commands/rm.ts +115 -0
  162. package/src/commands/servers.ts +506 -0
  163. package/src/commands/tunnel.ts +102 -2
  164. package/src/core/adapters/profiles.ts +22 -1
  165. package/src/core/adapters/types.ts +10 -0
  166. package/src/core/agents/cli.ts +14 -6
  167. package/src/core/agents/render/session-context.ts +1 -0
  168. package/src/core/agents/state/live-coordination-view.ts +73 -0
  169. package/src/core/artifacts/delivery-card.ts +10 -2
  170. package/src/core/artifacts/index.ts +697 -44
  171. package/src/core/config.ts +183 -4
  172. package/src/core/diagnostics/bundle.ts +2 -1
  173. package/src/core/events/v3/producers/recorder.ts +16 -0
  174. package/src/core/events/v3/projection.ts +11 -1
  175. package/src/core/hooks/adapter/events.ts +19 -0
  176. package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
  177. package/src/core/hooks/adapter/wiring.ts +116 -8
  178. package/src/core/hooks/cli.ts +47 -6
  179. package/src/core/hooks/run-markers.ts +132 -0
  180. package/src/core/servers/index.ts +712 -0
  181. package/src/core/servers/net.ts +283 -0
  182. package/src/core/servers/tunnels.ts +39 -0
  183. package/src/core/storage/builtins.ts +1 -0
  184. package/src/core/workflow/engine.ts +38 -1
  185. package/src/core/workflow/sandbox-projection.ts +38 -2
  186. package/src/core/workflow/spawn-claude.ts +5 -1
  187. package/src/core/workflow/spawn-codex.ts +18 -1
  188. package/src/core/workflow/spawn-cursor.ts +5 -1
  189. package/src/core/workflow/spawn-opencode.ts +4 -1
  190. package/src/core/workflow/types.ts +62 -2
  191. package/src/core/workflow/worker-access.ts +525 -0
  192. package/src/lib/browser/batch-steps.ts +79 -0
  193. package/src/lib/browser/collect/collector.ts +306 -0
  194. package/src/lib/browser/collect/fields.ts +87 -0
  195. package/src/lib/browser/collect/index.ts +47 -0
  196. package/src/lib/browser/collect/page-driver.ts +327 -0
  197. package/src/lib/browser/collect/presets.ts +237 -0
  198. package/src/lib/browser/collect/scroll-plan.ts +179 -0
  199. package/src/lib/browser/index.ts +1 -0
  200. package/src/lib/browser/page-review-judge.ts +16 -0
  201. package/src/lib/browser/qa-run-contracts.ts +11 -0
  202. package/src/lib/browser/qa-run.ts +1 -0
  203. package/src/lib/browser/windows-chrome.ts +227 -0
  204. package/src/lib/claude-desktop-share.ts +511 -0
  205. package/src/lib/disk-usage.ts +347 -0
  206. package/src/lib/docs-lint.ts +8 -3
  207. package/src/lib/guarded-remove.ts +376 -0
  208. package/src/lib/instructions/templates.ts +8 -8
  209. package/src/lib/tunnel/error-page.ts +25 -9
  210. package/src/lib/tunnel/gate.ts +22 -1
  211. package/src/lib/tunnel/path-scope.ts +73 -0
  212. package/src/lib/tunnel/state.ts +20 -3
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Local socket and process observation for the server registry.
3
+ *
4
+ * Linux reads procfs directly (no `ss`/`lsof` dependency, works under WSL).
5
+ * macOS shells out to `lsof` and `ps`. Other platforms report `unsupported`
6
+ * so callers can say the scan did not run rather than implying it found
7
+ * nothing.
8
+ */
9
+ import { spawnSync } from "node:child_process";
10
+ import { readdirSync, readFileSync, readlinkSync } from "node:fs";
11
+ export function scanSupport() {
12
+ if (process.platform === "linux")
13
+ return "procfs";
14
+ if (process.platform === "darwin")
15
+ return "lsof";
16
+ return "unsupported";
17
+ }
18
+ /** Parse `/proc/net/tcp` or `/proc/net/tcp6` content. */
19
+ export function parseProcNetTcp(content) {
20
+ const rows = [];
21
+ for (const line of content.split("\n").slice(1)) {
22
+ const fields = line.trim().split(/\s+/);
23
+ if (fields.length < 10)
24
+ continue;
25
+ const [address, portHex] = (fields[1] ?? "").split(":");
26
+ if (!address || !portHex)
27
+ continue;
28
+ rows.push({
29
+ localAddress: decodeProcAddress(address),
30
+ localPort: Number.parseInt(portHex, 16),
31
+ state: fields[3] ?? "",
32
+ inode: fields[9] ?? "",
33
+ });
34
+ }
35
+ return rows;
36
+ }
37
+ function decodeProcAddress(hex) {
38
+ if (hex.length === 8) {
39
+ const bytes = hex.match(/../g)?.map((pair) => Number.parseInt(pair, 16)) ?? [];
40
+ return bytes.reverse().join(".");
41
+ }
42
+ if (/^0+$/.test(hex))
43
+ return "::";
44
+ if (hex === "00000000000000000000000001000000")
45
+ return "::1";
46
+ if (hex.startsWith("0000000000000000FFFF0000"))
47
+ return decodeProcAddress(hex.slice(24));
48
+ return `ipv6:${hex.toLowerCase()}`;
49
+ }
50
+ function readProcSockets() {
51
+ const rows = [];
52
+ for (const file of ["/proc/net/tcp", "/proc/net/tcp6"]) {
53
+ try {
54
+ rows.push(...parseProcNetTcp(readFileSync(file, "utf8")));
55
+ }
56
+ catch {
57
+ // tcp6 is absent on IPv6-disabled kernels.
58
+ }
59
+ }
60
+ return rows;
61
+ }
62
+ /** Map socket inodes to the pids holding them (only processes we may read). */
63
+ function socketOwners(inodes) {
64
+ const owners = new Map();
65
+ if (inodes.size === 0)
66
+ return owners;
67
+ let entries;
68
+ try {
69
+ entries = readdirSync("/proc");
70
+ }
71
+ catch {
72
+ return owners;
73
+ }
74
+ for (const entry of entries) {
75
+ if (!/^\d+$/.test(entry))
76
+ continue;
77
+ let fds;
78
+ try {
79
+ fds = readdirSync(`/proc/${entry}/fd`);
80
+ }
81
+ catch {
82
+ continue;
83
+ }
84
+ for (const fd of fds) {
85
+ let target;
86
+ try {
87
+ target = readlinkSync(`/proc/${entry}/fd/${fd}`);
88
+ }
89
+ catch {
90
+ continue;
91
+ }
92
+ const match = /^socket:\[(\d+)\]$/.exec(target);
93
+ if (match?.[1] && inodes.has(match[1]) && !owners.has(match[1])) {
94
+ owners.set(match[1], Number(entry));
95
+ }
96
+ }
97
+ if (owners.size === inodes.size)
98
+ break;
99
+ }
100
+ return owners;
101
+ }
102
+ /** Listening TCP sockets whose owning process this user can see. */
103
+ export function listListeningSockets() {
104
+ const support = scanSupport();
105
+ if (support === "procfs") {
106
+ const listening = readProcSockets().filter((row) => row.state === "0A");
107
+ const owners = socketOwners(new Set(listening.map((row) => row.inode)));
108
+ const seen = new Set();
109
+ const sockets = [];
110
+ for (const row of listening) {
111
+ const pid = owners.get(row.inode);
112
+ if (!pid)
113
+ continue;
114
+ const key = `${pid}:${row.localPort}`;
115
+ if (seen.has(key))
116
+ continue;
117
+ seen.add(key);
118
+ sockets.push({ pid, port: row.localPort, address: row.localAddress });
119
+ }
120
+ return sockets.sort((a, b) => a.port - b.port);
121
+ }
122
+ if (support === "lsof")
123
+ return lsofListeningSockets();
124
+ return [];
125
+ }
126
+ /**
127
+ * Every listening TCP port on this machine, including ports whose owner this
128
+ * user cannot see (containers, other users). Used to avoid port collisions.
129
+ */
130
+ export function listListeningPorts() {
131
+ const support = scanSupport();
132
+ if (support === "procfs")
133
+ return new Set(readProcSockets()
134
+ .filter((row) => row.state === "0A")
135
+ .map((row) => row.localPort));
136
+ if (support === "lsof")
137
+ return new Set(listListeningSockets().map((socket) => socket.port));
138
+ return new Set();
139
+ }
140
+ /** Parse `lsof -Fpn` output for listening sockets. */
141
+ export function parseLsofListen(output) {
142
+ const sockets = [];
143
+ const seen = new Set();
144
+ let pid = 0;
145
+ for (const line of output.split("\n")) {
146
+ if (line.startsWith("p"))
147
+ pid = Number(line.slice(1));
148
+ else if (line.startsWith("n") && pid > 0) {
149
+ const match = /^(.*):(\d+)$/.exec(line.slice(1));
150
+ if (!match?.[2])
151
+ continue;
152
+ const port = Number(match[2]);
153
+ const key = `${pid}:${port}`;
154
+ if (seen.has(key))
155
+ continue;
156
+ seen.add(key);
157
+ sockets.push({ pid, port, address: (match[1] ?? "").replace(/^\[|\]$/g, "") });
158
+ }
159
+ }
160
+ return sockets.sort((a, b) => a.port - b.port);
161
+ }
162
+ /**
163
+ * Count established server-side connections per local port. A browser tab
164
+ * holding a live-reload socket or keep-alive connection counts as activity.
165
+ */
166
+ export function establishedConnectionCounts(ports) {
167
+ const counts = new Map();
168
+ const wanted = new Set(ports.filter((port) => Number.isInteger(port) && port > 0));
169
+ if (wanted.size === 0)
170
+ return counts;
171
+ const support = scanSupport();
172
+ if (support === "procfs") {
173
+ for (const row of readProcSockets()) {
174
+ // 01 ESTABLISHED; 03 SYN_RECV, where a deferred-accept server parks a
175
+ // connected client until its first bytes arrive.
176
+ if ((row.state !== "01" && row.state !== "03") || !wanted.has(row.localPort))
177
+ continue;
178
+ counts.set(row.localPort, (counts.get(row.localPort) ?? 0) + 1);
179
+ }
180
+ return counts;
181
+ }
182
+ if (support === "lsof")
183
+ return lsofConnectionCounts(wanted);
184
+ return counts;
185
+ }
186
+ /** Listening sockets via `lsof` (the macOS path; also runs wherever lsof exists). */
187
+ export function lsofListeningSockets() {
188
+ return parseLsofListen(run("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-Fpn"]));
189
+ }
190
+ /** Server-side established connections per wanted local port, via `lsof`. */
191
+ export function lsofConnectionCounts(wanted) {
192
+ const counts = new Map();
193
+ const output = run("lsof", ["-nP", "-iTCP", "-sTCP:ESTABLISHED", "-Fn"]);
194
+ for (const line of output.split("\n")) {
195
+ const match = /^n.*?:(\d+)->/.exec(line);
196
+ const port = match?.[1] ? Number(match[1]) : 0;
197
+ if (wanted.has(port))
198
+ counts.set(port, (counts.get(port) ?? 0) + 1);
199
+ }
200
+ return counts;
201
+ }
202
+ /** Parent, command line, and working directory via `ps` and `lsof` (the macOS path). */
203
+ export function psProcessInfo(pid) {
204
+ const ps = run("ps", ["-o", "ppid=,command=", "-p", String(pid)]).trim();
205
+ if (!ps)
206
+ return null;
207
+ const match = /^(\d+)\s+(.*)$/.exec(ps);
208
+ const cwdLine = run("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"])
209
+ .split("\n")
210
+ .find((line) => line.startsWith("n"));
211
+ return {
212
+ pid,
213
+ ppid: match?.[1] ? Number(match[1]) : null,
214
+ command: match?.[2] ?? ps,
215
+ cwd: cwdLine ? cwdLine.slice(1) : null,
216
+ };
217
+ }
218
+ export function readProcessInfo(pid) {
219
+ if (process.platform === "linux") {
220
+ try {
221
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
222
+ const afterComm = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
223
+ const ppid = Number(afterComm[1]);
224
+ const command = readFileSync(`/proc/${pid}/cmdline`, "utf8").split("\0").join(" ").trim();
225
+ let cwd = null;
226
+ try {
227
+ cwd = readlinkSync(`/proc/${pid}/cwd`);
228
+ }
229
+ catch {
230
+ cwd = null;
231
+ }
232
+ return { pid, ppid: Number.isInteger(ppid) ? ppid : null, command, cwd };
233
+ }
234
+ catch {
235
+ return null;
236
+ }
237
+ }
238
+ if (process.platform === "darwin")
239
+ return psProcessInfo(pid);
240
+ return null;
241
+ }
242
+ /** Parse `ps -o etime=` output (`[[dd-]hh:]mm:ss`) into seconds. */
243
+ export function parseElapsed(value) {
244
+ const match = /^\s*(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)\s*$/.exec(value);
245
+ if (!match)
246
+ return null;
247
+ const [, days, hours, minutes, seconds] = match;
248
+ return (Number(days ?? 0) * 86_400 + Number(hours ?? 0) * 3_600 + Number(minutes) * 60 + Number(seconds));
249
+ }
250
+ /** When a process started, from its elapsed run time; null when unavailable. */
251
+ export function processStartedAt(pid, now = Date.now()) {
252
+ if (process.platform !== "linux" && process.platform !== "darwin")
253
+ return null;
254
+ const elapsed = parseElapsed(run("ps", ["-o", "etime=", "-p", String(pid)]));
255
+ return elapsed === null ? null : new Date(now - elapsed * 1_000).toISOString();
256
+ }
257
+ /** The pid followed by its ancestors, nearest first (bounded). */
258
+ export function ancestry(pid, maxHops = 32) {
259
+ const chain = [];
260
+ let current = pid;
261
+ while (current && current > 1 && chain.length < maxHops && !chain.includes(current)) {
262
+ chain.push(current);
263
+ current = readProcessInfo(current)?.ppid ?? null;
264
+ }
265
+ return chain;
266
+ }
267
+ function run(command, args) {
268
+ const result = spawnSync(command, args, { encoding: "utf8", timeout: 10_000 });
269
+ return result.status === 0 || result.stdout ? (result.stdout ?? "") : "";
270
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tunnels as registry services.
3
+ *
4
+ * `tunnel up` and `tunnel reload` register the instance; `tunnel down`
5
+ * removes it. Tunnels started before the registry existed are adopted the next
6
+ * time the registry is read, so a long-lived tunnel never shows as an
7
+ * unregistered listener.
8
+ */
9
+ import { type TunnelState } from "../../lib/tunnel/state.js";
10
+ import type { RegisterServerInput } from "./index.js";
11
+ export declare function tunnelServerId(name: string): string;
12
+ /** The registry input for one tunnel instance. */
13
+ export declare function tunnelServerInput(state: TunnelState, coordRoot: string): RegisterServerInput;
14
+ //# sourceMappingURL=tunnels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnels.d.ts","sourceRoot":"","sources":["../../../src/core/servers/tunnels.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,kDAAkD;AAClD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAmB5F"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Tunnels as registry services.
3
+ *
4
+ * `tunnel up` and `tunnel reload` register the instance; `tunnel down`
5
+ * removes it. Tunnels started before the registry existed are adopted the next
6
+ * time the registry is read, so a long-lived tunnel never shows as an
7
+ * unregistered listener.
8
+ */
9
+ import { resolve } from "node:path";
10
+ import { gateLogFile } from "../../lib/tunnel/state.js";
11
+ import { resolveBinName } from "../config.js";
12
+ export function tunnelServerId(name) {
13
+ return `tunnel-${name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-")}`;
14
+ }
15
+ /** The registry input for one tunnel instance. */
16
+ export function tunnelServerInput(state, coordRoot) {
17
+ const helpers = [state.cloudflared_pid, state.provider_pid].filter((pid) => typeof pid === "number" && pid > 0);
18
+ return {
19
+ id: tunnelServerId(state.name),
20
+ kind: "service",
21
+ type: "tunnel",
22
+ label: `Tunnel ${state.name} → ${state.target}${state.vhost ? ` (${state.vhost})` : ""}`,
23
+ url: state.url,
24
+ port: state.gate_port,
25
+ pid: state.gate_pid,
26
+ ...(helpers.length ? { pids: helpers } : {}),
27
+ log: resolve(coordRoot, ".cache", "tunnel", gateLogFile(state.name)),
28
+ cwd: coordRoot,
29
+ stop_argv: [resolveBinName(coordRoot), "tunnel", "down", "--name", state.name],
30
+ started_at: state.started_at,
31
+ owner: null,
32
+ };
33
+ }
@@ -369,6 +369,7 @@ function cacheFamilies() {
369
369
  ["pid-map-cache", ".harnery/pid-map", "active native process observations"],
370
370
  ["remote-presence-cache", ".harnery/presence/remote", "presence relay observations"],
371
371
  ["event-v3-live-display", ".harnery/live/v3", "Event Ledger V3 canonical events"],
372
+ ["local-server-registry", ".harnery/servers", "running local server processes"],
372
373
  ];
373
374
  return [
374
375
  ...definitions.map(([id, root, source]) => family({
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAgDH,OAAO,KAAK,EAIV,UAAU,EAEV,SAAS,EAeV,MAAM,YAAY,CAAC;AAkDpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM/E;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;gBAEpB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM;CAOvF;AAED;;;;6EAI6E;AAC7E,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAEjB,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;CAUhD;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA6E1F"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAiDH,OAAO,KAAK,EAIV,UAAU,EAGV,SAAS,EAeV,MAAM,YAAY,CAAC;AAuDpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM/E;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;gBAEpB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM;CAOvF;AAED;;;;6EAI6E;AAC7E,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAEjB,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;CAUhD;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA6E1F"}
@@ -28,6 +28,7 @@ import { createHash, randomBytes } from "node:crypto";
28
28
  import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
29
29
  import { isAbsolute, join, resolve } from "node:path";
30
30
  import { pathToFileURL } from "node:url";
31
+ import { workflowWorkerFullAccessConfig } from "../config.js";
31
32
  import { snapshotRepo } from "../context/index.js";
32
33
  import { evaluatePolicy, normalizePolicy, PolicyDeniedError, policyDigest, summarizePolicyRequest, } from "../policy/index.js";
33
34
  import { failedWorkflowDiagnosticAdmissionObservation, observeWorkflowDiagnosticAdmission, } from "./admission.js";
@@ -45,6 +46,7 @@ import { appendWorkflowTranscriptEvent } from "./transcript.js";
45
46
  import { WORKFLOW_DIAGNOSTIC_ADMISSION_SCHEMA_VERSION } from "./types.js";
46
47
  import { parseStageOutput, validateAgainstSchema } from "./validate.js";
47
48
  import { freezeWorkflowWorkContext } from "./work-context.js";
49
+ import { decideWorkerAccess, detectParentAccess, parseWorkerFullAccessPolicy, } from "./worker-access.js";
48
50
  import { attestTerminal, attestWorkspaceFailure, resolveWorkspaceBinding, } from "./workspaces/execution.js";
49
51
  import { isWorkspaceAttestation } from "./workspaces/validate.js";
50
52
  /** Proof record of the filesystem projection a run applied (ADR 0039/0040).
@@ -360,6 +362,11 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
360
362
  writableRoots: [...(requestedPolicy.writableRoots ?? []), ...gitGrantRoots],
361
363
  }
362
364
  : undefined;
365
+ // Worker full access (ADR 0192) is decided per launch. The host policy is
366
+ // read once per run; the parent probe runs at most once, and only when a
367
+ // launch has already cleared every cheaper condition.
368
+ const workerFullAccess = parseWorkerFullAccessPolicy(workflowWorkerFullAccessConfig(opts.coordRoot));
369
+ let parentAccess;
363
370
  const executionRepoBefore = resumeState?.manifest.repository_before ?? snapshotRepo(executionCwd);
364
371
  if (!resumeState) {
365
372
  writeWorkflowRunManifest({
@@ -836,6 +843,21 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
836
843
  }
837
844
  if (!spawnCountClaimed)
838
845
  agentsSpawned++;
846
+ const access = decideWorkerAccess({
847
+ adapter,
848
+ model: agentOpts.model,
849
+ effort: agentOpts.effort,
850
+ policy: workerFullAccess,
851
+ filesystemPolicy,
852
+ parent: () => {
853
+ if (!parentAccess) {
854
+ parentAccess = (opts.detectParentAccess ?? detectParentAccess)();
855
+ transcript("worker_access.parent", { ...parentAccess });
856
+ }
857
+ return parentAccess;
858
+ },
859
+ });
860
+ agentProof.access = access;
839
861
  await acquire();
840
862
  try {
841
863
  await ensureDiagnosticAdmissionObserved();
@@ -847,7 +869,11 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
847
869
  specialist: agentOpts.specialist ?? null,
848
870
  model: agentOpts.model ?? null,
849
871
  effort: agentOpts.effort ?? null,
872
+ access,
850
873
  });
874
+ if (access.mode === "full-access") {
875
+ log(`[access] ${id} ${adapter}: full access (${access.detail})`);
876
+ }
851
877
  log(`[${name}] ${currentStage || "(no stage)"} → ${id} [${adapter}] ${label}`);
852
878
  // Headless children do not reliably fire adapter hooks. The engine owns
853
879
  // their canonical V3 start so every dispatched child has an auditable
@@ -875,7 +901,10 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
875
901
  runId,
876
902
  agentId: id,
877
903
  subscriptionOnly,
878
- filesystemPolicy,
904
+ // A full-access launch has no sandbox to project a policy into.
905
+ ...(access.mode === "full-access"
906
+ ? { access: "full-access" }
907
+ : { filesystemPolicy }),
879
908
  });
880
909
  agentProof.attempts = attempt;
881
910
  agentProof.duration_ms += last.durationMs;
@@ -14,11 +14,11 @@
14
14
  * not, which is worse than refusing and worse than never offering the feature.
15
15
  */
16
16
  import type { AdapterSandboxProjection } from "../adapters/types.js";
17
- import type { GitAdministrativeGrant, SpawnFilesystemPolicy } from "./types.js";
17
+ import type { GitAdministrativeGrant, SpawnFilesystemPolicy, SpawnRequest } from "./types.js";
18
18
  import type { WorkspaceBinding } from "./workspaces/types.js";
19
19
  export declare class SandboxProjectionError extends Error {
20
20
  readonly adapter: string;
21
- readonly reason: "mode_unrepresentable" | "writable_roots_unrepresentable" | "no_projection" | "writable_root_escapes_workspace" | "git_grant_unavailable";
21
+ readonly reason: "mode_unrepresentable" | "writable_roots_unrepresentable" | "no_projection" | "writable_root_escapes_workspace" | "git_grant_unavailable" | "full_access_unrepresentable" | "full_access_conflicts_with_policy";
22
22
  constructor(adapter: string, reason: SandboxProjectionError["reason"], message: string);
23
23
  }
24
24
  export interface ResolvedSandboxProjection {
@@ -54,4 +54,15 @@ export declare function assertProjectionWithinWorkspace(adapter: string, allowed
54
54
  * grant returns both rather than pretending the private half is useful alone.
55
55
  */
56
56
  export declare function resolveGitGrantRoots(grant: GitAdministrativeGrant, binding: WorkspaceBinding | undefined): readonly string[];
57
+ /**
58
+ * The vendor arguments for an unsandboxed launch, or undefined for an ordinary
59
+ * sandboxed one (ADR 0192).
60
+ *
61
+ * The engine decides whether a worker qualifies; this only renders the result,
62
+ * and refuses rather than degrades. An adapter with no declared full-access
63
+ * rendering cannot honor the request, and a request that also carries a
64
+ * filesystem policy asks for two contradictory things, so both fail before
65
+ * launch instead of silently picking one.
66
+ */
67
+ export declare function resolveFullAccessArgv(adapter: string, req: Pick<SpawnRequest, "access" | "filesystemPolicy">): readonly string[] | undefined;
57
68
  //# sourceMappingURL=sandbox-projection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox-projection.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/sandbox-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EACX,sBAAsB,GACtB,gCAAgC,GAChC,eAAe,GACf,iCAAiC,GACjC,uBAAuB,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,MAAM;CAMvF;AAED,MAAM,WAAW,yBAAyB;IACxC,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,wBAAwB,GAAG,SAAS,EACjD,MAAM,EAAE,qBAAqB,GAC5B,yBAAyB,CAoC3B;AASD;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,MAAM,EAC3B,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,IAAI,CAUN;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,sBAAsB,EAC7B,OAAO,EAAE,gBAAgB,GAAG,SAAS,GACpC,SAAS,MAAM,EAAE,CAanB"}
1
+ {"version":3,"file":"sandbox-projection.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/sandbox-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EACX,sBAAsB,GACtB,gCAAgC,GAChC,eAAe,GACf,iCAAiC,GACjC,uBAAuB,GACvB,6BAA6B,GAC7B,mCAAmC,CAAC;gBAE5B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,MAAM;CAMvF;AAED,MAAM,WAAW,yBAAyB;IACxC,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,wBAAwB,GAAG,SAAS,EACjD,MAAM,EAAE,qBAAqB,GAC5B,yBAAyB,CAoC3B;AASD;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,MAAM,EAC3B,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,IAAI,CAUN;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,sBAAsB,EAC7B,OAAO,EAAE,gBAAgB,GAAG,SAAS,GACpC,SAAS,MAAM,EAAE,CAanB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,kBAAkB,CAAC,GACrD,SAAS,MAAM,EAAE,GAAG,SAAS,CAkB/B"}
@@ -13,6 +13,7 @@
13
13
  * dropped would leave an operator believing a child was constrained when it was
14
14
  * not, which is worse than refusing and worse than never offering the feature.
15
15
  */
16
+ import { builtinAdapterProfile } from "../adapters/profiles.js";
16
17
  export class SandboxProjectionError extends Error {
17
18
  adapter;
18
19
  reason;
@@ -93,3 +94,25 @@ export function resolveGitGrantRoots(grant, binding) {
93
94
  // repeated writable root is noise in the rendered argv and in proof.
94
95
  return [...new Set([repository.gitdir.realpath, repository.common_dir.realpath])];
95
96
  }
97
+ /**
98
+ * The vendor arguments for an unsandboxed launch, or undefined for an ordinary
99
+ * sandboxed one (ADR 0192).
100
+ *
101
+ * The engine decides whether a worker qualifies; this only renders the result,
102
+ * and refuses rather than degrades. An adapter with no declared full-access
103
+ * rendering cannot honor the request, and a request that also carries a
104
+ * filesystem policy asks for two contradictory things, so both fail before
105
+ * launch instead of silently picking one.
106
+ */
107
+ export function resolveFullAccessArgv(adapter, req) {
108
+ if (req.access !== "full-access")
109
+ return undefined;
110
+ if (req.filesystemPolicy) {
111
+ throw new SandboxProjectionError(adapter, "full_access_conflicts_with_policy", `${adapter} was asked for full access and a "${req.filesystemPolicy.mode}" filesystem policy at once; a launch gets one or the other`);
112
+ }
113
+ const rendering = builtinAdapterProfile(adapter)?.fullAccess;
114
+ if (!rendering) {
115
+ throw new SandboxProjectionError(adapter, "full_access_unrepresentable", `${adapter} declares no full-access mode, so a worker on it cannot run without its sandbox`);
116
+ }
117
+ return rendering.argv;
118
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKhF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAYrE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAsB1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA2ExE;AAED,eAAO,MAAM,iBAAiB,EAAE,OAiB/B,CAAC"}
1
+ {"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKhF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAYrE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CA0B1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA2ExE;AAED,eAAO,MAAM,iBAAiB,EAAE,OAiB/B,CAAC"}
@@ -21,7 +21,7 @@ import { exec } from "../../lib/exec.js";
21
21
  import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profiles.js";
22
22
  import { notFoundError } from "./adapters.js";
23
23
  import { buildChildEnv } from "./child-env.js";
24
- import { resolveSandboxProjection } from "./sandbox-projection.js";
24
+ import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.js";
25
25
  import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.js";
26
26
  export function buildClaudeInvocation(req) {
27
27
  validateAdapterEffort("claude-code", req.effort);
@@ -38,6 +38,11 @@ export function buildClaudeInvocation(req) {
38
38
  "--max-turns",
39
39
  String(req.maxTurns),
40
40
  ];
41
+ // A qualified launch removes the permission prompt and the Bash sandbox
42
+ // (ADR 0192); an ordinary one leaves Claude Code's own settings in charge.
43
+ const fullAccess = resolveFullAccessArgv("claude-code", req);
44
+ if (fullAccess)
45
+ argv.push(...fullAccess);
41
46
  if (req.model)
42
47
  argv.push("--model", req.model);
43
48
  if (req.effort)
@@ -10,6 +10,8 @@
10
10
  * experimental `--json` JSONL event stream.
11
11
  * - `--skip-git-repo-check` keeps non-repo cwds working; `--sandbox
12
12
  * workspace-write` matches workflow-stage expectations (children may edit).
13
+ * A launch the engine qualified for full access renders `--sandbox
14
+ * danger-full-access` instead (ADR 0192).
13
15
  * - No per-run cost or session-id surface in this mode → both left undefined.
14
16
  * - No max-turns equivalent → `maxTurns` is accepted and ignored (documented
15
17
  * in the CLI docs page).
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-codex.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKhF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,iBAAiB,CA8B9F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAwCvE;AAED,eAAO,MAAM,YAAY,EAAE,OA8B1B,CAAC"}
1
+ {"version":3,"file":"spawn-codex.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKhF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,iBAAiB,CA6C9F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAwCvE;AAED,eAAO,MAAM,YAAY,EAAE,OA8B1B,CAAC"}
@@ -10,6 +10,8 @@
10
10
  * experimental `--json` JSONL event stream.
11
11
  * - `--skip-git-repo-check` keeps non-repo cwds working; `--sandbox
12
12
  * workspace-write` matches workflow-stage expectations (children may edit).
13
+ * A launch the engine qualified for full access renders `--sandbox
14
+ * danger-full-access` instead (ADR 0192).
13
15
  * - No per-run cost or session-id surface in this mode → both left undefined.
14
16
  * - No max-turns equivalent → `maxTurns` is accepted and ignored (documented
15
17
  * in the CLI docs page).
@@ -22,12 +24,29 @@ import { exec } from "../../lib/exec.js";
22
24
  import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profiles.js";
23
25
  import { notFoundError } from "./adapters.js";
24
26
  import { buildChildEnv } from "./child-env.js";
25
- import { resolveSandboxProjection } from "./sandbox-projection.js";
27
+ import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.js";
26
28
  import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.js";
27
29
  export function buildCodexInvocation(req, resultFile) {
28
30
  validateAdapterEffort("codex", req.effort);
29
31
  if (!resultFile)
30
32
  throw new Error("codex adapter requires a final-message result file");
33
+ const fullAccess = resolveFullAccessArgv("codex", req);
34
+ if (fullAccess) {
35
+ const argv = [
36
+ "codex",
37
+ "exec",
38
+ req.prompt,
39
+ "--output-last-message",
40
+ resultFile,
41
+ "--skip-git-repo-check",
42
+ ...fullAccess,
43
+ ];
44
+ if (req.model)
45
+ argv.push("--model", req.model);
46
+ if (req.effort)
47
+ argv.push("-c", `model_reasoning_effort=${JSON.stringify(req.effort)}`);
48
+ return { argv, resultFile };
49
+ }
31
50
  // Default stays workspace-write so an unprojected request is unchanged.
32
51
  const projection = req.filesystemPolicy
33
52
  ? resolveSandboxProjection("codex", builtinAdapterProfile("codex")?.sandboxProjection, req.filesystemPolicy)
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKhF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASrE,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAWA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAkB1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAqDxE;AAED,eAAO,MAAM,aAAa,EAAE,OAkB3B,CAAC"}
1
+ {"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKhF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASrE,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAWA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAsB1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAqDxE;AAED,eAAO,MAAM,aAAa,EAAE,OAkB3B,CAAC"}
@@ -22,7 +22,7 @@ import { exec } from "../../lib/exec.js";
22
22
  import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profiles.js";
23
23
  import { notFoundError } from "./adapters.js";
24
24
  import { buildChildEnv } from "./child-env.js";
25
- import { resolveSandboxProjection } from "./sandbox-projection.js";
25
+ import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.js";
26
26
  import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.js";
27
27
  /** Exported for unit tests (no live binary to test against). */
28
28
  export function parseCursorOutput(stdout) {
@@ -50,6 +50,11 @@ export function buildCursorInvocation(req) {
50
50
  // commands. Neither flag maps host policy into child tools; that capability
51
51
  // remains explicitly unsupported.
52
52
  const argv = ["cursor-agent", "-p", req.prompt, "--output-format", "json", "--trust", "--force"];
53
+ // A qualified launch also turns the Cursor sandbox off (ADR 0192); an
54
+ // ordinary one leaves the configured sandbox setting untouched.
55
+ const fullAccess = resolveFullAccessArgv("cursor", req);
56
+ if (fullAccess)
57
+ argv.push(...fullAccess);
53
58
  if (req.model)
54
59
  argv.push("--model", req.model);
55
60
  return { argv };
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-opencode.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-opencode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKhF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQrE;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAoBA;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAiB5E;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA2C1E;AAED,eAAO,MAAM,eAAe,EAAE,OAkB7B,CAAC"}
1
+ {"version":3,"file":"spawn-opencode.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-opencode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKhF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQrE;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAoBA;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAoB5E;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA2C1E;AAED,eAAO,MAAM,eAAe,EAAE,OAkB7B,CAAC"}
@@ -20,7 +20,7 @@ import { exec } from "../../lib/exec.js";
20
20
  import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profiles.js";
21
21
  import { notFoundError } from "./adapters.js";
22
22
  import { buildChildEnv } from "./child-env.js";
23
- import { resolveSandboxProjection } from "./sandbox-projection.js";
23
+ import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.js";
24
24
  import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.js";
25
25
  /**
26
26
  * Parse the NDJSON `opencode run --format json` stream into the final answer
@@ -59,6 +59,9 @@ export function buildOpenCodeInvocation(req) {
59
59
  // Declared unrepresentable: refuse rather than drop it silently (ADR 0039).
60
60
  resolveSandboxProjection("opencode", builtinAdapterProfile("opencode")?.sandboxProjection, req.filesystemPolicy);
61
61
  }
62
+ // OpenCode declares no full-access rendering, so this refuses a full-access
63
+ // request instead of launching it as an ordinary one (ADR 0192).
64
+ resolveFullAccessArgv("opencode", req);
62
65
  // `--auto` auto-approves permissions that are not explicitly denied, which a
63
66
  // headless workflow child needs because it has no interactive approval
64
67
  // channel; it does not map host policy into child tools (unsupported).