arona-agent 1.2.1 → 1.2.2
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/gui/main.cjs +39 -5
- package/package.json +1 -1
- package/src/gui/index.ts +31 -6
- package/src/gui/protocol.ts +2 -0
package/gui/main.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// ARONA GUI Electron 主进程:单窗口,与 Node 后端父进程经 stdin/stdout JSON lines 通信
|
|
2
2
|
// (协议行前缀 ###GUI### 过滤 Electron 日志;与桌宠桥同模式)。
|
|
3
3
|
const { app, BrowserWindow, Menu, ipcMain, nativeImage } = require("electron");
|
|
4
|
+
const crypto = require("crypto");
|
|
4
5
|
const fs = require("fs");
|
|
6
|
+
const http = require("http");
|
|
5
7
|
const path = require("path");
|
|
6
8
|
|
|
7
9
|
// Windows:GUI 是纯 HTML/CSS(无 WebGL),默认硬件加速下页面加载/脚本均正常但不 paint(白屏,
|
|
@@ -290,7 +292,8 @@ ipcMain.on("gui-send", (_event, msg) => {
|
|
|
290
292
|
send(msg);
|
|
291
293
|
});
|
|
292
294
|
|
|
293
|
-
// backend → renderer
|
|
295
|
+
// backend → renderer(行缓冲解析;Windows 下此路不通——GUI 子系统 Electron 的 stdin 数据不可达,
|
|
296
|
+
// 见下方 HTTP 通道。保留作非 Windows 平台的备用通道)
|
|
294
297
|
let buffer = "";
|
|
295
298
|
process.stdin.on("data", (data) => {
|
|
296
299
|
buffer += data.toString();
|
|
@@ -307,10 +310,41 @@ process.stdin.on("data", (data) => {
|
|
|
307
310
|
}
|
|
308
311
|
});
|
|
309
312
|
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
// backend
|
|
313
|
-
|
|
313
|
+
// 本地 HTTP 通道(backend → GUI 主方向):Windows 下 Electron 子进程 stdin 完全不可达(hello 握手
|
|
314
|
+
// 证明进程与 stdout 均正常、数据仍不到达),事件改走 127.0.0.1 随机端口 + 随机 token(随 hello 行
|
|
315
|
+
// 告知 backend,防本机其他进程伪造)。stdout(GUI → backend)方向不受影响,继续走 ###GUI### 行。
|
|
316
|
+
const HTTP_TOKEN = crypto.randomBytes(16).toString("hex");
|
|
317
|
+
let helloSent = false;
|
|
318
|
+
function sendHello(httpPort) {
|
|
319
|
+
if (helloSent) return;
|
|
320
|
+
helloSent = true;
|
|
321
|
+
send({ type: "hello", httpPort: httpPort || 0, token: httpPort ? HTTP_TOKEN : "" });
|
|
322
|
+
}
|
|
323
|
+
const httpServer = http.createServer((req, res) => {
|
|
324
|
+
if (req.method !== "POST" || req.headers["x-arona-token"] !== HTTP_TOKEN) {
|
|
325
|
+
res.writeHead(403);
|
|
326
|
+
res.end();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
let body = "";
|
|
330
|
+
req.on("data", (c) => (body += c));
|
|
331
|
+
req.on("end", () => {
|
|
332
|
+
res.writeHead(204);
|
|
333
|
+
res.end();
|
|
334
|
+
try {
|
|
335
|
+
forward(JSON.parse(body));
|
|
336
|
+
} catch {
|
|
337
|
+
// 非 JSON,忽略
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
httpServer.on("error", (e) => {
|
|
342
|
+
console.error("[gui] http server error:", e.message, "→ 退回 stdin 通道");
|
|
343
|
+
sendHello(0); // HTTP 起不来也要发 hello(不带端口,backend 退回 stdin 写入)
|
|
344
|
+
});
|
|
345
|
+
httpServer.listen(0, "127.0.0.1", () => {
|
|
346
|
+
sendHello(httpServer.address().port);
|
|
347
|
+
});
|
|
314
348
|
|
|
315
349
|
// 窗口全关:先发 exit 请求让后端走完整清理,再退出(延迟让协议行先 flush)
|
|
316
350
|
app.on("window-all-closed", () => {
|
package/package.json
CHANGED
package/src/gui/index.ts
CHANGED
|
@@ -21,11 +21,15 @@ class GuiBridge {
|
|
|
21
21
|
private controller: GuiController | null = null;
|
|
22
22
|
private startingMain = false;
|
|
23
23
|
private exited = false;
|
|
24
|
-
// Windows 下 spawn 后立刻写 stdin
|
|
25
|
-
|
|
26
|
-
//
|
|
24
|
+
// Windows 下 spawn 后立刻写 stdin 会丢数据,且实测 GUI 子系统 Electron 的 stdin **完全不可达**
|
|
25
|
+
//(hello 握手证明进程与 stdout 均正常,数据仍不到达)——GUI 进程起 127.0.0.1 随机端口 + 随机 token
|
|
26
|
+
// 的 HTTP 服务,端口/token 随 hello 行告知;hello 前入队,收到后按序经 HTTP 下发。
|
|
27
|
+
// httpPort=0(HTTP 起失败)时退回 stdin 写入(非 Windows 平台 stdin 本来就通)。
|
|
27
28
|
private helloReceived = false;
|
|
28
29
|
private queuedEvents: GuiEvent[] = [];
|
|
30
|
+
private guiHttpPort = 0;
|
|
31
|
+
private guiHttpToken = "";
|
|
32
|
+
private httpChain: Promise<void> = Promise.resolve(); // 串行链保序(HTTP 异步,并发会乱序)
|
|
29
33
|
|
|
30
34
|
emit(ev: GuiEvent): void {
|
|
31
35
|
if (verbose) console.error(chalk.gray("[gui:verbose]"), "emit", ev.type, this.helloReceived ? "" : "(queued)");
|
|
@@ -34,6 +38,10 @@ class GuiBridge {
|
|
|
34
38
|
return;
|
|
35
39
|
}
|
|
36
40
|
if (!this.proc || this.proc.killed) return;
|
|
41
|
+
if (this.guiHttpPort) {
|
|
42
|
+
this.httpChain = this.httpChain.then(() => this.postEvent(ev));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
37
45
|
try {
|
|
38
46
|
this.proc.stdin.write(formatGuiLine(ev));
|
|
39
47
|
} catch {
|
|
@@ -41,6 +49,21 @@ class GuiBridge {
|
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
|
|
52
|
+
/** 经本地 HTTP 通道下发事件(gui/main.cjs 的 127.0.0.1 随机端口服务,token 鉴权) */
|
|
53
|
+
private async postEvent(ev: GuiEvent): Promise<void> {
|
|
54
|
+
try {
|
|
55
|
+
await fetch(`http://127.0.0.1:${this.guiHttpPort}/`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "content-type": "application/json", "x-arona-token": this.guiHttpToken },
|
|
58
|
+
body: JSON.stringify(ev),
|
|
59
|
+
});
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (verbose) {
|
|
62
|
+
console.error(chalk.gray("[gui:verbose]"), "http emit failed:", err instanceof Error ? err.message : err);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
async start(): Promise<void> {
|
|
45
68
|
const electronPath = await getElectronPath();
|
|
46
69
|
if (!electronPath) {
|
|
@@ -187,13 +210,15 @@ class GuiBridge {
|
|
|
187
210
|
private async handleRequest(req: GuiRequest): Promise<void> {
|
|
188
211
|
switch (req.type) {
|
|
189
212
|
case "hello": {
|
|
190
|
-
// GUI
|
|
191
|
-
//
|
|
213
|
+
// GUI 进程握手:解锁排队中的事件(emit mode/setup_info 在 spawn 后立即调用,Windows 下彼时
|
|
214
|
+
// stdin 写入会丢失)。端口/token 就位后,后续事件经本地 HTTP 通道下发。
|
|
192
215
|
this.helloReceived = true;
|
|
216
|
+
this.guiHttpPort = Number(req.httpPort) || 0;
|
|
217
|
+
this.guiHttpToken = String(req.token || "");
|
|
193
218
|
const queued = this.queuedEvents;
|
|
194
219
|
this.queuedEvents = [];
|
|
195
220
|
if (verbose && queued.length) {
|
|
196
|
-
console.error(chalk.gray("[gui:verbose]"), "hello received, flushing", queued.length, "queued events");
|
|
221
|
+
console.error(chalk.gray("[gui:verbose]"), "hello received (httpPort=" + this.guiHttpPort + "), flushing", queued.length, "queued events");
|
|
197
222
|
}
|
|
198
223
|
for (const ev of queued) this.emit(ev);
|
|
199
224
|
break;
|
package/src/gui/protocol.ts
CHANGED
|
@@ -64,6 +64,8 @@ export interface GuiState {
|
|
|
64
64
|
// gui → backend
|
|
65
65
|
// ============================================================
|
|
66
66
|
export type GuiRequest =
|
|
67
|
+
// GUI 进程握手(gui/main.cjs 起好 HTTP 通道后回报;httpPort=0 表示 HTTP 不可用,backend 退回 stdin)
|
|
68
|
+
| { type: "hello"; httpPort: number; token: string }
|
|
67
69
|
// 用户消息(@file/!shell 由 backend 展开)
|
|
68
70
|
| { type: "input"; text: string }
|
|
69
71
|
// 斜杠命令(name 不含 "/")
|