arona-agent 1.1.8 → 1.2.1

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 CHANGED
@@ -37,33 +37,44 @@ const pending = [];
37
37
 
38
38
  function forward(msg) {
39
39
  if (win && !win.isDestroyed() && rendererReady) {
40
+ if (VERBOSE) console.error("[gui:verbose] forward", msg.type);
40
41
  win.webContents.send("gui-event", msg);
41
42
  } else {
43
+ if (VERBOSE) console.error("[gui:verbose] buffer", msg.type, "(rendererReady=" + rendererReady + ")");
42
44
  pending.push(msg);
43
45
  }
44
46
  }
45
47
 
46
48
  function flushPending() {
47
49
  if (!win || win.isDestroyed()) return;
50
+ if (VERBOSE && pending.length) console.error("[gui:verbose] flush " + pending.length + " buffered events");
48
51
  while (pending.length) {
49
52
  win.webContents.send("gui-event", pending.shift());
50
53
  }
51
54
  }
52
55
 
53
- // ARONA_GUI_SMOKE=1:loadFile + flush 后探测 DOM(页面可见性 / preload / 渲染层脚本),
54
- // 结果打 SMOKE_RESULT 行后退出——冒烟验证 mode 事件不丢(无需人工看窗口)。
55
- // 另起 SMOKE_UI 探测(8s 后,等 startMain 就绪):欢迎页 LOGO / 浅色背景 / 斜杠菜单过滤 / 工具行样式。
56
- function smokeProbe(skipQuit) {
56
+ // ARONA_GUI_SMOKE=1:loadFile 后周期探测 DOM(每 2s,最长 30s)——页面可见性 / preload / 渲染层脚本,
57
+ // 结果打 SMOKE_RESULT 行;一旦有页面揭示(或超时)打 SMOKE_UI 详情后退出。
58
+ // 周期重试的原因:后端 startMain(initAgent 等)可能远超 8s,单次探测会把"启动慢"误判成"事件未送达"。
59
+ function smokeProbe() {
57
60
  const js = '(function(){var p=document.querySelectorAll(".page:not(.hidden)");'
58
61
  + 'return JSON.stringify({visible:p.length?p[0].id:null,'
59
62
  + 'api:!!(window.guiAPI&&window.guiAPI.send&&window.guiAPI.on),'
60
63
  + 'setup:!!(window.SetupUI&&window.SetupUI.handle)});})()';
61
- setTimeout(() => {
64
+ const started = Date.now();
65
+ const timer = setInterval(() => {
66
+ if (!win || win.isDestroyed()) { clearInterval(timer); return; }
62
67
  win.webContents.executeJavaScript(js)
63
68
  // stderr 会被 backend 转发到终端(stdout 被 ###GUI### 协议解析占用)
64
- .then((r) => { console.error("SMOKE_RESULT " + r); if (!skipQuit) app.quit(); })
65
- .catch((e) => { console.error("SMOKE_ERROR " + e); if (!skipQuit) app.quit(); });
66
- }, 300);
69
+ .then((r) => {
70
+ console.error("SMOKE_RESULT " + r);
71
+ if (JSON.parse(r).visible || Date.now() - started >= 30000) {
72
+ clearInterval(timer);
73
+ smokeProbeUI();
74
+ }
75
+ })
76
+ .catch((e) => { console.error("SMOKE_ERROR " + e); clearInterval(timer); smokeProbeUI(); });
77
+ }, 2000);
67
78
  }
68
79
 
69
80
  function smokeProbeUI() {
@@ -251,8 +262,9 @@ function createWindow() {
251
262
  // 先挂监听再 loadFile:避免加载完成事件在挂监听前触发导致 rendererReady 永不置位
252
263
  win.webContents.once("did-finish-load", () => {
253
264
  rendererReady = true;
265
+ if (VERBOSE) console.error("[gui:verbose] did-finish-load, pending=" + pending.length);
254
266
  flushPending();
255
- if (process.env.ARONA_GUI_SMOKE === "1") { smokeProbe(true); smokeProbeUI(); }
267
+ if (process.env.ARONA_GUI_SMOKE === "1") smokeProbe();
256
268
  if (process.env.ARONA_GUI_DEMO === "1") setTimeout(demoScenario, 2000);
257
269
  });
258
270
  win.loadFile(path.join(__dirname, "renderer", "index.html"));
@@ -295,6 +307,11 @@ process.stdin.on("data", (data) => {
295
307
  }
296
308
  });
297
309
 
310
+ // 握手:告知 backend 本进程的 stdin 解析器已就绪,可以开始下发 ###GUI### 事件。
311
+ // Windows 下 spawn 后立刻写 stdin 会丢数据(GUI 白屏根因:mode 事件从未到达本进程),
312
+ // backend 收到 hello 前把事件全部入队,收到后按序下发。
313
+ send({ type: "hello" });
314
+
298
315
  // 窗口全关:先发 exit 请求让后端走完整清理,再退出(延迟让协议行先 flush)
299
316
  app.on("window-all-closed", () => {
300
317
  send({ type: "exit" });
@@ -1,5 +1,15 @@
1
1
  // ARONA GUI 渲染层主逻辑:###GUI### 协议 → DOM(侧栏会话列表 / 斜杠菜单 / 消息流 / 麦克风 / 弹窗)
2
+ // 全局错误陷阱:未捕获异常打进 console.error(经 main.cjs 以 [gui:render:3] 转发终端)——
3
+ // 定位"页面加载正常但 .page 保持 hidden → 白屏"时 app.js 中途崩溃的情况
4
+ window.addEventListener("error", (e) => {
5
+ console.error("[app.js] window error:", e.message, e.filename + ":" + e.lineno);
6
+ });
7
+ window.addEventListener("unhandledrejection", (e) => {
8
+ console.error("[app.js] unhandled rejection:", e.reason);
9
+ });
10
+
2
11
  (function () {
12
+ console.log("[app.js] boot");
3
13
  const $ = (sel) => document.querySelector(sel);
4
14
  const api = window.guiAPI;
5
15
  const chat = $("#chat");
@@ -1166,6 +1176,7 @@
1166
1176
 
1167
1177
  // ── 协议分发 ──────────────────────────────────
1168
1178
  api.on((msg) => {
1179
+ if (msg.type === "mode") console.log("[app.js] mode ->", msg.mode); // 白屏排查:确认事件到达渲染层
1169
1180
  switch (msg.type) {
1170
1181
  case "mode":
1171
1182
  $("#main-page").classList.toggle("hidden", msg.mode !== "main");
@@ -1227,4 +1238,5 @@
1227
1238
  });
1228
1239
 
1229
1240
  updateSendState(); // 初始为空输入:发送按钮置灰
1241
+ console.log("[app.js] listener ready"); // 白屏排查:走到此处 = app.js 完整执行、api.on 已注册
1230
1242
  })();
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "arona-agent",
3
- "version": "1.1.8",
3
+ "version": "1.2.1",
4
4
  "description": "Terminal AI Agent with desktop pet Arona — eye-tracking pupils, voice cloning, Computer Use, TTS/STT, MCP.",
5
- "keywords": ["voice-clone", "Blue Archive", "blue-archive", "ai-agent", "arona", "computer-use", "desktop-pet"],
5
+ "keywords": [
6
+ "voice-clone",
7
+ "Blue Archive",
8
+ "blue-archive",
9
+ "ai-agent",
10
+ "arona",
11
+ "computer-use",
12
+ "desktop-pet"
13
+ ],
6
14
  "license": "MIT",
7
15
  "type": "module",
8
16
  "bin": {
package/src/gui/index.ts CHANGED
@@ -21,8 +21,18 @@ class GuiBridge {
21
21
  private controller: GuiController | null = null;
22
22
  private startingMain = false;
23
23
  private exited = false;
24
+ // Windows 下 spawn 后立刻写 stdin 会丢数据(GUI 白屏根因:mode 事件从未到达 Electron 进程)——
25
+ // 等 GUI 进程回报 hello(stdin 解析器就绪的握手,gui/main.cjs 模块加载即发送)才真正下发,此前入队。
26
+ // macOS/Linux 不受影响,但握手在所有平台统一走一遍(幂等,且顺带验证 stdout 协议链路通)。
27
+ private helloReceived = false;
28
+ private queuedEvents: GuiEvent[] = [];
24
29
 
25
30
  emit(ev: GuiEvent): void {
31
+ if (verbose) console.error(chalk.gray("[gui:verbose]"), "emit", ev.type, this.helloReceived ? "" : "(queued)");
32
+ if (!this.helloReceived) {
33
+ this.queuedEvents.push(ev);
34
+ return;
35
+ }
26
36
  if (!this.proc || this.proc.killed) return;
27
37
  try {
28
38
  this.proc.stdin.write(formatGuiLine(ev));
@@ -55,6 +65,16 @@ class GuiBridge {
55
65
  stdio: ["pipe", "pipe", "pipe"],
56
66
  });
57
67
 
68
+ // 握手超时告警:hello 不来 = GUI 进程的 stdin/stdout 协议链路不通(页面白屏只是其结果)
69
+ setTimeout(() => {
70
+ if (!this.helloReceived && !this.exited && this.proc) {
71
+ console.error(chalk.red(t(
72
+ "GUI:15s 未收到 GUI 进程握手(hello),###GUI### stdin 协议链路不通,界面将保持白屏。",
73
+ "GUI: no handshake (hello) from the GUI process within 15s; the ###GUI### stdin protocol is broken and the window will stay blank.",
74
+ )));
75
+ }
76
+ }, 15000);
77
+
58
78
  this.proc.stdout.on("data", (data) => {
59
79
  this.buffer += data.toString();
60
80
  const lines = this.buffer.split("\n");
@@ -166,6 +186,18 @@ class GuiBridge {
166
186
 
167
187
  private async handleRequest(req: GuiRequest): Promise<void> {
168
188
  switch (req.type) {
189
+ case "hello": {
190
+ // GUI 进程 stdin 解析器就绪:解锁排队中的事件(emit mode/setup_info 在 spawn 后立即调用,
191
+ // Windows 下彼时写入会丢失——见 emit() 注释)
192
+ this.helloReceived = true;
193
+ const queued = this.queuedEvents;
194
+ this.queuedEvents = [];
195
+ if (verbose && queued.length) {
196
+ console.error(chalk.gray("[gui:verbose]"), "hello received, flushing", queued.length, "queued events");
197
+ }
198
+ for (const ev of queued) this.emit(ev);
199
+ break;
200
+ }
169
201
  case "setup_submit": {
170
202
  const ok = await runGuiSetup(req.form as unknown as GuiSetupForm, (ev) => this.emit(ev));
171
203
  if (ok) await this.startMain();