magic-agent 2.6.0 → 2.6.3

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 (102) hide show
  1. package/CLIENT-CONTRACT.md +188 -16
  2. package/client-ui/pc/app.css +42 -0
  3. package/client-ui/pc/js/90-glue.js +3 -0
  4. package/client-ui/shared/conv/03-chat-message.js +25 -19
  5. package/client-ui/shared/conv/09-stream-apply.js +9 -0
  6. package/client-ui/shared/conv/29-msg-variants.js +136 -0
  7. package/client-ui/shared/conv/conv.css +253 -158
  8. package/client-ui/shared/conversation.js +39 -2
  9. package/client-ui-mobile/apps.css +16 -0
  10. package/client-ui-mobile/apps.js +406 -0
  11. package/client-ui-mobile/index.html +2695 -379
  12. package/client-ui-mobile/reqs.css +11 -4
  13. package/client-ui-mobile/reqs.js +49 -5
  14. package/client-ui-mobile/scripts.css +46 -0
  15. package/client-ui-mobile/scripts.js +66 -6
  16. package/cmd/magic-client/browsectl.go +1 -1
  17. package/cmd/magic-client/clientctl.go +37 -2
  18. package/cmd/magic-client/main.go +159 -12
  19. package/internal/agent/dsh_caps_cache_test.go +184 -0
  20. package/internal/agent/dsh_resume.go +144 -5
  21. package/internal/agent/env.go +173 -1
  22. package/internal/agent/env_test.go +114 -0
  23. package/internal/agent/limitnotice.go +207 -1
  24. package/internal/agent/limitnotice_test.go +140 -0
  25. package/internal/agent/stream.go +30 -0
  26. package/internal/agent/streamjson.go +11 -1
  27. package/internal/cli/ask.go +8 -0
  28. package/internal/cli/engines_test.go +103 -0
  29. package/internal/cli/events.go +11 -0
  30. package/internal/cli/events_test.go +50 -0
  31. package/internal/cli/judge.go +104 -0
  32. package/internal/cli/root.go +35 -6
  33. package/internal/cli/session.go +69 -3
  34. package/internal/cli/session_test.go +82 -3
  35. package/internal/cli/sessionlog.go +6 -0
  36. package/internal/cli/sessionlog_test.go +22 -2
  37. package/internal/client/apk.go +8 -0
  38. package/internal/client/apk_test.go +59 -0
  39. package/internal/client/apps.go +1078 -0
  40. package/internal/client/apps_test.go +516 -0
  41. package/internal/client/coreexe.go +196 -0
  42. package/internal/client/coreexe_test.go +161 -0
  43. package/internal/client/coreupgrade.go +866 -0
  44. package/internal/client/coreupgrade_test.go +561 -0
  45. package/internal/client/daemon.go +209 -18
  46. package/internal/client/feishu.go +1259 -0
  47. package/internal/client/feishu_test.go +649 -0
  48. package/internal/client/health_identity_test.go +84 -0
  49. package/internal/client/hostenv.go +68 -0
  50. package/internal/client/hostenv_test.go +37 -0
  51. package/internal/client/jobs.go +233 -12
  52. package/internal/client/jobs_auto.go +571 -0
  53. package/internal/client/jobs_auto_test.go +431 -0
  54. package/internal/client/plugins.go +5 -1
  55. package/internal/client/prefs.go +67 -0
  56. package/internal/client/procstart_linux.go +59 -0
  57. package/internal/client/publicurl.go +82 -0
  58. package/internal/client/requirements.go +9 -1
  59. package/internal/client/requirements_test.go +103 -0
  60. package/internal/client/restart.go +167 -13
  61. package/internal/client/restart_handoff_test.go +230 -0
  62. package/internal/client/restart_test.go +140 -1
  63. package/internal/client/scripts_decode_implausible.go +38 -0
  64. package/internal/client/scripts_decode_other.go +4 -0
  65. package/internal/client/scripts_decode_test.go +16 -11
  66. package/internal/client/scripts_decode_windows.go +0 -24
  67. package/internal/client/scripts_test.go +14 -1
  68. package/internal/client/service.go +403 -87
  69. package/internal/client/service_test.go +166 -1
  70. package/internal/client/sessions_cache.go +148 -0
  71. package/internal/client/sessions_cache_test.go +163 -0
  72. package/internal/client/sessions_fold.go +165 -0
  73. package/internal/client/sessions_fold_test.go +204 -0
  74. package/internal/client/singleinstance.go +470 -0
  75. package/internal/client/singleinstance_test.go +607 -0
  76. package/internal/client/testmain_test.go +88 -0
  77. package/internal/client/workspace_default_test.go +158 -0
  78. package/internal/client/workspacelockenv_test.go +54 -0
  79. package/internal/config/config.go +48 -0
  80. package/internal/judge/judge.go +315 -0
  81. package/internal/session/multiclient_test.go +368 -0
  82. package/internal/session/session.go +83 -11
  83. package/internal/session/session_test.go +7 -0
  84. package/internal/session/status.go +21 -0
  85. package/internal/session/status_test.go +28 -0
  86. package/internal/session/title.go +16 -0
  87. package/internal/session/title_test.go +26 -0
  88. package/internal/session/workspace_lock.go +265 -28
  89. package/npm/apk/magic-agent-mobile-release.apk +0 -0
  90. package/npm/apk/version.json +8 -1
  91. package/npm/dist/darwin-arm64/magic-agent +0 -0
  92. package/npm/dist/darwin-arm64/magic-client +0 -0
  93. package/npm/dist/darwin-x64/magic-agent +0 -0
  94. package/npm/dist/darwin-x64/magic-client +0 -0
  95. package/npm/dist/linux-arm64/magic-agent +0 -0
  96. package/npm/dist/linux-arm64/magic-client +0 -0
  97. package/npm/dist/linux-x64/magic-agent +0 -0
  98. package/npm/dist/linux-x64/magic-client +0 -0
  99. package/npm/dist/win32-x64/magic-agent.exe +0 -0
  100. package/npm/dist/win32-x64/magic-client.exe +0 -0
  101. package/npm/install.js +11 -0
  102. package/package.json +8 -3
@@ -336,6 +336,142 @@ function qaErrorCardNode(opts){
336
336
  return wrap;
337
337
  }
338
338
 
339
+ /* ---------- 额度 / 限流提示卡(2026-10-25)----------
340
+ 用户报障原话:「没额度了,设计一个提示卡」—— 截图里那一轮引擎把供应商的 429 通告
341
+ **当正文**发了回来,气泡里铺着一大段英文原文,上下还挂着「任务耗时 1m 26s」与复制/重试
342
+ 图标行,整体观感像「这一轮正常完成了」。这比普通报错糟得多:用户既看不出没跑成,
343
+ 也不知道该等还是该换模型。
344
+
345
+ ⚠️ **界面不解析通告原文**(判据只在 core:`internal/agent/limitnotice.go` 分类 + 取恢复时刻,
346
+ 事件上带 `error_kind` / `error_code` / `reset_at`)。这里只做展示:认不出 kind 就退回
347
+ 普通失败卡 —— 把「工具报错」画成「额度不足」比不画更糟。
348
+ ⚠️ 与 qaErrorCardNode 的关系:同一骨架(`.qa-errcard` / `.qa-ec-hd` / `.qa-ec-bd`),
349
+ 只换色与信息层次(加一行「什么时候恢复」+ 一行建议)。所以 `.qa-done` 收尾行契约、
350
+ `.qa-mk` 展开委托两条都照旧成立,不必为这张卡单独加逻辑。 */
351
+ const QA_QUOTA_KINDS = {
352
+ quota: {
353
+ title: "额度用尽", badge: "额度不足",
354
+ lead: "引擎账号的额度已用尽,这一轮没有跑完。",
355
+ hint: "建议:等额度恢复后再试,或切换到其它模型继续。",
356
+ },
357
+ rate_limit: {
358
+ title: "请求太频繁", badge: "已被限流",
359
+ lead: "请求过于频繁,引擎暂时拒绝了这一轮。",
360
+ hint: "建议:隔几分钟再试;这不是配置问题,不必改设置。",
361
+ },
362
+ };
363
+
364
+ /* 恢复时刻的展示文案:{clock:"15:07", rel:"约 2 小时后", past:false};取不到回 null。
365
+ ⚠️ 只用 `new Date()` 解 core 给的 **RFC3339**(带偏移的确定形状),不做宽松解析 ——
366
+ V8 会把 `CST` 当美国中部时间(偏 14 小时,实测过),所以含糊的拼法在 core 侧就挡掉了。 */
367
+ function qaQuotaReset(at){
368
+ const s = String(at || "").trim();
369
+ if(!s) return null;
370
+ const t = new Date(s);
371
+ if(isNaN(t.getTime())) return null;
372
+ const now = new Date();
373
+ const pad = (n)=> (n < 10 ? "0" : "") + n;
374
+ const sameDay = t.getFullYear() === now.getFullYear() && t.getMonth() === now.getMonth()
375
+ && t.getDate() === now.getDate();
376
+ const clock = (sameDay ? "" : (t.getMonth() + 1) + "月" + t.getDate() + "日 ")
377
+ + pad(t.getHours()) + ":" + pad(t.getMinutes());
378
+ const diff = t.getTime() - now.getTime();
379
+ if(diff <= 0) return { clock: clock, rel: "", past: true };
380
+ /* 相对时间只做「大致」参考,**不跨档乱除**:超过一天就说天(额度按 5 小时一档重置,
381
+ 真通告基本都落在「几分钟 / 几小时」这一档;算出「约 639910 小时后」是捅了马蜂窝的写法)。 */
382
+ const min = Math.round(diff / 60000);
383
+ const rel = min < 1 ? "不到 1 分钟"
384
+ : min < 60 ? "约 " + min + " 分钟后"
385
+ : min < 60 * 24 ? "约 " + Math.round(min / 60) + " 小时后"
386
+ : "约 " + Math.round(min / 60 / 24) + " 天后";
387
+ return { clock: clock, rel: rel, past: false };
388
+ }
389
+
390
+ /* 事件信封 → 额度卡要的全部文案;**不是**额度/限流就回 null(调用方退回普通失败卡)。
391
+ 纯函数(不碰 DOM):单测与「这张卡该不该出现」的判定都只看它。 */
392
+ function qaQuotaInfo(det){
393
+ if(!det || typeof det !== "object") return null;
394
+ const kind = String(det.error_kind || "").trim();
395
+ const k = QA_QUOTA_KINDS[kind];
396
+ if(!k) return null;
397
+ return {
398
+ kind: kind, code: String(det.error_code || "").trim(),
399
+ raw: String(det.reason || det.error || "").trim(),
400
+ title: k.title, badge: k.badge, lead: k.lead, hint: k.hint,
401
+ reset: qaQuotaReset(det.reset_at),
402
+ };
403
+ }
404
+
405
+ function qaQuotaCardNode(opts){
406
+ const o = opts || {};
407
+ const q = o.info || qaQuotaInfo(o.detail);
408
+ if(!q) return qaErrorCardNode(opts); // 兜底:形状一样,只是不出额度卡
409
+
410
+ const wrap = document.createElement("div");
411
+ wrap.className = "qa-done err qa-errcard qa-quota";
412
+
413
+ const hd = document.createElement("div");
414
+ hd.className = "qa-ec-hd";
415
+ hd.innerHTML =
416
+ '<span class="qa-ec-ico">' + (QA_V_ICONS.alert || "") + '</span>'
417
+ + '<span class="qa-ec-tt"></span>'
418
+ + '<span class="qa-qc-badge"></span>';
419
+ hd.querySelector(".qa-ec-tt").textContent = q.title;
420
+ hd.querySelector(".qa-qc-badge").textContent = q.badge;
421
+ wrap.appendChild(hd);
422
+
423
+ const bd = document.createElement("div");
424
+ bd.className = "qa-ec-bd";
425
+ const lead = document.createElement("p");
426
+ lead.className = "qa-ec-reason";
427
+ lead.textContent = q.lead;
428
+ bd.appendChild(lead);
429
+
430
+ /* 恢复时刻:时刻本身加粗(用户此刻只想知道「几点能再用」),相对时间跟在后面;
431
+ 已经过了恢复时刻就说「可以重试」—— 这时候再写「将于…恢复」就是假话。 */
432
+ if(q.reset){
433
+ const rp = document.createElement("p");
434
+ rp.className = "qa-qc-reset";
435
+ const b = document.createElement("b");
436
+ b.textContent = q.reset.clock;
437
+ if(q.reset.past){
438
+ rp.appendChild(document.createTextNode("已过恢复时刻("));
439
+ rp.appendChild(b);
440
+ rp.appendChild(document.createTextNode("),可以重试。"));
441
+ }else{
442
+ rp.appendChild(document.createTextNode("将于 "));
443
+ rp.appendChild(b);
444
+ rp.appendChild(document.createTextNode(" 恢复" + (q.reset.rel ? "(" + q.reset.rel + ")" : "")));
445
+ }
446
+ bd.appendChild(rp);
447
+ }
448
+
449
+ const hint = document.createElement("p");
450
+ hint.className = "qa-qc-hint";
451
+ hint.textContent = q.hint;
452
+ bd.appendChild(hint);
453
+
454
+ /* 原文:留一条可展开的明细(排障要它 —— 谁限流的、request id 是多少)。
455
+ 复用 `.qa-mk` 词汇 → 点击走 #qa-stream 委托,快照重建后照样能展开。 */
456
+ if(q.raw){
457
+ const mk = document.createElement("div");
458
+ mk.className = "qa-mk";
459
+ const row = document.createElement("div");
460
+ row.className = "qa-mk-row";
461
+ row.dataset.tog = "1";
462
+ row.innerHTML = '<span class="qa-mk-chev">' + (QA_V_ICONS.chev || "") + '</span>'
463
+ + '<span class="qa-mk-tt">原文</span>';
464
+ mk.appendChild(row);
465
+ const mkbd = document.createElement("div");
466
+ mkbd.className = "qa-mk-bd";
467
+ mkbd.appendChild(qaCodeFrameNode(q.code ? "HTTP " + q.code : "原文", q.raw));
468
+ mk.appendChild(mkbd);
469
+ bd.appendChild(mk);
470
+ }
471
+ wrap.appendChild(bd);
472
+ return wrap;
473
+ }
474
+
339
475
  /* ---------- ④ 分隔条(anywhere Marker variant="separator")----------
340
476
  用在「横切整条会话」的事件上 —— anywhere 那边是**会话压缩**(`MarkerContentKind="compact"`,
341
477
  进行中/已完成两种文案)。分隔条不是谁说的话,形状本身就在说「这里是一条分界」。