harnery 0.46.0 → 0.47.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 (187) hide show
  1. package/README.md +24 -0
  2. package/bin/README.md +10 -0
  3. package/bin/agent-coord.mjs +3 -0
  4. package/bin/agent-hook.mjs +3 -0
  5. package/bin/harn.mjs +3 -0
  6. package/bin/run.mjs +43 -0
  7. package/dist/commander.d.ts +18 -6
  8. package/dist/commander.d.ts.map +1 -1
  9. package/dist/commander.js +74 -2
  10. package/dist/commands/agents.d.ts +25 -0
  11. package/dist/commands/agents.d.ts.map +1 -1
  12. package/dist/commands/agents.js +70 -27
  13. package/dist/commands/autoupdate.d.ts +12 -0
  14. package/dist/commands/autoupdate.d.ts.map +1 -0
  15. package/dist/commands/autoupdate.js +190 -0
  16. package/dist/commands/computer.d.ts +45 -0
  17. package/dist/commands/computer.d.ts.map +1 -0
  18. package/dist/commands/computer.js +509 -0
  19. package/dist/commands/docs.js +3 -1
  20. package/dist/commands/fetch.js +11 -1
  21. package/dist/commands/grep.d.ts +72 -0
  22. package/dist/commands/grep.d.ts.map +1 -1
  23. package/dist/commands/grep.js +200 -47
  24. package/dist/commands/init.d.ts.map +1 -1
  25. package/dist/commands/init.js +15 -9
  26. package/dist/commands/rm.js +2 -2
  27. package/dist/commands/web.d.ts.map +1 -1
  28. package/dist/commands/web.js +2 -0
  29. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  30. package/dist/core/agents/canonical-emit.js +2 -3
  31. package/dist/core/agents/coord-bin.d.ts +3 -0
  32. package/dist/core/agents/coord-bin.d.ts.map +1 -1
  33. package/dist/core/agents/coord-bin.js +6 -0
  34. package/dist/core/agents/coord-client.d.ts.map +1 -1
  35. package/dist/core/agents/coord-client.js +6 -2
  36. package/dist/core/agents/render/prompt-context.d.ts +55 -7
  37. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  38. package/dist/core/agents/render/prompt-context.js +122 -48
  39. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  40. package/dist/core/agents/rules/claim-conflict.js +36 -16
  41. package/dist/core/agents/rules/stop-hook.d.ts +6 -0
  42. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  43. package/dist/core/agents/rules/stop-hook.js +20 -1
  44. package/dist/core/agents/state/proc-start.d.ts +3 -1
  45. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  46. package/dist/core/agents/state/proc-start.js +26 -4
  47. package/dist/core/autoupdate/engine.d.ts +97 -0
  48. package/dist/core/autoupdate/engine.d.ts.map +1 -0
  49. package/dist/core/autoupdate/engine.js +548 -0
  50. package/dist/core/autoupdate/fingerprint.d.ts +16 -0
  51. package/dist/core/autoupdate/fingerprint.d.ts.map +1 -0
  52. package/dist/core/autoupdate/fingerprint.js +73 -0
  53. package/dist/core/autoupdate/git.d.ts +30 -0
  54. package/dist/core/autoupdate/git.d.ts.map +1 -0
  55. package/dist/core/autoupdate/git.js +82 -0
  56. package/dist/core/autoupdate/index.d.ts +6 -0
  57. package/dist/core/autoupdate/index.d.ts.map +1 -0
  58. package/dist/core/autoupdate/index.js +4 -0
  59. package/dist/core/autoupdate/state.d.ts +59 -0
  60. package/dist/core/autoupdate/state.d.ts.map +1 -0
  61. package/dist/core/autoupdate/state.js +214 -0
  62. package/dist/core/autoupdate/types.d.ts +113 -0
  63. package/dist/core/autoupdate/types.d.ts.map +1 -0
  64. package/dist/core/autoupdate/types.js +35 -0
  65. package/dist/core/config.d.ts +53 -1
  66. package/dist/core/config.d.ts.map +1 -1
  67. package/dist/core/config.js +114 -1
  68. package/dist/core/events/v3/bootstrap.js +1 -0
  69. package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
  70. package/dist/core/events/v3/runtime-build.js +1 -0
  71. package/dist/core/hooks/adapter/wiring.d.ts +12 -2
  72. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  73. package/dist/core/hooks/adapter/wiring.js +38 -8
  74. package/dist/core/hooks/autoupdate-cue.d.ts +29 -0
  75. package/dist/core/hooks/autoupdate-cue.d.ts.map +1 -0
  76. package/dist/core/hooks/autoupdate-cue.js +53 -0
  77. package/dist/core/hooks/cli.js +52 -4
  78. package/dist/core/hooks/effects/index.js +4 -4
  79. package/dist/core/hooks/resolve/coord-root.js +1 -0
  80. package/dist/core/hooks/resolve/transcript.d.ts +12 -0
  81. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  82. package/dist/core/hooks/resolve/transcript.js +54 -0
  83. package/dist/core/semantic/contract.d.ts +3 -3
  84. package/dist/core/semantic/service.d.ts.map +1 -1
  85. package/dist/core/semantic/service.js +4 -2
  86. package/dist/core/storage/modes.d.ts +1 -1
  87. package/dist/core/storage/modes.d.ts.map +1 -1
  88. package/dist/core/storage/modes.js +5 -1
  89. package/dist/core/workflow/durable-record.d.ts +2 -0
  90. package/dist/core/workflow/durable-record.d.ts.map +1 -1
  91. package/dist/core/workflow/durable-record.js +13 -1
  92. package/dist/core/workflow/engine.js +4 -0
  93. package/dist/core/workflow/spawn-claude.d.ts +7 -0
  94. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  95. package/dist/core/workflow/spawn-claude.js +16 -4
  96. package/dist/core/workflow/specialists.d.ts.map +1 -1
  97. package/dist/core/workflow/specialists.js +1 -0
  98. package/dist/core/workflow/types.d.ts +10 -0
  99. package/dist/core/workflow/types.d.ts.map +1 -1
  100. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  101. package/dist/core/workflow/workspaces/leases.js +1 -9
  102. package/dist/lib/computer/cua.d.ts +50 -0
  103. package/dist/lib/computer/cua.d.ts.map +1 -0
  104. package/dist/lib/computer/cua.js +474 -0
  105. package/dist/lib/computer/host.d.ts +19 -0
  106. package/dist/lib/computer/host.d.ts.map +1 -0
  107. package/dist/lib/computer/host.js +72 -0
  108. package/dist/lib/computer/lease.d.ts +13 -0
  109. package/dist/lib/computer/lease.d.ts.map +1 -0
  110. package/dist/lib/computer/lease.js +269 -0
  111. package/dist/lib/computer/types.d.ts +163 -0
  112. package/dist/lib/computer/types.d.ts.map +1 -0
  113. package/dist/lib/computer/types.js +10 -0
  114. package/dist/lib/docs-metadata-v2.d.ts.map +1 -1
  115. package/dist/lib/docs-metadata-v2.js +6 -5
  116. package/dist/lib/exec.d.ts.map +1 -1
  117. package/dist/lib/exec.js +1 -0
  118. package/dist/lib/guarded-remove.d.ts.map +1 -1
  119. package/dist/lib/guarded-remove.js +5 -4
  120. package/dist/lib/headless/index.d.ts +33 -0
  121. package/dist/lib/headless/index.d.ts.map +1 -1
  122. package/dist/lib/headless/index.js +38 -2
  123. package/dist/lib/http/client.d.ts +2 -9
  124. package/dist/lib/http/client.d.ts.map +1 -1
  125. package/dist/lib/http/client.js +30 -9
  126. package/dist/lib/instructions/apply.d.ts +9 -0
  127. package/dist/lib/instructions/apply.d.ts.map +1 -1
  128. package/dist/lib/instructions/apply.js +92 -14
  129. package/dist/lib/instructions/git-hooks.d.ts.map +1 -1
  130. package/dist/lib/instructions/git-hooks.js +8 -4
  131. package/dist/lib/instructions/templates.d.ts +11 -0
  132. package/dist/lib/instructions/templates.d.ts.map +1 -1
  133. package/dist/lib/instructions/templates.js +81 -1
  134. package/package.json +5 -5
  135. package/schemas/config.schema.json +89 -1
  136. package/src/commander.ts +98 -8
  137. package/src/commands/agents.ts +100 -32
  138. package/src/commands/autoupdate.ts +250 -0
  139. package/src/commands/computer.ts +682 -0
  140. package/src/commands/docs.ts +3 -1
  141. package/src/commands/fetch.ts +11 -1
  142. package/src/commands/grep.ts +274 -59
  143. package/src/commands/init.ts +17 -7
  144. package/src/commands/rm.ts +2 -2
  145. package/src/commands/web.ts +2 -0
  146. package/src/core/agents/canonical-emit.ts +2 -3
  147. package/src/core/agents/coord-bin.ts +30 -0
  148. package/src/core/agents/coord-client.ts +6 -2
  149. package/src/core/agents/render/prompt-context.ts +152 -53
  150. package/src/core/agents/rules/claim-conflict.ts +50 -18
  151. package/src/core/agents/rules/stop-hook.ts +28 -1
  152. package/src/core/agents/state/proc-start.ts +29 -5
  153. package/src/core/autoupdate/engine.ts +691 -0
  154. package/src/core/autoupdate/fingerprint.ts +89 -0
  155. package/src/core/autoupdate/git.ts +120 -0
  156. package/src/core/autoupdate/index.ts +29 -0
  157. package/src/core/autoupdate/state.ts +269 -0
  158. package/src/core/autoupdate/types.ts +135 -0
  159. package/src/core/config.ts +142 -1
  160. package/src/core/events/v3/bootstrap.ts +1 -0
  161. package/src/core/events/v3/runtime-build.ts +1 -0
  162. package/src/core/hooks/adapter/wiring.ts +51 -6
  163. package/src/core/hooks/autoupdate-cue.ts +72 -0
  164. package/src/core/hooks/cli.ts +54 -1
  165. package/src/core/hooks/effects/index.ts +4 -4
  166. package/src/core/hooks/resolve/coord-root.ts +1 -0
  167. package/src/core/hooks/resolve/transcript.ts +61 -0
  168. package/src/core/semantic/service.ts +6 -2
  169. package/src/core/storage/modes.ts +7 -1
  170. package/src/core/workflow/durable-record.ts +15 -1
  171. package/src/core/workflow/engine.ts +3 -0
  172. package/src/core/workflow/spawn-claude.ts +16 -4
  173. package/src/core/workflow/specialists.ts +1 -0
  174. package/src/core/workflow/types.ts +10 -0
  175. package/src/core/workflow/workspaces/leases.ts +1 -9
  176. package/src/lib/computer/cua.ts +572 -0
  177. package/src/lib/computer/host.ts +87 -0
  178. package/src/lib/computer/lease.ts +299 -0
  179. package/src/lib/computer/types.ts +177 -0
  180. package/src/lib/docs-metadata-v2.ts +24 -5
  181. package/src/lib/exec.ts +1 -0
  182. package/src/lib/guarded-remove.ts +5 -3
  183. package/src/lib/headless/index.ts +66 -2
  184. package/src/lib/http/client.ts +33 -19
  185. package/src/lib/instructions/apply.ts +100 -12
  186. package/src/lib/instructions/git-hooks.ts +8 -4
  187. package/src/lib/instructions/templates.ts +95 -1
@@ -0,0 +1,509 @@
1
+ // `computer`: opt-in desktop control through one pinned desktop driver
2
+ // (ADR 0228). The driver adapter (src/lib/computer/cua.ts), host detection and
3
+ // the cross-call desktop lease live in src/lib/computer/; this command owns
4
+ // the config gate, flag parsing, lease policy for `call`, screenshot redaction,
5
+ // rendering, and exit codes.
6
+ //
7
+ // Exit codes: 0 ok · 1 error (disabled, unsupported host, not installed,
8
+ // driver failure; `call` propagates the driver's own exit code) · 3 the
9
+ // desktop lease is held by another agent.
10
+ import { hostname, userInfo } from "node:os";
11
+ import { Command } from "commander";
12
+ import { resolveCoordRoot, resolveOwner } from "../core/agents/coord-client.js";
13
+ import { agentDisplayName, resolveShortName } from "../core/agents/state/activity-log.js";
14
+ import { readComputerConfig } from "../core/config.js";
15
+ /** Exit code for a desktop held by another agent. */
16
+ export const EXIT_LEASE_HELD = 3;
17
+ /** `call` waits this long for another agent's lease when nothing overrides it. */
18
+ export const DEFAULT_CALL_WAIT_SECONDS = 30;
19
+ /**
20
+ * Who is asking for the desktop: the coordinated agent when one is resolvable,
21
+ * else the calling OS user (`shell:<user>@<hostname>`, label `shell-<user>`), so
22
+ * separate shell invocations by the same person share one lease.
23
+ */
24
+ export function resolveLeaseOwner() {
25
+ const root = resolveCoordRoot();
26
+ const id = resolveOwner();
27
+ if (id) {
28
+ return {
29
+ id,
30
+ label: root ? resolveShortName(root, id) : agentDisplayName(id),
31
+ ...(root ? { project: root } : {}),
32
+ };
33
+ }
34
+ const user = userInfo().username;
35
+ return {
36
+ id: `shell:${user}@${hostname()}`,
37
+ label: `shell-${user}`,
38
+ ...(root ? { project: root } : {}),
39
+ };
40
+ }
41
+ async function loadDefaultDeps() {
42
+ const [{ detectHost }, { cuaDriver }, { desktopLease, leaseDir, DEFAULT_LEASE_IDLE_MS }] = await Promise.all([
43
+ import("../lib/computer/host.js"),
44
+ import("../lib/computer/cua.js"),
45
+ import("../lib/computer/lease.js"),
46
+ ]);
47
+ return {
48
+ host: () => detectHost(),
49
+ driver: cuaDriver,
50
+ lease: desktopLease,
51
+ leaseDir,
52
+ defaultIdleMs: DEFAULT_LEASE_IDLE_MS,
53
+ };
54
+ }
55
+ /** Fill in every dependency the caller did not inject; driver modules load lazily. */
56
+ async function resolveDeps(input) {
57
+ const needsDefaults = !input.host ||
58
+ !input.driver ||
59
+ !input.lease ||
60
+ !input.leaseDir ||
61
+ input.defaultIdleMs === undefined;
62
+ const defaults = needsDefaults ? await loadDefaultDeps() : undefined;
63
+ return {
64
+ emit: input.emit,
65
+ binName: input.binName,
66
+ host: input.host ?? defaults?.host,
67
+ driver: input.driver ?? defaults?.driver,
68
+ lease: input.lease ?? defaults?.lease,
69
+ leaseDir: input.leaseDir ?? defaults?.leaseDir,
70
+ defaultIdleMs: input.defaultIdleMs ?? defaults?.defaultIdleMs,
71
+ owner: input.owner ?? resolveLeaseOwner,
72
+ config: input.config ?? (() => readComputerConfig()),
73
+ writeStderr: input.writeStderr ?? ((text) => process.stderr.write(text)),
74
+ now: input.now ?? Date.now,
75
+ };
76
+ }
77
+ class ComputerError extends Error {
78
+ code;
79
+ exitCode;
80
+ hint;
81
+ constructor(code, message, exitCode = 1, hint) {
82
+ super(message);
83
+ this.code = code;
84
+ this.exitCode = exitCode;
85
+ this.hint = hint;
86
+ }
87
+ }
88
+ function enableHint() {
89
+ return 'Add `"computer": { "enabled": true }` to .harnery/config.jsonc to enable it for this project.';
90
+ }
91
+ function requireEnabled(deps) {
92
+ if (deps.config().enabled)
93
+ return;
94
+ throw new ComputerError("computer_disabled", `Computer use is off in this project. ${enableHint()}`);
95
+ }
96
+ function requireSupported(host) {
97
+ if (host.kind === "unsupported" || !host.desktopStateDir) {
98
+ throw new ComputerError("computer_unsupported_host", `Computer use is not available on this host: ${host.reason ?? "no supported desktop driver build"}`);
99
+ }
100
+ return host;
101
+ }
102
+ function requireInstalled(deps, host) {
103
+ const exe = deps.driver.installedExecutable(host);
104
+ if (exe)
105
+ return exe;
106
+ throw new ComputerError("computer_not_installed", `The pinned desktop driver (${deps.driver.id} ${deps.driver.version}) is not installed. Run \`${deps.binName} computer install --yes\`.`);
107
+ }
108
+ function leaseOptions(deps, host, waitMs = 0) {
109
+ const idleSeconds = deps.config().idle_seconds;
110
+ return {
111
+ dir: deps.leaseDir(host.desktopStateDir),
112
+ idleMs: idleSeconds !== undefined ? Math.round(idleSeconds * 1000) : deps.defaultIdleMs,
113
+ waitMs,
114
+ now: deps.now,
115
+ };
116
+ }
117
+ function expiresInSeconds(lease, nowMs) {
118
+ return Math.max(0, Math.ceil((Date.parse(lease.expires_at) - nowMs) / 1000));
119
+ }
120
+ function describeLease(lease, nowMs) {
121
+ return `${lease.owner_label} (generation ${lease.generation}, idle-expires in ${expiresInSeconds(lease, nowMs)}s)`;
122
+ }
123
+ function heldError(deps, holder) {
124
+ return new ComputerError("computer_lease_held", `Desktop is held by ${describeLease(holder, deps.now())}. Wait, or ask them to run \`${deps.binName} computer lease release\`.`, EXIT_LEASE_HELD);
125
+ }
126
+ function parseSeconds(raw, flag) {
127
+ if (raw === undefined)
128
+ return undefined;
129
+ const value = Number(raw);
130
+ if (!Number.isFinite(value) || value < 0 || value > 3600) {
131
+ throw new ComputerError("computer_usage", `${flag} must be a number of seconds from 0 to 3600`);
132
+ }
133
+ return value;
134
+ }
135
+ function parseCallArgs(raw) {
136
+ if (raw === undefined || raw.trim() === "")
137
+ return {};
138
+ let parsed;
139
+ try {
140
+ parsed = JSON.parse(raw);
141
+ }
142
+ catch (err) {
143
+ throw new ComputerError("computer_usage", `Tool arguments must be JSON: ${err.message}`);
144
+ }
145
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
146
+ throw new ComputerError("computer_usage", "Tool arguments must be a JSON object.");
147
+ }
148
+ return parsed;
149
+ }
150
+ const PNG_B64_RE = /"([A-Za-z0-9_]*png_b64)"(\s*:\s*)"((?:[^"\\]|\\.)*)"/g;
151
+ /**
152
+ * Replace inline base64 screenshots in driver output with a short note, so a
153
+ * single state read cannot flood the calling agent's context. Works on the raw
154
+ * text, so the driver's formatting survives.
155
+ */
156
+ export function redactInlineScreenshots(stdout) {
157
+ return stdout.replace(PNG_B64_RE, (_match, key, sep, value) => `"${key}"${sep}"<omitted ${value.length} chars; pass screenshot_out_file or --keep-screenshot>"`);
158
+ }
159
+ function printDriverResult(deps, result, stdout = result.stdout) {
160
+ if (stdout)
161
+ emitLine(deps.emit, stdout);
162
+ if (result.stderr)
163
+ deps.writeStderr(result.stderr.endsWith("\n") ? result.stderr : `${result.stderr}\n`);
164
+ if (result.exitCode !== 0)
165
+ deps.emit.setExitCode(result.exitCode);
166
+ }
167
+ async function ensureDaemon(deps, host) {
168
+ const current = deps.driver.daemonStatus(host);
169
+ if (current.running)
170
+ return current;
171
+ const started = await deps.driver.startDaemon(host);
172
+ if (!started.running) {
173
+ throw new ComputerError("computer_daemon_failed", `The desktop driver daemon did not start${started.detail ? `: ${started.detail}` : "."}`);
174
+ }
175
+ return started;
176
+ }
177
+ /** Write human-readable text, ending it with exactly one newline. */
178
+ function emitLine(emit, s) {
179
+ emit.text(s.endsWith("\n") ? s : `${s}\n`);
180
+ }
181
+ /** Run one action, turning a ComputerError (or any throw) into emit.error + exit code. */
182
+ function guarded(input, body) {
183
+ return async (...args) => {
184
+ const deps = await resolveDeps(input);
185
+ try {
186
+ await body(deps, ...args);
187
+ }
188
+ catch (err) {
189
+ if (err instanceof ComputerError) {
190
+ deps.emit.error({
191
+ code: err.code,
192
+ message: err.message,
193
+ ...(err.hint ? { hint: err.hint } : {}),
194
+ });
195
+ deps.emit.setExitCode(err.exitCode);
196
+ return;
197
+ }
198
+ deps.emit.error({ code: "computer_error", message: err.message ?? String(err) });
199
+ deps.emit.setExitCode(1);
200
+ }
201
+ };
202
+ }
203
+ /** Pull `{name, status, message}` checks out of the driver's health report, leniently. */
204
+ function healthChecks(json) {
205
+ const source = Array.isArray(json)
206
+ ? json
207
+ : json && typeof json === "object"
208
+ ? json.checks
209
+ : undefined;
210
+ const toCheck = (name, value) => {
211
+ if (!value || typeof value !== "object")
212
+ return undefined;
213
+ const record = value;
214
+ const status = record.status ??
215
+ record.state ??
216
+ (record.ok === true ? "pass" : record.ok === false ? "fail" : undefined);
217
+ if (typeof status !== "string")
218
+ return undefined;
219
+ const message = record.message ?? record.detail;
220
+ return {
221
+ name: typeof record.name === "string" ? record.name : name,
222
+ status,
223
+ ...(typeof message === "string" ? { message } : {}),
224
+ };
225
+ };
226
+ if (Array.isArray(source)) {
227
+ const checks = source
228
+ .map((value, i) => toCheck(`check-${i + 1}`, value))
229
+ .filter((c) => c !== undefined);
230
+ return checks.length > 0 ? checks : undefined;
231
+ }
232
+ if (source && typeof source === "object") {
233
+ const checks = Object.entries(source)
234
+ .map(([name, value]) => toCheck(name, value))
235
+ .filter((c) => c !== undefined);
236
+ return checks.length > 0 ? checks : undefined;
237
+ }
238
+ return undefined;
239
+ }
240
+ // "skip" is a check that does not apply on this platform, not a failure.
241
+ const PASSING = new Set(["pass", "passed", "ok", "healthy", "success", "skip", "skipped", "info"]);
242
+ /**
243
+ * Build the `computer` command tree under `parent` (a fresh root when omitted)
244
+ * and return it. Tests pass fakes for host/driver/lease/owner/config.
245
+ */
246
+ export function createComputerCommand(input, parent = new Command()) {
247
+ const bin = input.binName;
248
+ const computer = parent
249
+ .command("computer")
250
+ .description("Opt-in desktop control through a pinned desktop driver: install it, run its daemon, " +
251
+ "and call its tools while holding a desktop lease shared by every agent on this machine.")
252
+ .addHelpText("after", `\nEnable per project with "computer": { "enabled": true } in .harnery/config.jsonc.\n` +
253
+ `Exit codes: 0 ok · 1 error · ${EXIT_LEASE_HELD} desktop held by another agent.\n` +
254
+ `Start with \`${bin} computer status\`.`);
255
+ // ------------------------------------------------------------------ status
256
+ computer
257
+ .command("status")
258
+ .description("Show the host, pinned driver, install, daemon, desktop lease holder, and enabled flag.")
259
+ .option("--json", "Emit the status as JSON.")
260
+ .action(guarded(input, (deps, opts) => {
261
+ const config = deps.config();
262
+ const host = deps.host();
263
+ const supported = host.kind !== "unsupported" && Boolean(host.desktopStateDir);
264
+ const installed = supported ? (deps.driver.installedExecutable(host) ?? null) : null;
265
+ let daemon = null;
266
+ if (installed) {
267
+ try {
268
+ daemon = deps.driver.daemonStatus(host);
269
+ }
270
+ catch (err) {
271
+ daemon = { running: false, detail: err.message };
272
+ }
273
+ }
274
+ const lease = supported ? deps.lease.read(leaseOptions(deps, host)) : null;
275
+ const nowMs = deps.now();
276
+ if (opts.json) {
277
+ deps.emit.data({
278
+ enabled: config.enabled,
279
+ host: { kind: host.kind, ...(host.reason ? { reason: host.reason } : {}) },
280
+ driver: { id: deps.driver.id, version: deps.driver.version },
281
+ installed,
282
+ daemon,
283
+ lease: lease ? { ...lease, expires_in_seconds: expiresInSeconds(lease, nowMs) } : null,
284
+ });
285
+ return;
286
+ }
287
+ const lines = [
288
+ `enabled: ${config.enabled ? "yes" : `no (${enableHint()})`}`,
289
+ `host: ${host.kind}${host.reason ? ` (${host.reason})` : ""}`,
290
+ `driver: ${deps.driver.id} ${deps.driver.version} (pinned)`,
291
+ ];
292
+ if (supported) {
293
+ lines.push(`installed: ${installed ?? "not installed"}`);
294
+ lines.push(`daemon: ${daemon
295
+ ? daemon.running
296
+ ? `running${daemon.pid !== undefined ? ` (pid ${daemon.pid})` : ""}`
297
+ : `stopped${daemon.detail ? ` (${daemon.detail})` : ""}`
298
+ : "n/a"}`);
299
+ lines.push(`lease: ${lease ? `held by ${describeLease(lease, nowMs)}` : "free"}`);
300
+ }
301
+ emitLine(deps.emit, lines.join("\n"));
302
+ }));
303
+ // ----------------------------------------------------------------- install
304
+ computer
305
+ .command("install")
306
+ .description("Preview the pinned driver install (URL, SHA-256, install dir, preference file); --yes downloads, verifies, and installs it.")
307
+ .option("--yes", "Apply the install. Without it, only the plan is shown.")
308
+ .option("--json", "Emit the plan or result as JSON.")
309
+ .action(guarded(input, async (deps, opts) => {
310
+ requireEnabled(deps);
311
+ const host = requireSupported(deps.host());
312
+ const plan = deps.driver.planInstall(host);
313
+ if (!opts.yes) {
314
+ if (opts.json) {
315
+ deps.emit.data({ applied: false, plan });
316
+ return;
317
+ }
318
+ emitLine(deps.emit, [
319
+ `${plan.alreadyInstalled ? "Already installed" : "Would install"}: ${plan.driver} ${plan.version} for ${plan.host}`,
320
+ ` url: ${plan.artifact.url}`,
321
+ ` sha256: ${plan.artifact.sha256}`,
322
+ ` install dir: ${plan.installDir}`,
323
+ ` executable: ${plan.executable}`,
324
+ ` preferences: ${plan.preferenceFile} ${JSON.stringify(plan.preferences)}`,
325
+ plan.alreadyInstalled
326
+ ? "Nothing to do."
327
+ : `Re-run with --yes to install: \`${deps.binName} computer install --yes\`.`,
328
+ ].join("\n"));
329
+ return;
330
+ }
331
+ const result = await deps.driver.install(plan);
332
+ if (opts.json) {
333
+ deps.emit.data({ applied: true, result });
334
+ return;
335
+ }
336
+ emitLine(deps.emit, result.installed
337
+ ? `Installed ${result.driver} ${result.version} at ${result.executable}.\nStart it with \`${deps.binName} computer up\`.`
338
+ : `${result.driver} ${result.version} was already installed at ${result.executable}.`);
339
+ }));
340
+ // ---------------------------------------------------------------- up/down
341
+ computer
342
+ .command("up")
343
+ .description("Start the desktop driver daemon (unelevated) if it is not running.")
344
+ .action(guarded(input, async (deps) => {
345
+ requireEnabled(deps);
346
+ const host = requireSupported(deps.host());
347
+ requireInstalled(deps, host);
348
+ const status = await ensureDaemon(deps, host);
349
+ emitLine(deps.emit, `Daemon running${status.pid !== undefined ? ` (pid ${status.pid})` : ""}.`);
350
+ }));
351
+ computer
352
+ .command("down")
353
+ .description("Stop the desktop driver daemon.")
354
+ .action(guarded(input, (deps) => {
355
+ const host = requireSupported(deps.host());
356
+ requireInstalled(deps, host);
357
+ const status = deps.driver.stopDaemon(host);
358
+ emitLine(deps.emit, status.running ? `Daemon still running: ${status.detail}` : "Daemon stopped.");
359
+ if (status.running)
360
+ deps.emit.setExitCode(1);
361
+ }));
362
+ // ------------------------------------------------------------------ doctor
363
+ computer
364
+ .command("doctor")
365
+ .description("Run the driver's own health report; any non-passing check exits 1.")
366
+ .option("--json", "Emit the checks as JSON.")
367
+ .action(guarded(input, (deps, opts) => {
368
+ const host = requireSupported(deps.host());
369
+ requireInstalled(deps, host);
370
+ const result = deps.driver.health(host);
371
+ let json = result.json;
372
+ if (json === undefined) {
373
+ try {
374
+ json = JSON.parse(result.stdout);
375
+ }
376
+ catch {
377
+ json = undefined;
378
+ }
379
+ }
380
+ const checks = healthChecks(json);
381
+ const failing = checks?.filter((c) => !PASSING.has(c.status.toLowerCase())) ?? [];
382
+ const ok = result.exitCode === 0 && checks !== undefined && failing.length === 0;
383
+ if (opts.json) {
384
+ deps.emit.data({
385
+ ok,
386
+ exit_code: result.exitCode,
387
+ checks: checks ?? null,
388
+ report: json ?? result.stdout,
389
+ });
390
+ }
391
+ else if (checks) {
392
+ emitLine(deps.emit, checks
393
+ .map((c) => `${c.status.padEnd(6)} ${c.name}${c.message ? `: ${c.message}` : ""}`)
394
+ .join("\n"));
395
+ }
396
+ else {
397
+ if (result.stdout)
398
+ emitLine(deps.emit, result.stdout);
399
+ emitLine(deps.emit, "No structured checks in the driver's health report.");
400
+ }
401
+ if (result.stderr)
402
+ deps.writeStderr(result.stderr);
403
+ if (!ok)
404
+ deps.emit.setExitCode(1);
405
+ }));
406
+ // ---------------------------------------------------------- tools/describe
407
+ computer
408
+ .command("tools")
409
+ .description("List the driver's tools (name: description).")
410
+ .action(guarded(input, (deps) => {
411
+ const host = requireSupported(deps.host());
412
+ requireInstalled(deps, host);
413
+ printDriverResult(deps, deps.driver.listTools(host));
414
+ }));
415
+ computer
416
+ .command("describe <tool>")
417
+ .description("Show one tool's description and input schema.")
418
+ .action(guarded(input, (deps, tool) => {
419
+ const host = requireSupported(deps.host());
420
+ requireInstalled(deps, host);
421
+ printDriverResult(deps, deps.driver.describe(host, tool));
422
+ }));
423
+ // -------------------------------------------------------------------- call
424
+ computer
425
+ .command("call <tool> [json]")
426
+ .description("Call one driver tool with a JSON object of arguments, holding the desktop lease. " +
427
+ "`session` defaults to your agent name; inline base64 screenshots are redacted unless --keep-screenshot.")
428
+ .option("--wait <seconds>", "How long to wait for another agent's lease (default: config wait_seconds or 30).")
429
+ .option("--keep-screenshot", "Print inline base64 screenshots instead of redacting them.")
430
+ .action(guarded(input, async (deps, tool, rawJson, opts) => {
431
+ requireEnabled(deps);
432
+ const host = requireSupported(deps.host());
433
+ requireInstalled(deps, host);
434
+ const args = parseCallArgs(rawJson);
435
+ const waitSeconds = parseSeconds(opts.wait, "--wait") ??
436
+ deps.config().wait_seconds ??
437
+ DEFAULT_CALL_WAIT_SECONDS;
438
+ const owner = deps.owner();
439
+ if (!("session" in args))
440
+ args.session = owner.label;
441
+ const acquired = await deps.lease.acquire(owner, leaseOptions(deps, host, Math.round(waitSeconds * 1000)));
442
+ if (!acquired.ok)
443
+ throw heldError(deps, acquired.holder);
444
+ await ensureDaemon(deps, host);
445
+ const translated = deps.driver.translateArgsPaths(host, tool, args);
446
+ const result = deps.driver.call(host, tool, translated);
447
+ let stdout = deps.driver.translateResultPaths(host, result.stdout);
448
+ if (!opts.keepScreenshot)
449
+ stdout = redactInlineScreenshots(stdout);
450
+ printDriverResult(deps, result, stdout);
451
+ }));
452
+ // ------------------------------------------------------------------- lease
453
+ const lease = computer
454
+ .command("lease")
455
+ .description("Inspect, take, or release the desktop lease shared by every agent on this machine.");
456
+ lease
457
+ .command("status")
458
+ .description("Show the current desktop lease holder, or that the desktop is free.")
459
+ .option("--json", "Emit the lease as JSON (null when free).")
460
+ .action(guarded(input, (deps, opts) => {
461
+ const host = requireSupported(deps.host());
462
+ const current = deps.lease.read(leaseOptions(deps, host));
463
+ const nowMs = deps.now();
464
+ if (opts.json) {
465
+ deps.emit.data({
466
+ lease: current
467
+ ? { ...current, expires_in_seconds: expiresInSeconds(current, nowMs) }
468
+ : null,
469
+ });
470
+ return;
471
+ }
472
+ emitLine(deps.emit, current ? `held by ${describeLease(current, nowMs)}` : "free");
473
+ }));
474
+ lease
475
+ .command("acquire")
476
+ .description("Take (or refresh) the desktop lease for yourself without calling a tool.")
477
+ .option("--wait <seconds>", "How long to wait for another agent's lease (default 0).")
478
+ .action(guarded(input, async (deps, opts) => {
479
+ requireEnabled(deps);
480
+ const host = requireSupported(deps.host());
481
+ const waitSeconds = parseSeconds(opts.wait, "--wait") ?? 0;
482
+ const result = await deps.lease.acquire(deps.owner(), leaseOptions(deps, host, Math.round(waitSeconds * 1000)));
483
+ if (!result.ok)
484
+ throw heldError(deps, result.holder);
485
+ emitLine(deps.emit, `${result.fresh ? "Acquired" : "Refreshed"} the desktop lease: ${describeLease(result.lease, deps.now())}.`);
486
+ }));
487
+ lease
488
+ .command("release")
489
+ .description("Release the desktop lease if you hold it.")
490
+ .action(guarded(input, (deps) => {
491
+ const host = requireSupported(deps.host());
492
+ const owner = deps.owner();
493
+ const released = deps.lease.release(owner, leaseOptions(deps, host));
494
+ if (released) {
495
+ emitLine(deps.emit, `Released the desktop lease held by ${owner.label}.`);
496
+ return;
497
+ }
498
+ const current = deps.lease.read(leaseOptions(deps, host));
499
+ if (!current) {
500
+ emitLine(deps.emit, "The desktop was not leased.");
501
+ return;
502
+ }
503
+ throw new ComputerError("computer_lease_held", `Not released: the desktop is held by ${describeLease(current, deps.now())}, not ${owner.label}.`, EXIT_LEASE_HELD);
504
+ }));
505
+ return computer;
506
+ }
507
+ export function registerComputerCommand(program, emit, binName) {
508
+ createComputerCommand({ emit, binName }, program);
509
+ }
@@ -277,7 +277,9 @@ async function handleLint(opts) {
277
277
  error_count: errors.length,
278
278
  warning_count: warnings.length,
279
279
  cold_handoffs: cold,
280
- violations,
280
+ // Errors first: a reader (or a truncated pipe) sees what blocks before
281
+ // thousands of advisory warnings.
282
+ violations: [...errors, ...warnings],
281
283
  });
282
284
  if (errors.length > 0)
283
285
  emit.setExitCode(1);
@@ -56,6 +56,17 @@ async function runFetch(url, opts, emit, context) {
56
56
  }
57
57
  headers[h.slice(0, idx).trim()] = h.slice(idx + 1).trim();
58
58
  }
59
+ // Caller headers only: the User-Agent above is ours, not the caller's.
60
+ if (context?.headerPolicy) {
61
+ const host = new URL(url).hostname;
62
+ for (const h of opts.header ?? []) {
63
+ const idx = h.indexOf(":");
64
+ const name = h.slice(0, idx).trim();
65
+ const verdict = context.headerPolicy(host, name, h.slice(idx + 1).trim());
66
+ if (!verdict.allow)
67
+ throw new Error(verdict.message);
68
+ }
69
+ }
59
70
  const jar = opts.cookies !== false
60
71
  ? new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-fetch" })
61
72
  : null;
@@ -74,7 +85,6 @@ async function runFetch(url, opts, emit, context) {
74
85
  signal: ac.signal,
75
86
  extraHeaders: context?.extraHeaders,
76
87
  extraCookies: context?.extraCookies,
77
- clientRedirects: context?.clientRedirects?.() || undefined,
78
88
  responseType: "bytes",
79
89
  });
80
90
  }
@@ -1,6 +1,55 @@
1
1
  import type { Command } from "commander";
2
2
  import type { EmitContext, HarneryProgramContext } from "../commander.js";
3
3
  import { type SearchEngine } from "../lib/tools/ripgrep.js";
4
+ /**
5
+ * `grep`: monorepo-aware code search. Prefers ripgrep (`rg`) when it is on
6
+ * PATH and falls back to GNU/BSD grep transparently — both engines are driven
7
+ * with equivalent flags and their output is parsed into the same envelope, so
8
+ * results are identical (pinned by tests/unit/grep-engine.test.ts).
9
+ * Smart default excludes (skip dist/.next/node_modules/.git/...), repo
10
+ * scoping (`--repo <name>` or `--all-repos`), language presets, file-level
11
+ * boolean composition (`--and` / `--without`), and context (`-C`/`-A`/`-B`).
12
+ *
13
+ * Engine selection: `HARNERY_GREP_ENGINE=rg|grep` forces one; otherwise `rg`
14
+ * is resolved (managed install, PATH, or opt-in auto-provision; see
15
+ * resolveEngine) and used when available. Repos are searched in parallel, and
16
+ * in `--all-repos` mode the parent scan prunes submodule directories so each
17
+ * match is attributed to exactly one repo.
18
+ *
19
+ * Output framing: content searches request the NUL filename delimiter
20
+ * (`--null` on both engines — the long spelling, because BSD grep repurposes
21
+ * `-Z` for decompression) so filename boundaries are never inferred from
22
+ * punctuation. Context lines are NOT requested from the engines: matches are
23
+ * selected (and budgeted) first, then context windows are materialized from
24
+ * one file read per selected file, which keeps `--limit` semantics exact and
25
+ * both engines byte-identical.
26
+ *
27
+ * Default behavior matches extended-regex semantics (`grep -E` / ripgrep's
28
+ * default). Use `-F` / `--literal` to pin to literal-string mode. Output is
29
+ * line-oriented `file:line:content` in TTY mode (context rows render
30
+ * grep-style as `file-line-content`); `--json` emits the full GrepResult
31
+ * envelope `{ pattern, mode, engine, and_patterns, without_patterns, repos,
32
+ * total_matches, total_files, truncated, elapsed_ms }`. Matches are sorted
33
+ * (file, then line) for stable output across runs and engines.
34
+ *
35
+ * Output and paging: a search shows every result row unless `--limit <n>`
36
+ * sets a page size, and clips each printed line to DEFAULT_MAX_COLUMNS
37
+ * characters (`--max-columns 0` turns clipping off). 500 columns keeps any
38
+ * hand-written source line and long prose lines while cutting minified or
39
+ * generated ones; on the v2 tune split a 300-column clip lost a claim that
40
+ * 500 kept, at about 8% more tokens than 300. There is
41
+ * no default row bound: on the context bench's v2 tune split, every default
42
+ * from 50 to 300 rows lost answer evidence that the unbounded search found
43
+ * (plan 2026-10-31_context-round-trips, Track D). When rows are left out, a count pass
44
+ * (the same search with `-c`, which prints one short row per file) supplies
45
+ * the totals, and the last line states how many matches and files were left
46
+ * out and the `--page <n>` that continues. Pages are cut from one global order
47
+ * (repo order, then file path, then line), so `--page 2` starts exactly where
48
+ * page 1 stopped, whichever engine ran. A content page is fetched by searching
49
+ * only the files that hold its rows.
50
+ */
51
+ /** Characters kept per printed line unless --max-columns says otherwise. */
52
+ export declare const DEFAULT_MAX_COLUMNS = 500;
4
53
  export type GrepEngine = SearchEngine;
5
54
  export interface GrepOpts {
6
55
  repo?: string;
@@ -18,6 +67,11 @@ export interface GrepOpts {
18
67
  beforeContext?: string;
19
68
  maxCount?: string;
20
69
  limit?: string;
70
+ /** 1-based page of `limit`-sized pages. */
71
+ page?: string;
72
+ /** Lift the default row bound (no paging). */
73
+ /** Characters kept per line; "0" disables clipping. */
74
+ maxColumns?: string;
21
75
  include?: string[];
22
76
  exclude?: string[];
23
77
  and?: string[];
@@ -33,6 +87,8 @@ export interface Match {
33
87
  text: string;
34
88
  /** "match" = primary result row (counts toward totals + --limit); "context" = free -C/-A/-B row. */
35
89
  kind: "match" | "context";
90
+ /** Characters removed from `text` by --max-columns clipping (absent when whole). */
91
+ clipped?: number;
36
92
  }
37
93
  export declare function registerGrepCommand(program: Command, emit: EmitContext, context?: HarneryProgramContext): void;
38
94
  export interface GrepResult {
@@ -47,9 +103,23 @@ export interface GrepResult {
47
103
  matches: Match[];
48
104
  truncated: boolean;
49
105
  }[];
106
+ /** Result rows shown on this page (context rows excluded). */
50
107
  total_matches: number;
108
+ /** Files with at least one shown row. */
51
109
  total_files: number;
110
+ /** More rows exist after this page. */
52
111
  truncated: boolean;
112
+ /** Page size; null without --limit. */
113
+ limit: number | null;
114
+ page: number;
115
+ /** Result rows / files the whole search found, across every page. */
116
+ available_matches: number;
117
+ available_files: number;
118
+ /** Rows / files the search found that this page does not show. */
119
+ omitted_matches: number;
120
+ omitted_files: number;
121
+ /** The --page value that continues, or null on the last page. */
122
+ next_page: number | null;
53
123
  elapsed_ms: number;
54
124
  }
55
125
  /** Exported for tests (not part of the package exports map). */
@@ -77,5 +147,7 @@ export declare class NulDecoder {
77
147
  flush(): Omit<Match, "repo" | "kind">[];
78
148
  private decodeRecord;
79
149
  }
150
+ /** Exported for tests (not part of the package exports map). */
151
+ export declare function renderResult(r: GrepResult, opts: GrepOpts): string;
80
152
  export {};
81
153
  //# sourceMappingURL=grep.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../src/commands/grep.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../src/commands/grep.ts"],"names":[],"mappings":"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"}