@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.
- package/CHANGELOG.md +25 -0
- package/README.md +469 -467
- package/output/native-build/desktop-controller.mjs +1 -1
- package/output/native-build/renderer-extension.js +23 -4
- package/package.json +16 -9
- package/scripts/antigravity-adapter-test.cjs +647 -626
- package/scripts/codex-adapter-test.cjs +162 -127
- package/scripts/collaboration-test.cjs +274 -262
- package/scripts/core-review-test.cjs +68 -0
- package/scripts/delegation-await-test.cjs +76 -0
- package/scripts/jsonl-stdin-test.cjs +40 -0
- package/scripts/kiro-cursor-adapters-test.cjs +124 -100
- package/scripts/native-acp-depth-test.cjs +30 -5
- package/scripts/native-protocol-test.cjs +14 -1
- package/scripts/native-update-apply-test.cjs +269 -215
- package/scripts/native-update.cjs +78 -0
- package/scripts/native-vendor-adapters-test.cjs +196 -154
- package/scripts/salvage-rollout-writes.cjs +72 -0
- package/scripts/send-cancel-race-test.cjs +80 -0
- package/scripts/send-pre-turn-cancel-test.cjs +100 -0
- package/scripts/stuck-turn-test.cjs +6 -1
- package/scripts/zcode-adapter-test.cjs +329 -0
- package/scripts/zcode-live-probe.cjs +66 -0
- package/src/main/adapters/antigravity.js +1428 -1415
- package/src/main/adapters/codex.js +656 -649
- package/src/main/adapters/native-acp-command.js +51 -48
- package/src/main/adapters/native-acp.js +47 -12
- package/src/main/adapters/qoder.js +12 -8
- package/src/main/adapters/zcode.js +921 -10
- package/src/main/harness-adapter/event-normalizer.js +5 -2
- package/src/main/host/collaboration.js +723 -715
- package/src/main/host/jsonl.js +130 -116
- package/src/main/host/runtime.js +30 -14
- package/src/main/native/config.js +9 -9
- package/src/main/native/host.js +2 -0
- package/src/main/native/launcher.js +252 -237
- package/src/main/native/process-utils.js +157 -57
- package/src/main/native/protocol.js +1221 -1177
- package/src/main/native/secure-store.js +2 -0
- package/src/main/native/update-state.js +123 -110
- package/src/main/native/updater.js +460 -394
- package/src/main/workspace/core-review.js +13 -5
- package/src/native-ui/desktop-control/src/renderer-cdp-control-session.ts +358 -358
- package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +3211 -3181
- package/src/native-ui/renderer-extension/src/settings/connections-page.ts +2 -2
package/src/main/host/jsonl.js
CHANGED
|
@@ -1,116 +1,130 @@
|
|
|
1
|
-
const { spawn } = require("node:child_process");
|
|
2
|
-
const { StringDecoder } = require("node:string_decoder");
|
|
3
|
-
const { terminateTree } = require("../native/process-utils");
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* JSONL 进程传输层。
|
|
7
|
-
* - 严格 JSONL 分帧:仅以 \n 切分,剥离行尾 \r(Node readline 会把 U+2028/U+2029
|
|
8
|
-
* 当作换行,不符合 Pi RPC 协议要求,这里按规范自行实现)。
|
|
9
|
-
* - 同时支持两种报文形态:
|
|
10
|
-
* 1. Pi 命令式:{ id, type, ... },响应为 { type: "response", id, success, data|error }
|
|
11
|
-
* 2. JSON-RPC 式:{ jsonrpc, id, method, params }(DSH/ACP),包括 Agent → Client 的请求
|
|
12
|
-
*/
|
|
13
|
-
class JsonlProcess {
|
|
14
|
-
constructor(command, args, options, hooks) {
|
|
15
|
-
this.pending = new Map();
|
|
16
|
-
this.nextId = 1;
|
|
17
|
-
this.hooks = hooks;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
1
|
+
const { spawn } = require("node:child_process");
|
|
2
|
+
const { StringDecoder } = require("node:string_decoder");
|
|
3
|
+
const { terminateTree } = require("../native/process-utils");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* JSONL 进程传输层。
|
|
7
|
+
* - 严格 JSONL 分帧:仅以 \n 切分,剥离行尾 \r(Node readline 会把 U+2028/U+2029
|
|
8
|
+
* 当作换行,不符合 Pi RPC 协议要求,这里按规范自行实现)。
|
|
9
|
+
* - 同时支持两种报文形态:
|
|
10
|
+
* 1. Pi 命令式:{ id, type, ... },响应为 { type: "response", id, success, data|error }
|
|
11
|
+
* 2. JSON-RPC 式:{ jsonrpc, id, method, params }(DSH/ACP),包括 Agent → Client 的请求
|
|
12
|
+
*/
|
|
13
|
+
class JsonlProcess {
|
|
14
|
+
constructor(command, args, options = {}, hooks = {}) {
|
|
15
|
+
this.pending = new Map();
|
|
16
|
+
this.nextId = 1;
|
|
17
|
+
this.hooks = hooks;
|
|
18
|
+
// jsonrpc: false = 纯 {id, method, params} 帧(省略 "jsonrpc" 字段)。
|
|
19
|
+
// ZCode app-server 的 zod 校验把 "jsonrpc" 当 unrecognized key 拒收。
|
|
20
|
+
this.jsonrpc = options.jsonrpc !== false;
|
|
21
|
+
const spawnOptions = { ...options };
|
|
22
|
+
delete spawnOptions.jsonrpc;
|
|
23
|
+
this.child = spawn(command, args, { windowsHide: true, ...spawnOptions, stdio: ["pipe", "pipe", "pipe"] });
|
|
24
|
+
// 子进程异常退出/管道破裂时,迟到的 stdin.write 会在流上异步抛 EPIPE;
|
|
25
|
+
// Writable 无 error 监听会被 Node 当作未捕获异常直接 crash 宿主进程。
|
|
26
|
+
// 真实失败由 exit/error 路径统一结算,这里仅吞掉管道噪声。
|
|
27
|
+
this.child.stdin.on("error", (error) => this.hooks.onDiagnostic?.(`stdin: ${error.message}`));
|
|
28
|
+
this.#attachReader(this.child.stdout, (line) => this.#dispatch(line));
|
|
29
|
+
this.#attachReader(this.child.stderr, (line) => hooks.onDiagnostic?.(line));
|
|
30
|
+
this.child.on("error", (error) => this.#failAll(error));
|
|
31
|
+
this.child.on("exit", (code, signal) => {
|
|
32
|
+
const error = new Error(`Harness 进程已退出 (${code ?? signal ?? "unknown"})`);
|
|
33
|
+
// 确定性失败标记:harness 二进制在应答挂起请求前就退出。调用方用它抑制重试循环。
|
|
34
|
+
error.harnessExited = true;
|
|
35
|
+
this.#failAll(error);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#attachReader(stream, onLine) {
|
|
40
|
+
const decoder = new StringDecoder("utf8");
|
|
41
|
+
let buffer = "";
|
|
42
|
+
stream.on("data", (chunk) => {
|
|
43
|
+
buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);
|
|
44
|
+
let index;
|
|
45
|
+
while ((index = buffer.indexOf("\n")) !== -1) {
|
|
46
|
+
let line = buffer.slice(0, index);
|
|
47
|
+
buffer = buffer.slice(index + 1);
|
|
48
|
+
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
49
|
+
if (line.trim()) onLine(line);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
stream.on("end", () => {
|
|
53
|
+
buffer += decoder.end();
|
|
54
|
+
if (buffer.trim()) onLine(buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#dispatch(line) {
|
|
59
|
+
let value;
|
|
60
|
+
try { value = JSON.parse(line); }
|
|
61
|
+
catch { this.hooks.onDiagnostic?.(`Non-JSON stdout: ${line.slice(0, 500)}`); return; }
|
|
62
|
+
// Agent → Client 请求(JSON-RPC,带 method 和 id),需要回复
|
|
63
|
+
if (value.method !== undefined && value.id !== undefined) {
|
|
64
|
+
Promise.resolve()
|
|
65
|
+
.then(() => {
|
|
66
|
+
if (!this.hooks.onRequest) throw new Error(`Unsupported native client request: ${value.method}`);
|
|
67
|
+
return this.hooks.onRequest(value);
|
|
68
|
+
})
|
|
69
|
+
.then((result) => this.#write(this.jsonrpc ? { jsonrpc: "2.0", id: value.id, result: result ?? {} } : { id: value.id, result: result ?? {} }))
|
|
70
|
+
.catch((error) => this.#write(this.jsonrpc ? { jsonrpc: "2.0", id: value.id, error: { code: -32603, message: error.message } } : { id: value.id, error: { code: -32603, message: error.message } }));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// 命令响应(id 匹配 pending)
|
|
74
|
+
if (value.id !== undefined && this.pending.has(value.id)) {
|
|
75
|
+
const { resolve, reject } = this.pending.get(value.id);
|
|
76
|
+
this.pending.delete(value.id);
|
|
77
|
+
if (value.error || value.success === false) {
|
|
78
|
+
reject(new Error(typeof value.error === "string" ? value.error : value.error?.message || "Harness 请求失败"));
|
|
79
|
+
} else {
|
|
80
|
+
resolve(value.result ?? value.data ?? value);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// 普通事件 / 通知
|
|
85
|
+
this.hooks.onEvent?.(value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#write(payload) {
|
|
89
|
+
if (!this.child.stdin.destroyed) this.child.stdin.write(`${JSON.stringify(payload)}\n`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** JSON-RPC 请求(DSH/ACP);纯帧模式下省略 jsonrpc 字段(ZCode) */
|
|
93
|
+
request(method, params) {
|
|
94
|
+
const id = this.nextId++;
|
|
95
|
+
this.#write(this.jsonrpc ? { jsonrpc: "2.0", id, method, params } : { id, method, params });
|
|
96
|
+
return new Promise((resolve, reject) => this.pending.set(id, { resolve, reject }));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** JSON-RPC 通知(无响应) */
|
|
100
|
+
notify(method, params) { this.#write(this.jsonrpc ? { jsonrpc: "2.0", method, params } : { method, params }); }
|
|
101
|
+
|
|
102
|
+
/** Pi 命令式请求 */
|
|
103
|
+
command(payload) {
|
|
104
|
+
const id = String(this.nextId++);
|
|
105
|
+
this.#write({ id, ...payload });
|
|
106
|
+
return new Promise((resolve, reject) => this.pending.set(id, { resolve, reject }));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 回复 Agent → Client 请求之外的自由格式报文(如 Pi extension_ui_response) */
|
|
110
|
+
send(payload) { this.#write(payload); }
|
|
111
|
+
|
|
112
|
+
stop() { void terminateTree(this.child.pid); }
|
|
113
|
+
|
|
114
|
+
#failAll(error) {
|
|
115
|
+
for (const { reject } of this.pending.values()) reject(error);
|
|
116
|
+
this.pending.clear();
|
|
117
|
+
this.hooks.onExit?.(error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 跨平台 CLI 启动:Windows 上 .cmd shim 需要经 cmd.exe 执行 */
|
|
122
|
+
function cliSpawn(bin, args) {
|
|
123
|
+
if (process.platform === "win32") {
|
|
124
|
+
const safe = [`${bin}.cmd`, ...args.map(String)].map((a) => (/[&|<>^%"]/.test(a) ? `"${a.replace(/["&|<>^%]/g, "")}"` : a.includes(" ") ? `"${a}"` : a));
|
|
125
|
+
return { command: "cmd.exe", args: ["/d", "/s", "/c", safe.join(" ")] };
|
|
126
|
+
}
|
|
127
|
+
return { command: bin, args: args.map(String) };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { JsonlProcess, cliSpawn };
|
package/src/main/host/runtime.js
CHANGED
|
@@ -345,8 +345,9 @@ class HostRuntime {
|
|
|
345
345
|
async #send(threadId, text, { commandId, attachments, delegateOf, collaborationOf, isolated, turnPermissions, ticket }) {
|
|
346
346
|
const thread = this.#requireThread(threadId);
|
|
347
347
|
if (this.execution.isRunning(thread.id)) throw new Error("任务正在执行,请先停止或等待完成");
|
|
348
|
-
//
|
|
349
|
-
|
|
348
|
+
// 不得在此按票据清理 cancelRequests:登记可能属于仍停留在 Turn 启动前阶段(会话
|
|
349
|
+
// 打开/prompt 组装)的在途旧 send——删掉会让旧 send 错过下方的取消结算而继续投递,
|
|
350
|
+
// 与新发送双双进入原生会话。陈旧登记由本次 send 的 finally(票据匹配时)回收。
|
|
350
351
|
const prepared = this.#prepareAttachments(thread, attachments);
|
|
351
352
|
const typed = typeof text === "string" ? text.trim() : "";
|
|
352
353
|
if (!typed && !prepared.images.length && !prepared.texts.length) throw new Error("请输入消息");
|
|
@@ -447,12 +448,19 @@ class HostRuntime {
|
|
|
447
448
|
thread.updatedAt = Date.now();
|
|
448
449
|
delete thread.error;
|
|
449
450
|
delete thread.errorKind;
|
|
450
|
-
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
451
|
+
// 会话打开/prompt 组装期间用户已按停止(cancel 时 Turn 尚未开始,原生侧无从 abort):
|
|
452
|
+
// 本代发送直接结算取消、不再投递。若取消后用户已重发且新回合已在运行,旧 send 不得
|
|
453
|
+
// 再 turnStarted——那会把新回合挤出 lastTurn,使新发送在投递前自查时误判空闲而丢消息;
|
|
454
|
+
// 此时静默退出,把投影权交给新回合。
|
|
455
|
+
const cancelledBeforeTurn = this.cancelRequests.get(thread.id) === ticket;
|
|
456
|
+
if (cancelledBeforeTurn) this.cancelRequests.delete(thread.id);
|
|
457
|
+
if (cancelledBeforeTurn && this.execution.isRunning(thread.id)) {
|
|
458
|
+
await this.#save();
|
|
459
|
+
this.#broadcast();
|
|
460
|
+
return;
|
|
455
461
|
}
|
|
462
|
+
const turn = this.execution.turnStarted(thread, displayPrompt);
|
|
463
|
+
if (cancelledBeforeTurn) this.#applyEvent({ threadId, event: { kind: 'completed', stopReason: 'cancelled' } });
|
|
456
464
|
this.turnActivity.set(thread.id, Date.now());
|
|
457
465
|
const message = thread.messages.at(-1);
|
|
458
466
|
if (hasConcurrentTurn) message.concurrent = true;
|
|
@@ -460,8 +468,12 @@ class HostRuntime {
|
|
|
460
468
|
this.#syncCore(thread);
|
|
461
469
|
try { if (!collaborationOf || isolated) message.reviewId = await this.reviews.begin(thread.cwd); else message.reviewOwnerThreadId = collaborationOf; }
|
|
462
470
|
catch (e) { message.reviewError = '本轮未建立文件快照:' + e.message; }
|
|
463
|
-
|
|
464
|
-
|
|
471
|
+
// 本代回合已结算(含上方的取消结算)时退出。注意 isRunning 反映的是最新回合:
|
|
472
|
+
// 取消后用户重发的新回合一旦启动,这里会被重新置真——必须再按回合身份核验,
|
|
473
|
+
// 否则被取消的旧发送会把 review 快照与后续 prompt 投递错误地挂到新回合上。
|
|
474
|
+
const superseded = this.execution.lastTurn(thread.id)?.id !== turn.id;
|
|
475
|
+
if (!this.execution.isRunning(thread.id) || superseded) {
|
|
476
|
+
if (message.reviewId && !superseded) await this.#settleReview(thread, message);
|
|
465
477
|
return;
|
|
466
478
|
}
|
|
467
479
|
this.startReviewUpdates(thread, message);
|
|
@@ -477,14 +489,16 @@ class HostRuntime {
|
|
|
477
489
|
await this.#save();
|
|
478
490
|
this.#broadcast();
|
|
479
491
|
}
|
|
480
|
-
// 投递前最后检查:Turn 可能在 review 快照/保存期间被取消(cancel
|
|
492
|
+
// 投递前最后检查:Turn 可能在 review 快照/保存期间被取消(cancel 已结算),
|
|
493
|
+
// 或已被取消后重发的新回合取代(lastTurn 易主时 isRunning 仍为真)。
|
|
481
494
|
// 此时再投递,先到的 abort 会在原生侧落空,形成用户看不见的僵尸运行
|
|
482
|
-
|
|
495
|
+
const outdated = this.execution.lastTurn(thread.id)?.id !== turn.id;
|
|
496
|
+
if (!this.execution.isRunning(thread.id) || outdated) {
|
|
483
497
|
if (handoff?.checkpointId) {
|
|
484
498
|
handoff.phase = 'failed';
|
|
485
499
|
await this.handoffs.mark(thread.id, handoff.checkpointId, 'failed').catch(() => {});
|
|
486
500
|
}
|
|
487
|
-
if (message.reviewId) await this.#settleReview(thread, message);
|
|
501
|
+
if (message.reviewId && !outdated) await this.#settleReview(thread, message);
|
|
488
502
|
return;
|
|
489
503
|
}
|
|
490
504
|
await session.adapter.send(session, promptText, hooks, { images: prepared.images, turnPermissions });
|
|
@@ -498,8 +512,10 @@ class HostRuntime {
|
|
|
498
512
|
handoff.phase = 'failed';
|
|
499
513
|
await this.handoffs.mark(thread.id, handoff.checkpointId, 'failed').catch(() => {});
|
|
500
514
|
}
|
|
501
|
-
// 用户取消造成的 reject 已由 cancel()
|
|
502
|
-
|
|
515
|
+
// 用户取消造成的 reject 已由 cancel() 结算,不再标错。若回合已易主(取消后重发 /
|
|
516
|
+
// 外部转向启动了新回合),旧发送迟到的 reject 不得击中正在运行的新回合——
|
|
517
|
+
// 仅当本代回合仍是 lastTurn 时才把错误投到它上面。
|
|
518
|
+
if (this.execution.isRunning(thread.id) && this.execution.lastTurn(thread.id)?.id === turn?.id) this.#applyEvent({ threadId, event: { kind: "error", message: error.message } });
|
|
503
519
|
await this.#save().catch(() => {});
|
|
504
520
|
this.#broadcast();
|
|
505
521
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
const fs = require('node:fs');
|
|
2
2
|
const path = require('node:path');
|
|
3
|
-
const { dataDirectory, executableName } = require('./platform');
|
|
3
|
+
const { dataDirectory, executableName } = require('./platform');
|
|
4
4
|
const root = path.resolve(__dirname, '../../..');
|
|
5
|
-
const settingKeys = ['HARNESS_MIX_DSH_ROOT', 'HARNESSMIX_PI_COMMAND', 'HARNESSMIX_CLAUDE_COMMAND', 'HARNESSMIX_DEEPSEEK_HARNESS_COMMAND', 'HARNESSMIX_ANTIGRAVITY_COMMAND', 'HARNESS_MIX_CODEBUDDY_EXECUTABLE', 'HARNESS_MIX_WORKBUDDY_EXECUTABLE', 'HARNESS_MIX_KIRO_EXECUTABLE', 'HARNESS_MIX_CURSOR_EXECUTABLE', 'HARNESS_MIX_QODER_EXECUTABLE', '
|
|
5
|
+
const settingKeys = ['HARNESS_MIX_DSH_ROOT', 'HARNESSMIX_PI_COMMAND', 'HARNESSMIX_CLAUDE_COMMAND', 'HARNESSMIX_DEEPSEEK_HARNESS_COMMAND', 'HARNESSMIX_ANTIGRAVITY_COMMAND', 'HARNESS_MIX_CODEBUDDY_EXECUTABLE', 'HARNESS_MIX_WORKBUDDY_EXECUTABLE', 'HARNESS_MIX_KIRO_EXECUTABLE', 'HARNESS_MIX_CURSOR_EXECUTABLE', 'HARNESS_MIX_QODER_EXECUTABLE', 'HARNESS_MIX_ZCODE_EXECUTABLE', 'HARNESS_MIX_TRAE_EXECUTABLE'];
|
|
6
6
|
|
|
7
|
-
function nativePaths(platform = process.platform) {
|
|
7
|
+
function nativePaths(platform = process.platform) {
|
|
8
8
|
const build = path.join(root, 'output/native-build');
|
|
9
9
|
return {
|
|
10
10
|
cli: path.join(root, 'scripts/launch-codex.cjs'),
|
|
11
|
-
shim: path.join(build, executableName('harness-mix-shim', platform)),
|
|
12
|
-
...(platform === 'win32' ? {
|
|
13
|
-
activation: path.join(build, 'harness-mix-appx.exe'),
|
|
14
|
-
secret: path.join(build, 'harness-mix-secret.exe'),
|
|
15
|
-
} : {}),
|
|
11
|
+
shim: path.join(build, executableName('harness-mix-shim', platform)),
|
|
12
|
+
...(platform === 'win32' ? {
|
|
13
|
+
activation: path.join(build, 'harness-mix-appx.exe'),
|
|
14
|
+
secret: path.join(build, 'harness-mix-secret.exe'),
|
|
15
|
+
} : {}),
|
|
16
16
|
runtime: path.join(root, 'src/main/native/host.js'),
|
|
17
17
|
controller: path.join(build, 'desktop-controller.mjs'),
|
|
18
18
|
renderer: path.join(build, 'renderer-extension.js'),
|
|
@@ -22,7 +22,7 @@ function nativePaths(platform = process.platform) {
|
|
|
22
22
|
|
|
23
23
|
function nativeEnvironment(environment = process.env) {
|
|
24
24
|
const env = { ...environment };
|
|
25
|
-
env.HARNESSMIX_DATA_DIR = dataDirectory(env);
|
|
25
|
+
env.HARNESSMIX_DATA_DIR = dataDirectory(env);
|
|
26
26
|
const settingsPath = path.join(env.HARNESSMIX_DATA_DIR, 'harness-mix-settings.json');
|
|
27
27
|
if (fs.existsSync(settingsPath)) {
|
|
28
28
|
const saved = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
package/src/main/native/host.js
CHANGED
|
@@ -83,6 +83,8 @@ async function runNativeHost() {
|
|
|
83
83
|
const passthrough = process.argv.slice(2);
|
|
84
84
|
const officialArgs = passthrough.includes('--listen') ? passthrough : [...passthrough, '--listen', 'stdio://'];
|
|
85
85
|
const official = spawn(stock, officialArgs, { env, windowsHide: true, stdio: ['pipe', 'pipe', 'inherit'] });
|
|
86
|
+
// official 异常退出后迟到的 stdin.write 会异步抛 EPIPE,无监听即 crash 宿主;退出由 close() 路径处理
|
|
87
|
+
official.stdin.on('error', () => {});
|
|
86
88
|
const forwarded = new Map();
|
|
87
89
|
const lines = readline.createInterface({ input: official.stdout });
|
|
88
90
|
lines.on('line', line => {
|