@harness-mix/cli 0.2.2 → 0.2.4

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 (45) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +469 -467
  3. package/output/native-build/desktop-controller.mjs +1 -1
  4. package/output/native-build/renderer-extension.js +23 -4
  5. package/package.json +16 -9
  6. package/scripts/antigravity-adapter-test.cjs +647 -626
  7. package/scripts/codex-adapter-test.cjs +162 -127
  8. package/scripts/collaboration-test.cjs +274 -262
  9. package/scripts/core-review-test.cjs +68 -0
  10. package/scripts/delegation-await-test.cjs +76 -0
  11. package/scripts/jsonl-stdin-test.cjs +40 -0
  12. package/scripts/kiro-cursor-adapters-test.cjs +124 -100
  13. package/scripts/native-acp-depth-test.cjs +30 -5
  14. package/scripts/native-protocol-test.cjs +14 -1
  15. package/scripts/native-update-apply-test.cjs +269 -215
  16. package/scripts/native-update.cjs +78 -0
  17. package/scripts/native-vendor-adapters-test.cjs +196 -154
  18. package/scripts/salvage-rollout-writes.cjs +72 -0
  19. package/scripts/send-cancel-race-test.cjs +80 -0
  20. package/scripts/send-pre-turn-cancel-test.cjs +100 -0
  21. package/scripts/stuck-turn-test.cjs +6 -1
  22. package/scripts/zcode-adapter-test.cjs +329 -0
  23. package/scripts/zcode-live-probe.cjs +66 -0
  24. package/src/main/adapters/antigravity.js +1428 -1415
  25. package/src/main/adapters/codex.js +656 -649
  26. package/src/main/adapters/native-acp-command.js +51 -48
  27. package/src/main/adapters/native-acp.js +47 -12
  28. package/src/main/adapters/qoder.js +12 -8
  29. package/src/main/adapters/zcode.js +921 -10
  30. package/src/main/harness-adapter/event-normalizer.js +5 -2
  31. package/src/main/host/collaboration.js +723 -715
  32. package/src/main/host/jsonl.js +130 -116
  33. package/src/main/host/runtime.js +30 -14
  34. package/src/main/native/config.js +9 -9
  35. package/src/main/native/host.js +2 -0
  36. package/src/main/native/launcher.js +252 -237
  37. package/src/main/native/process-utils.js +157 -57
  38. package/src/main/native/protocol.js +1221 -1177
  39. package/src/main/native/secure-store.js +2 -0
  40. package/src/main/native/update-state.js +123 -110
  41. package/src/main/native/updater.js +460 -394
  42. package/src/main/workspace/core-review.js +13 -5
  43. package/src/native-ui/desktop-control/src/renderer-cdp-control-session.ts +358 -358
  44. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +3211 -3181
  45. package/src/native-ui/renderer-extension/src/settings/connections-page.ts +2 -2
@@ -1,57 +1,157 @@
1
- // Child-process lifecycle helpers shared by adapters, host runtime and launcher.
2
- // On Windows a plain child.kill() only terminates the direct child, so CLI
3
- // wrappers (cmd → npm → node) need taskkill /T to reach the whole tree.
4
- const { spawn, execFile } = require('node:child_process');
5
-
6
- function descendantPids(rows, root) {
7
- const children = new Map();
8
- for (const row of rows.split('\n')) {
9
- const match = row.trim().match(/^(\d+)\s+(\d+)$/);
10
- if (!match) continue;
11
- const pid = Number(match[1]), parent = Number(match[2]);
12
- if (!children.has(parent)) children.set(parent, []);
13
- children.get(parent).push(pid);
14
- }
15
- const result = [], visited = new Set([root]);
16
- function visit(parent) {
17
- for (const pid of children.get(parent) || []) {
18
- if (visited.has(pid) || pid === process.pid) continue;
19
- visited.add(pid);
20
- visit(pid);
21
- result.push(pid);
22
- }
23
- }
24
- visit(root);
25
- return result;
26
- }
27
-
28
- // Force-terminate a process tree. Resolves once the attempt finished; never
29
- // throws and never rejects, so callers can fire-and-forget with `void`.
30
- function terminateTree(pid, { timeoutMs = 5000 } = {}) {
31
- if (!Number.isInteger(pid) || pid <= 1 || pid === process.pid) return Promise.resolve();
32
- return new Promise(resolve => {
33
- if (process.platform !== 'win32') {
34
- execFile('ps', ['-ax', '-o', 'pid=', '-o', 'ppid='], { encoding: 'utf8', timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (error, rows) => {
35
- const descendants = error ? [] : descendantPids(rows, pid);
36
- for (const target of [...descendants, pid]) {
37
- try { process.kill(target, 'SIGKILL'); } catch { /* already gone */ }
38
- }
39
- resolve();
40
- });
41
- return;
42
- }
43
- let settled = false;
44
- const killer = spawn('taskkill.exe', ['/PID', String(pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
45
- const timer = setTimeout(() => { try { killer.kill(); } catch { /* ignore */ } done(); }, timeoutMs);
46
- function done() {
47
- if (settled) return;
48
- settled = true;
49
- clearTimeout(timer);
50
- resolve();
51
- }
52
- killer.on('error', done);
53
- killer.on('close', done);
54
- });
55
- }
56
-
57
- module.exports = { terminateTree, descendantPids };
1
+ // Child-process lifecycle helpers shared by adapters, host runtime and launcher.
2
+ // On Windows a plain child.kill() only terminates the direct child, so CLI
3
+ // wrappers (cmd → npm → node) need taskkill /T to reach the whole tree.
4
+ const { spawn, execFile } = require('node:child_process');
5
+
6
+ function descendantPids(rows, root) {
7
+ const children = new Map();
8
+ for (const row of rows.split('\n')) {
9
+ const match = row.trim().match(/^(\d+)\s+(\d+)$/);
10
+ if (!match) continue;
11
+ const pid = Number(match[1]), parent = Number(match[2]);
12
+ if (!children.has(parent)) children.set(parent, []);
13
+ children.get(parent).push(pid);
14
+ }
15
+ const result = [], visited = new Set([root]);
16
+ function visit(parent) {
17
+ for (const pid of children.get(parent) || []) {
18
+ if (visited.has(pid) || pid === process.pid) continue;
19
+ visited.add(pid);
20
+ visit(pid);
21
+ result.push(pid);
22
+ }
23
+ }
24
+ visit(root);
25
+ return result;
26
+ }
27
+
28
+ // Force-terminate a process tree. Resolves once the attempt finished; never
29
+ // throws and never rejects, so callers can fire-and-forget with `void`.
30
+ function terminateTree(pid, { timeoutMs = 5000 } = {}) {
31
+ if (!Number.isInteger(pid) || pid <= 1 || pid === process.pid) return Promise.resolve();
32
+ return new Promise(resolve => {
33
+ if (process.platform !== 'win32') {
34
+ execFile('ps', ['-ax', '-o', 'pid=', '-o', 'ppid='], { encoding: 'utf8', timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (error, rows) => {
35
+ const descendants = error ? [] : descendantPids(rows, pid);
36
+ for (const target of [...descendants, pid]) {
37
+ try { process.kill(target, 'SIGKILL'); } catch { /* already gone */ }
38
+ }
39
+ resolve();
40
+ });
41
+ return;
42
+ }
43
+ let settled = false;
44
+ const killer = spawn('taskkill.exe', ['/PID', String(pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
45
+ const timer = setTimeout(() => { try { killer.kill(); } catch { /* ignore */ } done(); }, timeoutMs);
46
+ function done() {
47
+ if (settled) return;
48
+ settled = true;
49
+ clearTimeout(timer);
50
+ resolve();
51
+ }
52
+ killer.on('error', done);
53
+ killer.on('close', done);
54
+ });
55
+ }
56
+
57
+ // Windows console children never inherit the WinINET system proxy: Go CLIs
58
+ // (agy, gh, …) only honor HTTP(S)_PROXY env vars, so when harness-mix spawns
59
+ // them from a desktop host without proxy env, their OAuth token refresh and
60
+ // API traffic black-hole even though the system proxy is up — for agy this
61
+ // re-triggers interactive login on every start. Expose the WinINET settings as
62
+ // standard proxy env vars; callers merge this into the child env. Explicit
63
+ // proxy env on the host always wins.
64
+ const WININET_INTERNET_SETTINGS_KEY =
65
+ 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings';
66
+ const SYSTEM_PROXY_ENV_TTL_MS = 5 * 60 * 1000;
67
+ let systemProxyEnvCache = { env: null, at: 0 };
68
+
69
+ function parseWininetProxyTarget(raw) {
70
+ const text = String(raw || '').trim();
71
+ if (!text) return null;
72
+ if (!text.includes('=')) return { http: text, https: text };
73
+ const perScheme = {};
74
+ for (const part of text.split(';')) {
75
+ const eq = part.indexOf('=');
76
+ if (eq <= 0) continue;
77
+ const scheme = part.slice(0, eq).trim().toLowerCase();
78
+ const target = part.slice(eq + 1).trim();
79
+ if (!target) continue;
80
+ if (scheme === 'http' || scheme === 'https' || scheme === 'socks') perScheme[scheme] = target;
81
+ }
82
+ const socks = perScheme.socks ? `socks5://${perScheme.socks.replace(/^socks5:\/\//i, '')}` : null;
83
+ const http = perScheme.http || socks || null;
84
+ const https = perScheme.https || socks || perScheme.http || null;
85
+ return http || https ? { http, https } : null;
86
+ }
87
+
88
+ function withProxyScheme(target) {
89
+ const text = String(target || '').trim();
90
+ if (!text || /^[a-z][a-z0-9+.-]*:\/\//i.test(text)) return text;
91
+ return `http://${text}`;
92
+ }
93
+
94
+ // WinINET's wildcard override list (`127.*`, `*.corp`, `<local>`) is not valid
95
+ // NO_PROXY syntax for Go/curl; map the common shapes and pass the rest through.
96
+ function parseWininetProxyOverride(raw) {
97
+ const entries = String(raw || '').split(';').map((entry) => entry.trim().toLowerCase()).filter(Boolean);
98
+ if (!entries.length) return null;
99
+ const out = ['localhost', '127.0.0.1', '::1'];
100
+ for (const entry of entries) {
101
+ if (entry === '<local>' || entry === 'localhost' || entry === '127.*') continue;
102
+ if (entry === '10.*') out.push('10.0.0.0/8');
103
+ else if (entry === '192.168.*') out.push('192.168.0.0/16');
104
+ else if (/^172\.(?:1[6-9]|2\d|3[01])\.\*$/.test(entry)) out.push('172.16.0.0/12');
105
+ else if (entry.startsWith('*.')) out.push(entry.slice(1));
106
+ else out.push(entry);
107
+ }
108
+ return [...new Set(out)].join(',');
109
+ }
110
+
111
+ function readWininetProxySettings() {
112
+ return new Promise((resolve) => {
113
+ execFile(
114
+ 'reg.exe',
115
+ ['query', WININET_INTERNET_SETTINGS_KEY],
116
+ { encoding: 'utf8', windowsHide: true, timeout: 5000, maxBuffer: 1024 * 1024 },
117
+ (error, stdout) => {
118
+ if (error || !stdout) return resolve(null);
119
+ const values = {};
120
+ for (const line of String(stdout).split(/\r?\n/)) {
121
+ const match = line.match(/^\s*(ProxyEnable|ProxyServer|ProxyOverride)\s+\S+\s+(.*?)\s*$/);
122
+ if (match) values[match[1]] = match[2];
123
+ }
124
+ resolve(values);
125
+ },
126
+ );
127
+ });
128
+ }
129
+
130
+ function hasExplicitProxyEnv(env) {
131
+ return Boolean(env.HTTP_PROXY || env.http_proxy || env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy);
132
+ }
133
+
134
+ async function systemProxyEnv() {
135
+ if (process.platform !== 'win32') return {};
136
+ if (hasExplicitProxyEnv(process.env)) return {};
137
+ const now = Date.now();
138
+ if (systemProxyEnvCache.env && now - systemProxyEnvCache.at < SYSTEM_PROXY_ENV_TTL_MS) {
139
+ return systemProxyEnvCache.env;
140
+ }
141
+ const values = await readWininetProxySettings();
142
+ let resolved = {};
143
+ if (values && String(values.ProxyEnable || '').trim() === '0x1') {
144
+ const targets = parseWininetProxyTarget(values.ProxyServer);
145
+ if (targets) {
146
+ resolved = {};
147
+ if (targets.http) resolved.HTTP_PROXY = resolved.http_proxy = withProxyScheme(targets.http);
148
+ if (targets.https) resolved.HTTPS_PROXY = resolved.https_proxy = withProxyScheme(targets.https);
149
+ const noProxy = parseWininetProxyOverride(values.ProxyOverride);
150
+ if (noProxy) resolved.NO_PROXY = resolved.no_proxy = noProxy;
151
+ }
152
+ }
153
+ systemProxyEnvCache = { env: resolved, at: now };
154
+ return resolved;
155
+ }
156
+
157
+ module.exports = { terminateTree, descendantPids, systemProxyEnv, parseWininetProxyTarget, parseWininetProxyOverride, withProxyScheme };