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,394 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { hostname } from "node:os";
3
+ import { resolveCoordRoot } from "../core/agents/coord-client.js";
4
+ import { resolveBinName, serverGcConfig } from "../core/config.js";
5
+ import { adoptServer, allocateServerPort, isValidServerId, listServers, planServerGc, readServer, registerServer, SERVER_DEFAULT_IDLE_HOURS, SERVER_DEFAULT_OWNER_STALE_HOURS, stopServer, touchServer, unregisterServer, } from "../core/servers/index.js";
6
+ export function registerServersCommand(program, emit, context) {
7
+ const root = () => {
8
+ const resolved = context?.resolveCoordRoot?.() ?? context?.repoRoot ?? resolveCoordRoot();
9
+ if (!resolved)
10
+ throw new Error("not inside a Harnery project");
11
+ return resolved;
12
+ };
13
+ const fail = (error, code = "servers_error") => {
14
+ emit.error({ code, message: error instanceof Error ? error.message : String(error) });
15
+ emit.setExitCode(1);
16
+ };
17
+ const cmd = program
18
+ .command("servers")
19
+ .description("Every local server started in this project: list, stop, clean up idle session servers, read logs");
20
+ cmd
21
+ .command("list", { isDefault: true })
22
+ .alias("status")
23
+ .description("List registered servers and any unregistered listener running inside the project")
24
+ .option("--no-scan", "Skip the scan for unregistered listeners")
25
+ .option("--json", "Emit the versioned report")
26
+ .action((options) => {
27
+ try {
28
+ const report = listServers({ coordRoot: root(), scan: options.scan !== false });
29
+ if (options.json) {
30
+ emit.config({ format: "json" });
31
+ emit.data(report);
32
+ }
33
+ else
34
+ emit.text(renderReport(report, resolveBinName(report.coord_root)));
35
+ }
36
+ catch (error) {
37
+ fail(error);
38
+ }
39
+ });
40
+ cmd
41
+ .command("stop [ids...]")
42
+ .description("Stop named servers now; --type selects several and previews unless --yes")
43
+ .option("--type <type>", "Select every running server of this type")
44
+ .option("--pid <pid>", "Stop an unregistered listener found by the list scan (repeatable)", (value, previous = []) => [...previous, value])
45
+ .option("--kind <kind>", "With --type, limit to session or service")
46
+ .option("--yes", "Apply a --type selection")
47
+ .option("--json", "Emit JSON")
48
+ .action(async (ids, options) => {
49
+ try {
50
+ const coordRoot = root();
51
+ const pids = (options.pid ?? []).map((value) => integer(value, "--pid"));
52
+ if (!ids.length && !options.type && !pids.length)
53
+ throw new Error("name server ids, or pass --type or --pid");
54
+ const report = listServers({ coordRoot, scan: pids.length > 0 });
55
+ const strays = pids.map((pid) => {
56
+ const found = report.unregistered.find((item) => item.pid === pid);
57
+ if (!found)
58
+ throw new Error(`pid ${pid} is not an unregistered listener inside this project; only pids shown by "servers list" can be stopped this way`);
59
+ return found;
60
+ });
61
+ const byId = new Map(report.servers.map((view) => [view.record.id, view]));
62
+ const missing = ids.filter((id) => !byId.has(id));
63
+ if (missing.length)
64
+ throw new Error(`no running server named: ${missing.join(", ")}`);
65
+ const selected = [
66
+ ...ids.map((id) => byId.get(id)),
67
+ ...(options.type
68
+ ? report.servers.filter((view) => view.record.type === options.type &&
69
+ view.state === "running" &&
70
+ (!options.kind || view.record.kind === options.kind) &&
71
+ !ids.includes(view.record.id))
72
+ : []),
73
+ ];
74
+ const preview = Boolean(options.type) && !options.yes;
75
+ const results = [];
76
+ for (const stray of strays) {
77
+ results.push(await stopServer({
78
+ schema_version: 1,
79
+ id: `pid-${stray.pid}`,
80
+ kind: "session",
81
+ type: "unregistered",
82
+ label: stray.command,
83
+ pid: stray.pid,
84
+ host: hostname(),
85
+ started_at: new Date().toISOString(),
86
+ registered_at: new Date().toISOString(),
87
+ }, { coordRoot }));
88
+ }
89
+ for (const view of selected) {
90
+ if (preview && !ids.includes(view.record.id))
91
+ results.push({ id: view.record.id, would_stop: true });
92
+ else
93
+ results.push(await stopServer(view.record, { coordRoot }));
94
+ }
95
+ if (options.json) {
96
+ emit.config({ format: "json" });
97
+ emit.data({ applied: !preview, results });
98
+ }
99
+ else {
100
+ emit.text(results.length
101
+ ? results
102
+ .map((r) => "would_stop" in r
103
+ ? `would stop ${r.id}`
104
+ : `${r.stopped ? "stopped" : r.method === "not-running" ? "already stopped" : "could not stop"} ${r.id}${r.detail ? ` (${r.detail})` : ""}`)
105
+ .join("\n") +
106
+ (preview ? `\nPreview only. Re-run with --yes to stop these.` : "")
107
+ : "No matching running servers.");
108
+ }
109
+ if (results.some((r) => "stopped" in r && !r.stopped && r.method === "refused"))
110
+ emit.setExitCode(1);
111
+ }
112
+ catch (error) {
113
+ fail(error);
114
+ }
115
+ });
116
+ cmd
117
+ .command("gc")
118
+ .description(`Stop idle session servers whose starting agent ended (preview unless --yes); services are never touched`)
119
+ .option("--idle-hours <hours>", `Idle window before a server may stop (default ${SERVER_DEFAULT_IDLE_HOURS})`)
120
+ .option("--owner-stale-hours <hours>", `Treat a starting agent unobserved this long as gone (default ${SERVER_DEFAULT_OWNER_STALE_HOURS})`)
121
+ .option("--yes", "Stop the selected servers")
122
+ .option("--json", "Emit JSON")
123
+ .action(async (options) => {
124
+ try {
125
+ const coordRoot = root();
126
+ const configured = serverGcConfig(coordRoot);
127
+ const idleHours = positive(options.idleHours, configured.idle_hours ?? SERVER_DEFAULT_IDLE_HOURS, "--idle-hours");
128
+ const ownerStaleHours = positive(options.ownerStaleHours, configured.owner_stale_hours ?? SERVER_DEFAULT_OWNER_STALE_HOURS, "--owner-stale-hours");
129
+ const report = listServers({ coordRoot, scan: false, ownerStaleHours });
130
+ const decisions = planServerGc(report.servers, { idleHours, ownerStaleHours });
131
+ const stops = decisions.filter((d) => d.action === "stop");
132
+ const results = [];
133
+ if (options.yes) {
134
+ for (const decision of stops) {
135
+ const view = report.servers.find((v) => v.record.id === decision.id);
136
+ results.push(await stopServer(view.record, { coordRoot }));
137
+ }
138
+ }
139
+ if (options.json) {
140
+ emit.config({ format: "json" });
141
+ emit.data({ applied: Boolean(options.yes), pruned: report.pruned, decisions, results });
142
+ }
143
+ else {
144
+ const lines = decisions.map((d) => `${d.action === "stop" ? (options.yes ? "stopped" : "would stop") : "keep"} ${d.id} ${d.reason}`);
145
+ if (report.pruned.length)
146
+ lines.push(`Removed records of exited servers: ${report.pruned.join(", ")}`);
147
+ if (!decisions.length)
148
+ lines.push("No registered servers.");
149
+ if (stops.length && !options.yes)
150
+ lines.push("Preview only. Re-run with --yes to stop.");
151
+ emit.text(lines.join("\n"));
152
+ }
153
+ }
154
+ catch (error) {
155
+ fail(error);
156
+ }
157
+ });
158
+ cmd
159
+ .command("adopt")
160
+ .description("Register running servers that never registered (only pids the list reports as unregistered)")
161
+ .option("--pid <pid>", "Adopt this unregistered listener (repeatable)", (value, previous = []) => [...previous, value])
162
+ .option("--all", "Adopt every unregistered listener the scan reports")
163
+ .option("--type <type>", "Server family to record", "adopted")
164
+ .option("--kind <kind>", "session (cleaned up when idle) or service", "session")
165
+ .option("--label <label>", "Human-readable name (single --pid only)")
166
+ .option("--scope <path>", "What the server serves (default: its working directory)")
167
+ .option("--no-owner", "Record no owner, so gc never stops it")
168
+ .option("--json", "Emit JSON")
169
+ .action((options) => {
170
+ try {
171
+ const coordRoot = root();
172
+ if (options.kind !== "session" && options.kind !== "service")
173
+ throw new Error("--kind must be session or service");
174
+ const pids = options.all
175
+ ? [...new Set(listServers({ coordRoot, sample: false }).unregistered.map((u) => u.pid))]
176
+ : (options.pid ?? []).map((value) => integer(value, "--pid"));
177
+ if (!pids.length)
178
+ throw new Error(options.all ? "no unregistered listeners" : "pass --pid or --all");
179
+ if (options.label && pids.length > 1)
180
+ throw new Error("--label needs a single --pid");
181
+ const records = pids.map((pid) => adoptServer(pid, {
182
+ coordRoot,
183
+ type: options.type,
184
+ kind: options.kind,
185
+ ...(options.label ? { label: options.label } : {}),
186
+ ...(options.scope ? { scope: options.scope } : {}),
187
+ owner: options.owner === false ? "none" : "self",
188
+ }));
189
+ if (options.json) {
190
+ emit.config({ format: "json" });
191
+ emit.data({ adopted: records });
192
+ }
193
+ else
194
+ emit.text(records.map((r) => `adopted ${r.id} ${r.url ?? ""}`).join("\n"));
195
+ }
196
+ catch (error) {
197
+ fail(error);
198
+ }
199
+ });
200
+ cmd
201
+ .command("port <type>")
202
+ .description("Print a free port from the type's configured range (.harnery/config.jsonc servers.port_ranges)")
203
+ .action((type) => {
204
+ try {
205
+ const port = allocateServerPort(type, { coordRoot: root() });
206
+ if (port === null)
207
+ throw new Error(`no port range configured for ${type}`);
208
+ emit.text(String(port));
209
+ }
210
+ catch (error) {
211
+ fail(error);
212
+ }
213
+ });
214
+ cmd
215
+ .command("logs <id>")
216
+ .description("Print the end of a server's log file")
217
+ .option("-n, --lines <n>", "Lines to print", "60")
218
+ .action((id, options) => {
219
+ try {
220
+ const record = readServer(id, { coordRoot: root() });
221
+ if (!record)
222
+ throw new Error(`no server named ${id}`);
223
+ if (!record.log || !existsSync(record.log))
224
+ throw new Error(`${id} did not record a log file`);
225
+ const lines = positive(options.lines, 60, "--lines");
226
+ const size = statSync(record.log).size;
227
+ const content = readFileSync(record.log, "utf8").slice(Math.max(0, size - 512 * 1024));
228
+ emit.text(content
229
+ .split("\n")
230
+ .slice(-Math.floor(lines) - 1)
231
+ .join("\n"));
232
+ }
233
+ catch (error) {
234
+ fail(error);
235
+ }
236
+ });
237
+ cmd
238
+ .command("open <id>")
239
+ .description("Print a server's URL")
240
+ .action((id) => {
241
+ try {
242
+ const record = readServer(id, { coordRoot: root() });
243
+ if (!record)
244
+ throw new Error(`no server named ${id}`);
245
+ if (!record.url)
246
+ throw new Error(`${id} did not record a URL`);
247
+ emit.text(record.url);
248
+ }
249
+ catch (error) {
250
+ fail(error);
251
+ }
252
+ });
253
+ cmd
254
+ .command("register")
255
+ .description("Record a server started by a script or another language (prints the record; the calling agent is recorded as owner)")
256
+ .requiredOption("--type <type>", "Server family, for example preview or review")
257
+ .option("--kind <kind>", "session (per piece of work) or service (long-running)", "session")
258
+ .option("--pid <pid>", "Process that owns the server's lifetime (default: this command's parent)")
259
+ .option("--port <port>", "Listening port")
260
+ .option("--id <id>", "Stable id (default derived from type and scope)")
261
+ .option("--url <url>", "Local URL")
262
+ .option("--scope <path>", "What the server serves, usually a directory")
263
+ .option("--label <label>", "Human-readable name")
264
+ .option("--log <path>", "Log file")
265
+ .option("--cwd <path>", "Working directory")
266
+ .action((options) => {
267
+ try {
268
+ if (options.kind !== "session" && options.kind !== "service")
269
+ throw new Error("--kind must be session or service");
270
+ if (options.id && !isValidServerId(options.id))
271
+ throw new Error(`invalid id: ${options.id}`);
272
+ const record = registerServer({
273
+ kind: options.kind,
274
+ type: options.type,
275
+ pid: options.pid ? integer(options.pid, "--pid") : process.ppid,
276
+ ...(options.port ? { port: integer(options.port, "--port") } : {}),
277
+ ...(options.id ? { id: options.id } : {}),
278
+ ...(options.url ? { url: options.url } : {}),
279
+ ...(options.scope ? { scope: options.scope } : {}),
280
+ ...(options.label ? { label: options.label } : {}),
281
+ ...(options.log ? { log: options.log } : {}),
282
+ ...(options.cwd ? { cwd: options.cwd } : {}),
283
+ }, { coordRoot: root() });
284
+ emit.config({ format: "json" });
285
+ emit.data(record);
286
+ }
287
+ catch (error) {
288
+ fail(error);
289
+ }
290
+ });
291
+ cmd
292
+ .command("unregister <id>")
293
+ .description("Remove a server's record (with --pid, only if the record still names that pid)")
294
+ .option("--pid <pid>", "Only remove when the record names this pid")
295
+ .action((id, options) => {
296
+ try {
297
+ const removed = unregisterServer(id, {
298
+ coordRoot: root(),
299
+ ...(options.pid ? { pid: integer(options.pid, "--pid") } : {}),
300
+ });
301
+ emit.config({ format: "json" });
302
+ emit.data({ id, removed });
303
+ }
304
+ catch (error) {
305
+ fail(error);
306
+ }
307
+ });
308
+ cmd
309
+ .command("touch <id>")
310
+ .description("Mark a server as used now, postponing idle cleanup")
311
+ .action((id) => {
312
+ try {
313
+ touchServer(id, { coordRoot: root() });
314
+ }
315
+ catch (error) {
316
+ fail(error);
317
+ }
318
+ });
319
+ }
320
+ function integer(value, flag) {
321
+ const parsed = Number(value);
322
+ if (!Number.isSafeInteger(parsed) || parsed < 1)
323
+ throw new Error(`${flag} must be a positive integer`);
324
+ return parsed;
325
+ }
326
+ function positive(value, fallback, flag) {
327
+ if (value === undefined)
328
+ return fallback;
329
+ const parsed = Number(value);
330
+ if (!Number.isFinite(parsed) || parsed <= 0)
331
+ throw new Error(`${flag} must be a positive number`);
332
+ return parsed;
333
+ }
334
+ function duration(ms) {
335
+ const minutes = Math.round(ms / 60_000);
336
+ if (minutes < 60)
337
+ return `${minutes}m`;
338
+ const hours = Math.floor(minutes / 60);
339
+ if (hours < 48)
340
+ return `${hours}h${String(minutes % 60).padStart(2, "0")}m`;
341
+ return `${Math.floor(hours / 24)}d`;
342
+ }
343
+ function ownerText(view) {
344
+ if (!view.record.owner)
345
+ return "no agent";
346
+ const name = view.record.owner.name ?? view.record.owner.instance_id.slice(0, 8);
347
+ return view.owner_state === "live" ? name : `${name} (${view.owner_state})`;
348
+ }
349
+ /** Plain-text table grouped by kind; exported for tests. */
350
+ export function renderReport(report, bin) {
351
+ const lines = [];
352
+ for (const kind of ["service", "session"]) {
353
+ const views = report.servers.filter((view) => view.record.kind === kind);
354
+ lines.push(kind === "service"
355
+ ? "Services (stopped only by hand)"
356
+ : "Session servers (cleaned up when idle and their agent is gone)");
357
+ if (!views.length) {
358
+ lines.push(" none");
359
+ continue;
360
+ }
361
+ for (const view of views) {
362
+ const { record } = view;
363
+ const state = view.state === "running" ? "" : ` [${view.state}]`;
364
+ const started = Date.parse(record.started_at);
365
+ const use = kind === "service"
366
+ ? `up ${Number.isFinite(started) ? duration(Date.now() - started) : "?"}`
367
+ : view.connections === null
368
+ ? `idle ${duration(view.idle_ms)}`
369
+ : view.connections > 0
370
+ ? `${view.connections} open connection(s)`
371
+ : `idle ${duration(view.idle_ms)}`;
372
+ lines.push(` ${record.id}${state} ${record.url ?? (record.port ? `port ${record.port}` : "")}`, ` ${record.label} · ${ownerText(view)} · ${use}`);
373
+ }
374
+ }
375
+ if (report.scan === "unsupported") {
376
+ lines.push("Unregistered listeners: not checked on this platform");
377
+ }
378
+ else if (report.unregistered.length) {
379
+ const byPid = new Map();
380
+ for (const item of report.unregistered) {
381
+ const entry = byPid.get(item.pid) ?? { ports: [], command: item.command };
382
+ entry.ports.push(item.port);
383
+ byPid.set(item.pid, entry);
384
+ }
385
+ lines.push(`Unregistered servers inside this project (${byPid.size} processes):`);
386
+ for (const [pid, entry] of byPid)
387
+ lines.push(` pid ${pid} port ${entry.ports.join(", ")} ${entry.command.slice(0, 100)}`);
388
+ lines.push(` These started without registering. Stop one with: ${bin} servers stop --pid <pid>`);
389
+ }
390
+ if (report.pruned.length)
391
+ lines.push(`Removed records of exited servers: ${report.pruned.join(", ")}`);
392
+ lines.push(`Control: ${bin} servers stop <id> · ${bin} servers gc · ${bin} servers logs <id>`);
393
+ return lines.join("\n");
394
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/commands/tunnel.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAQ1E,OAAO,EAYL,KAAK,cAAc,EACnB,KAAK,WAAW,EAGjB,MAAM,wBAAwB,CAAC;AAoDhC,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,EACxB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,IAAI,SAAgB,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAcpC;AAuoBD;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAsE7F;AA+MD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,YAAY,CAAC,EAAE,qBAAqB,GACnC,IAAI,CA2EN"}
1
+ {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/commands/tunnel.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAgB1E,OAAO,EAYL,KAAK,cAAc,EACnB,KAAK,WAAW,EAGjB,MAAM,wBAAwB,CAAC;AAuDhC,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,EACxB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,IAAI,SAAgB,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAcpC;AA2sBD;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAiF7F;AAkND,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,YAAY,CAAC,EAAE,qBAAqB,GACnC,IAAI,CAkFN"}
@@ -1,8 +1,11 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
+ import { resolveCoordRoot } from "../core/agents/coord-client.js";
4
5
  import { resolveBinName } from "../core/config.js";
6
+ import { tryRegisterServer, tunnelServerId, tunnelServerInput, unregisterServer, } from "../core/servers/index.js";
5
7
  import { processLogDestination, runRotatingProcessSync, spawnRotatingProcess, } from "../core/storage/process-log.js";
8
+ import { ALLOW_PATHS_ENV, normalizeAllowPaths } from "../lib/tunnel/path-scope.js";
6
9
  import { clearState, DEFAULT_INSTANCE, ensureCloudflared, gateLogFile, isProcessAlive, isTunnelStateLive, listStates, providerLogFile, readConfig, readState, writeConfig, writeState, } from "../lib/tunnel/state.js";
7
10
  /**
8
11
  * `tunnel`: provider-backed tunnel in front of a local upstream.
@@ -11,6 +14,8 @@ import { clearState, DEFAULT_INSTANCE, ensureCloudflared, gateLogFile, isProcess
11
14
  * upstream. Cloudflare quick tunnels add an IP allowlist at the gate via the
12
15
  * Cloudflare-set `CF-Connecting-IP` header; Tailscale Serve/Funnel exposes the
13
16
  * same gate through tailscaled, with Tailscale owning the access boundary.
17
+ * Every provider also passes through a path scope: `up` requires at least one
18
+ * `--allow-path <prefix>`, and the gate refuses any request outside it.
14
19
  *
15
20
  * State + config persisted under `.cache/tunnel/`. cloudflared auto-installs
16
21
  * to ~/.local/bin/ on first run (Linux only; macOS users `brew install`);
@@ -98,6 +103,42 @@ function resolveHttpsPort(raw) {
98
103
  }
99
104
  return port;
100
105
  }
106
+ /**
107
+ * The path scope for `up`. At least one prefix is required: a tunnel forwards
108
+ * to a whole local app, and publishing all of it has to be a stated choice
109
+ * (`--allow-path /`), never the result of leaving a flag off.
110
+ */
111
+ function resolveAllowPaths(raw) {
112
+ let paths;
113
+ try {
114
+ paths = normalizeAllowPaths(raw ?? []);
115
+ }
116
+ catch (error) {
117
+ emit.error({
118
+ code: "tunnel_path_scope_invalid",
119
+ message: error instanceof Error ? error.message : String(error),
120
+ });
121
+ process.exit(1);
122
+ }
123
+ if (paths.length === 0) {
124
+ emit.error({
125
+ code: "tunnel_path_scope_empty",
126
+ message: "No path scope; refusing to start. Name what the tunnel shares with --allow-path <prefix> " +
127
+ "(repeatable), or pass --allow-path / to publish the whole upstream.",
128
+ });
129
+ process.exit(1);
130
+ }
131
+ return paths;
132
+ }
133
+ function samePaths(a, b) {
134
+ return a.length === b.length && [...a].sort().join("\n") === [...b].sort().join("\n");
135
+ }
136
+ function formatPaths(paths) {
137
+ return paths.length === 0 ? "(none: every path refused)" : paths.join(", ");
138
+ }
139
+ function collectPath(value, previous) {
140
+ return [...previous, value];
141
+ }
101
142
  function gateScriptPath() {
102
143
  return resolve(import.meta.dirname, "..", "lib", "tunnel", "gate.ts");
103
144
  }
@@ -366,6 +407,7 @@ function spawnGate(o) {
366
407
  HARNERY_TUNNEL_TARGET: o.target,
367
408
  HARNERY_TUNNEL_VHOST: o.vhost,
368
409
  HARNERY_TUNNEL_PORT: String(o.gatePort),
410
+ [ALLOW_PATHS_ENV]: o.allowPaths.join(","),
369
411
  },
370
412
  });
371
413
  gateProc.unref();
@@ -389,6 +431,7 @@ async function up(opts) {
389
431
  const tailscaleMode = provider === "tailscale" ? resolveTailscaleMode(opts.visibility) : undefined;
390
432
  const tailscalePath = provider === "tailscale" ? resolveTailscalePath(opts.path, name) : undefined;
391
433
  const tailscaleHttpsPort = provider === "tailscale" ? resolveHttpsPort(opts.httpsPort) : undefined;
434
+ const allowPaths = resolveAllowPaths(opts.allowPath);
392
435
  const existing = readState(name);
393
436
  if (existing && tunnelIsAlive(existing)) {
394
437
  if (existing.provider !== provider) {
@@ -398,9 +441,18 @@ async function up(opts) {
398
441
  });
399
442
  process.exit(1);
400
443
  }
444
+ if (!samePaths(existing.allow_paths, allowPaths)) {
445
+ emit.error({
446
+ code: "tunnel_instance_in_use",
447
+ message: `Tunnel [${name}] is already up sharing ${formatPaths(existing.allow_paths)}. ` +
448
+ `Stop it before starting it with ${formatPaths(allowPaths)}.`,
449
+ });
450
+ process.exit(1);
451
+ }
401
452
  emit.text(`Already up [${name}]: ${existing.url}\n`);
402
453
  emit.text(` Provider: ${existing.provider}\n`);
403
454
  emit.text(` Forwarding: ${existing.target} (Host: ${existing.vhost})\n`);
455
+ emit.text(` Paths: ${formatPaths(existing.allow_paths)}\n`);
404
456
  return;
405
457
  }
406
458
  if (existing)
@@ -438,6 +490,7 @@ async function up(opts) {
438
490
  vhost,
439
491
  provider,
440
492
  allowedIps: cfg.allowed_ips,
493
+ allowPaths,
441
494
  gateLogPath,
442
495
  });
443
496
  await sleep(800);
@@ -507,16 +560,19 @@ async function up(opts) {
507
560
  target,
508
561
  vhost,
509
562
  gate_port: gatePort,
563
+ allow_paths: allowPaths,
510
564
  tailscale_mode: tailscaleMode,
511
565
  tailscale_path: tailscalePath,
512
566
  tailscale_https_port: tailscaleHttpsPort,
513
567
  };
514
568
  writeState(state);
569
+ recordTunnelServer(state);
515
570
  const stopHint = name === DEFAULT_INSTANCE ? "harn tunnel down" : `harn tunnel down --name ${name}`;
516
571
  emit.text(`\n Instance: ${name}\n`);
517
572
  emit.text(` Provider: ${provider}${tailscaleMode ? ` (${tailscaleMode})` : ""}\n`);
518
573
  emit.text(` URL: ${url}\n\n`);
519
574
  emit.text(` Forwarding: ${target} (Host: ${vhost})\n`);
575
+ emit.text(` Paths: ${formatPaths(allowPaths)}\n`);
520
576
  emit.text(` Gate port: ${gatePort}\n`);
521
577
  if (provider === "cloudflare") {
522
578
  emit.text(` Allowed IPs: ${cfg.allowed_ips.join(", ")}\n\n`);
@@ -533,6 +589,14 @@ async function up(opts) {
533
589
  emit.text(` Stop: ${stopHint}\n`);
534
590
  emit.text(" Status: harn tunnel status\n");
535
591
  }
592
+ /** The server registry lists tunnels beside every other local server. */
593
+ function registryRoot() {
594
+ return resolveCoordRoot() ?? process.cwd();
595
+ }
596
+ function recordTunnelServer(state) {
597
+ const coordRoot = registryRoot();
598
+ tryRegisterServer(tunnelServerInput(state, coordRoot), { coordRoot });
599
+ }
536
600
  /** Tear down a single instance by name. Returns the number of processes killed. */
537
601
  function downOne(name, killed) {
538
602
  const before = killed.size;
@@ -558,6 +622,7 @@ function downOne(name, killed) {
558
622
  // lost; they'd otherwise squat on the port and break the next `up`.
559
623
  sweepStrays(state?.gate_port ?? DEFAULT_GATE_PORT, killed);
560
624
  clearState(name);
625
+ unregisterServer(tunnelServerId(name), { coordRoot: registryRoot() });
561
626
  return killed.size - before;
562
627
  }
563
628
  function down(opts) {
@@ -633,6 +698,14 @@ export async function reloadOne(state) {
633
698
  if (!bunAvailable()) {
634
699
  return { ok: false, message: `[${name}] bun is not on PATH, so the gate can't be respawned.` };
635
700
  }
701
+ if (state.allow_paths.length === 0) {
702
+ return {
703
+ ok: false,
704
+ message: `[${name}] has no recorded path scope, so a reloaded gate would refuse every request. ` +
705
+ `Restart it with \`${bin} tunnel down --name ${name}\` and ` +
706
+ `\`${bin} tunnel up --name ${name} --allow-path <prefix>\`.`,
707
+ };
708
+ }
636
709
  // Kill ONLY the gate. sweepStrays() is deliberately avoided here: it also
637
710
  // matches `--url http://localhost:<port>`, which is the provider process we
638
711
  // are going out of our way to keep alive.
@@ -662,6 +735,7 @@ export async function reloadOne(state) {
662
735
  vhost: state.vhost,
663
736
  provider: state.provider,
664
737
  allowedIps: cfg.allowed_ips,
738
+ allowPaths: state.allow_paths,
665
739
  gateLogPath: tunnelLogDestinations(name, state.provider).gate,
666
740
  });
667
741
  await sleep(800);
@@ -676,6 +750,7 @@ export async function reloadOne(state) {
676
750
  };
677
751
  }
678
752
  writeState({ ...state, gate_pid: pid });
753
+ recordTunnelServer({ ...state, gate_pid: pid });
679
754
  const allowNote = state.provider === "cloudflare" ? ` Allowlist: ${cfg.allowed_ips.length} IP(s).` : "";
680
755
  return { ok: true, message: `[${name}] gate reloaded, URL unchanged: ${state.url}.${allowNote}` };
681
756
  }
@@ -745,6 +820,7 @@ function statusDetail(state) {
745
820
  emit.text(` Provider: ${state.provider}${state.tailscale_mode ? ` (${state.tailscale_mode})` : ""}\n`);
746
821
  emit.text(` URL: ${state.url}\n`);
747
822
  emit.text(` Forwarding: ${state.target} (Host: ${state.vhost})\n`);
823
+ emit.text(` Paths: ${formatPaths(state.allow_paths)}\n`);
748
824
  emit.text(` Gate port: ${state.gate_port}\n`);
749
825
  if (state.provider === "cloudflare") {
750
826
  emit.text(` Allowed IPs: ${cfg.allowed_ips.join(", ")}\n`);
@@ -786,6 +862,7 @@ function status(opts) {
786
862
  state: instanceState(s),
787
863
  url: s.url,
788
864
  fwd: `${s.target} (${s.vhost})`,
865
+ paths: formatPaths(s.allow_paths),
789
866
  port: String(s.gate_port),
790
867
  up: fmtUptime(s.started_at),
791
868
  }));
@@ -795,12 +872,13 @@ function status(opts) {
795
872
  state: 5,
796
873
  url: Math.max(3, ...rows.map((r) => r.url.length)),
797
874
  fwd: Math.max(10, ...rows.map((r) => r.fwd.length)),
875
+ paths: Math.max(5, ...rows.map((r) => r.paths.length)),
798
876
  port: 4,
799
877
  };
800
878
  const pad = (s, n) => s.padEnd(n);
801
- emit.text(`${pad("NAME", w.name)} ${pad("PROVIDER", w.provider)} ${pad("STATE", w.state)} ${pad("URL", w.url)} ${pad("FORWARDING", w.fwd)} ${pad("PORT", w.port)} UPTIME\n`);
879
+ emit.text(`${pad("NAME", w.name)} ${pad("PROVIDER", w.provider)} ${pad("STATE", w.state)} ${pad("URL", w.url)} ${pad("FORWARDING", w.fwd)} ${pad("PATHS", w.paths)} ${pad("PORT", w.port)} UPTIME\n`);
802
880
  for (const r of rows) {
803
- emit.text(`${pad(r.name, w.name)} ${pad(r.provider, w.provider)} ${pad(r.state, w.state)} ${pad(r.url, w.url)} ${pad(r.fwd, w.fwd)} ${pad(r.port, w.port)} ${r.up}\n`);
881
+ emit.text(`${pad(r.name, w.name)} ${pad(r.provider, w.provider)} ${pad(r.state, w.state)} ${pad(r.url, w.url)} ${pad(r.fwd, w.fwd)} ${pad(r.paths, w.paths)} ${pad(r.port, w.port)} ${r.up}\n`);
804
882
  }
805
883
  }
806
884
  function logs(opts) {
@@ -886,6 +964,8 @@ export function registerTunnelCommand(program, emitParam, contextParam) {
886
964
  .option("--visibility <visibility>", "Tailscale only: tailnet (Serve, default) or public (Funnel)", "tailnet")
887
965
  .option("--path <path>", "Tailscale only: URL path mount (default: / for default, /<name> for named instances)")
888
966
  .option("--https-port <port>", "Tailscale only: HTTPS listen port (default: 443)", "443")
967
+ .option("--allow-path <prefix>", "URL path prefix the tunnel shares (repeatable, required). Every other path is refused; " +
968
+ "pass / to publish the whole upstream.", collectPath, [])
889
969
  .action(up);
890
970
  cmd
891
971
  .command("down")
@@ -17,6 +17,9 @@ export declare const BUILTIN_ADAPTER_PROFILES: {
17
17
  readonly date: "2026-08-18";
18
18
  readonly version: "2.1.233 (Claude Code)";
19
19
  };
20
+ readonly fullAccess: {
21
+ readonly argv: readonly ["--permission-mode", "bypassPermissions", "--settings", "{\"sandbox\":{\"enabled\":false}}"];
22
+ };
20
23
  readonly capabilities: AdapterCapabilities;
21
24
  };
22
25
  readonly codex: {
@@ -41,6 +44,9 @@ export declare const BUILTIN_ADAPTER_PROFILES: {
41
44
  };
42
45
  readonly writableRoots: true;
43
46
  };
47
+ readonly fullAccess: {
48
+ readonly argv: readonly ["--sandbox", "danger-full-access"];
49
+ };
44
50
  readonly capabilities: AdapterCapabilities;
45
51
  };
46
52
  readonly cursor: {
@@ -58,6 +64,9 @@ export declare const BUILTIN_ADAPTER_PROFILES: {
58
64
  readonly date: "2026-09-20";
59
65
  readonly version: "2026.09.18-9a7762b";
60
66
  };
67
+ readonly fullAccess: {
68
+ readonly argv: readonly ["--sandbox", "disabled"];
69
+ };
61
70
  readonly capabilities: AdapterCapabilities;
62
71
  };
63
72
  readonly opencode: {
@@ -1 +1 @@
1
- {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAmB,MAAM,YAAY,CAAC;AAgCvF;gFACgF;AAChF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkIc,CAAC;AAEpD,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,wBAAwB,CAAC;AAErE,eAAO,MAAM,mBAAmB,8DAE/B,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,gBAAgB,CAEnE;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE5E;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAUlF"}
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAmB,MAAM,YAAY,CAAC;AAgCvF;gFACgF;AAChF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuJc,CAAC;AAEpD,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,wBAAwB,CAAC;AAErE,eAAO,MAAM,mBAAmB,8DAE/B,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,gBAAgB,CAEnE;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE5E;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAUlF"}