magic-agent 2.4.3 → 2.6.0
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/CLIENT-CONTRACT.md +284 -30
- package/README.md +15 -14
- package/client-ui/pc/app.css +32 -0
- package/client-ui/pc/js/04-task-list.js +5 -1
- package/client-ui/pc/js/07-chat-stream-render.js +3 -3
- package/client-ui/pc/js/90-glue.js +37 -2
- package/client-ui/pc/js/92-composer.js +19 -4
- package/client-ui/pc/js/93-rail.js +16 -5
- package/client-ui/shared/conv/02-media-kinds.js +2 -2
- package/client-ui/shared/conv/03-chat-message.js +241 -23
- package/client-ui/shared/conv/06-ask-answer.js +75 -1
- package/client-ui/shared/conv/09-stream-apply.js +63 -7
- package/client-ui/shared/conv/19-attachments.js +157 -6
- package/client-ui/shared/conv/27-conv-ledger.js +5 -2
- package/client-ui/shared/conv/29-msg-variants.js +81 -0
- package/client-ui/shared/conv/conv.css +79 -19
- package/client-ui/shared/conv/dom-map.md +1 -0
- package/client-ui/shared/conversation.css +13 -2
- package/client-ui/shared/conversation.js +336 -50
- package/client-ui/shared/transport-http.js +14 -0
- package/client-ui-mobile/index.html +2932 -333
- package/client-ui-mobile/reqs.css +102 -0
- package/client-ui-mobile/reqs.js +485 -70
- package/client-ui-mobile/reqshot.js +505 -0
- package/client-ui-mobile/scripts.css +21 -0
- package/client-ui-mobile/scripts.js +539 -0
- package/client-ui-mobile/vendor/cose-base.js +3214 -0
- package/client-ui-mobile/vendor/cytoscape-fcose.js +1549 -0
- package/client-ui-mobile/vendor/cytoscape.min.js +32 -0
- package/client-ui-mobile/vendor/layout-base.js +5230 -0
- package/cmd/magic-client/browsectl.go +199 -0
- package/cmd/magic-client/clientctl.go +33 -9
- package/cmd/magic-client/main.go +57 -26
- package/internal/agent/ask.go +45 -0
- package/internal/agent/ask_test.go +39 -0
- package/internal/agent/attachments_test.go +6 -2
- package/internal/agent/capability.go +7 -1
- package/internal/agent/claude.go +20 -15
- package/internal/agent/codebuddy.go +125 -34
- package/internal/agent/codebuddy_account_test.go +54 -0
- package/internal/agent/codebuddy_stream_test.go +103 -0
- package/internal/agent/credits.go +713 -0
- package/internal/agent/credits_minimax_test.go +97 -0
- package/internal/agent/credits_test.go +211 -0
- package/internal/agent/custom_models.go +155 -0
- package/internal/agent/custom_models_test.go +125 -0
- package/internal/agent/dsh.go +123 -49
- package/internal/agent/dsh_resume.go +13 -2
- package/internal/agent/dsh_test.go +108 -6
- package/internal/agent/engine.go +37 -1
- package/internal/agent/engine_base.go +21 -17
- package/internal/agent/engine_base_test.go +2 -2
- package/internal/agent/engine_test.go +47 -86
- package/internal/agent/fallback.go +313 -0
- package/internal/agent/fallback_test.go +340 -0
- package/internal/agent/launch.go +43 -1
- package/internal/agent/llmhttp.go +46 -4
- package/internal/agent/llmhttp_test.go +157 -0
- package/internal/agent/models.go +53 -1
- package/internal/agent/models_test.go +20 -5
- package/internal/agent/pidalive_windows_test.go +40 -0
- package/internal/agent/runcmd.go +26 -0
- package/internal/agent/runcmd_windows.go +36 -5
- package/internal/agent/stream.go +136 -6
- package/internal/agent/stream_test.go +25 -38
- package/internal/agent/stream_timeline_test.go +2 -37
- package/internal/agent/streamjson.go +108 -6
- package/internal/agent/tools_suffix_test.go +6 -3
- package/internal/agent/trae.go +50 -243
- package/internal/agent/trae_exec.go +433 -0
- package/internal/agent/trae_exec_test.go +181 -0
- package/internal/agent/wraplaunch_test.go +113 -0
- package/internal/cli/ask.go +160 -37
- package/internal/cli/ask_test.go +1 -1
- package/internal/cli/checkin.go +77 -0
- package/internal/cli/clientcmd.go +3 -2
- package/internal/cli/control.go +46 -5
- package/internal/cli/control_test.go +65 -0
- package/internal/cli/events.go +4 -0
- package/internal/cli/quotas.go +125 -0
- package/internal/cli/requirements.go +51 -0
- package/internal/cli/root.go +122 -5
- package/internal/cli/session.go +6 -0
- package/internal/cli/suggest.go +7 -5
- package/internal/cli/team.go +37 -9
- package/internal/cli/team_test.go +39 -0
- package/internal/client/ab.go +372 -0
- package/internal/client/ab_test.go +287 -0
- package/internal/client/browse.go +84 -36
- package/internal/client/browse_test.go +107 -9
- package/internal/client/browseinput.go +481 -0
- package/internal/client/browseinput_test.go +216 -0
- package/internal/client/corecmd.go +23 -0
- package/internal/client/corecmd_other.go +8 -0
- package/internal/client/corecmd_windows.go +24 -0
- package/internal/client/daemon.go +90 -17
- package/internal/client/daemon_test.go +33 -0
- package/internal/client/engines_checkin_test.go +57 -0
- package/internal/client/fallback_flags_test.go +64 -0
- package/internal/client/jobs.go +294 -21
- package/internal/client/jobs_test.go +299 -2
- package/internal/client/knowledge.go +854 -0
- package/internal/client/knowledge_test.go +510 -0
- package/internal/client/lan.go +3 -3
- package/internal/client/quotas_merge_test.go +107 -0
- package/internal/client/requirements.go +198 -1
- package/internal/client/requirements_shots_test.go +200 -0
- package/internal/client/restart.go +134 -0
- package/internal/client/restart_test.go +151 -0
- package/internal/client/scripts.go +1047 -0
- package/internal/client/scripts_decode_other.go +27 -0
- package/internal/client/scripts_decode_test.go +125 -0
- package/internal/client/scripts_decode_windows.go +206 -0
- package/internal/client/scripts_test.go +726 -0
- package/internal/client/service.go +322 -18
- package/internal/client/service_test.go +51 -0
- package/internal/client/suggest.go +105 -2
- package/internal/client/suggest_test.go +86 -0
- package/internal/client/team_flags.go +25 -11
- package/internal/client/team_flags_test.go +39 -0
- package/internal/requirements/bootstrap.go +16 -0
- package/internal/requirements/consolidate.go +100 -9
- package/internal/requirements/doc.go +154 -3
- package/internal/requirements/graph.go +138 -0
- package/internal/requirements/memory.go +7 -0
- package/internal/requirements/modules.go +312 -0
- package/internal/requirements/modules_test.go +373 -0
- package/internal/requirements/relations.go +155 -0
- package/internal/requirements/relations_test.go +231 -0
- package/internal/requirements/requirements.go +10 -0
- package/internal/requirements/shots.go +418 -0
- package/internal/requirements/shots_test.go +387 -0
- package/internal/session/session.go +92 -0
- package/internal/suggest/suggest.go +23 -1
- package/internal/team/event.go +3 -1
- package/internal/team/execute.go +21 -6
- package/internal/team/fallback_route_test.go +80 -0
- package/internal/team/planner.go +15 -3
- package/internal/team/route.go +89 -17
- package/internal/team/team.go +18 -4
- package/npm/apk/magic-agent-mobile-release.apk +0 -0
- package/npm/apk/version.json +1 -5
- package/npm/dist/darwin-arm64/magic-agent +0 -0
- package/npm/dist/darwin-arm64/magic-client +0 -0
- package/npm/dist/darwin-x64/magic-agent +0 -0
- package/npm/dist/darwin-x64/magic-client +0 -0
- package/npm/dist/linux-arm64/magic-agent +0 -0
- package/npm/dist/linux-arm64/magic-client +0 -0
- package/npm/dist/linux-x64/magic-agent +0 -0
- package/npm/dist/linux-x64/magic-client +0 -0
- package/npm/dist/win32-x64/magic-agent.exe +0 -0
- package/npm/dist/win32-x64/magic-client.exe +0 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
```bash
|
|
18
18
|
npm install -g magic-agent # 从 npmjs 全局安装(无需 Go)
|
|
19
19
|
npm install -g ./magic-agent-<version>.tgz # 从本地 tgz 安装(离线 / 内网)
|
|
20
|
-
|
|
20
|
+
npm run build:current # 从源码构建当前平台(core + client,Go 1.26+)
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
> ⚠️ 截至 2026-09-28,npmjs 上还查不到 `magic-agent`(首次正式发布尚未发生),
|
|
@@ -36,7 +36,7 @@ go build -o bin/magic-agent ./cmd/magic-agent # 从源码构建(Go 1.26+)
|
|
|
36
36
|
| claude | Claude Code | `MAGIC_AGENT_CLAUDE_BIN` → `/opt/homebrew/bin/claude` → PATH |
|
|
37
37
|
| codebuddy | CodeBuddy Code CLI(`npm i -g @tencent-ai/codebuddy-code`) | `MAGIC_AGENT_CODEBUDDY_BIN` → PATH → npm 全局前缀 |
|
|
38
38
|
| codebuddy-ai | 同上(**同一个 CLI 的第二个账号**) | `MAGIC_AGENT_CODEBUDDY_AI_BIN` → PATH → npm 全局前缀 |
|
|
39
|
-
| trae |
|
|
39
|
+
| trae | TRAE CLI 2.0(`traex`) | `MAGIC_AGENT_TRAE_BIN` → `%LOCALAPPDATA%\Programs\TraeCLI\bin\traex.exe` / `~/.local/bin/traex` → PATH |
|
|
40
40
|
| llm | [simonw/LLM](https://github.com/simonw/LLM) | `MAGIC_AGENT_LLM_BIN` → `~/.llm-venv/bin/llm` → `/opt/homebrew/bin/llm` → PATH |
|
|
41
41
|
| codex | `@openai/codex` | `MAGIC_AGENT_CODEX_BIN` → `/opt/homebrew/bin/codex` → PATH |
|
|
42
42
|
| openclaw | OpenClaw | `MAGIC_AGENT_OPENCLAW_BIN` → `/opt/homebrew/bin/openclaw` → PATH |
|
|
@@ -96,7 +96,7 @@ magic-agent --engines --no-models # 跳过模型与版本探测(不启动
|
|
|
96
96
|
| codex | `npm install -g @openai/codex` |
|
|
97
97
|
| openclaw | `npm install -g openclaw@latest` |
|
|
98
98
|
| dsh | `npm i -g @deepseek-ai/dsh` |
|
|
99
|
-
| trae | `sh -c "$(curl -
|
|
99
|
+
| trae | `sh -c "$(curl -fsSL https://trae.cn/trae-cli/install_v2.sh)"`(Windows: `irm https://trae.cn/trae-cli/install_v2.ps1 | iex`) |
|
|
100
100
|
| llm | `python3 -m venv ~/.llm-venv && ~/.llm-venv/bin/pip install llm` |
|
|
101
101
|
| codebuddy / codebuddy-ai / arkclaw / codebuddy-gateway / 具名 A2A agent | **无该字段**(桌面端 GUI 应用 / 网关,没有可执行的安装命令),怎么才能用见同行 `note` |
|
|
102
102
|
|
|
@@ -116,7 +116,7 @@ magic-agent --engines --no-models | jq -r '.[] | select(.install) | "\(.engine)\
|
|
|
116
116
|
| claude | `~/.claude/settings.json`(顶层 `model` + env 里 `ANTHROPIC_*MODEL[*_NAME]`) |
|
|
117
117
|
| codebuddy | 客户端模型选择器清单 ∩ 客户端合并配置缓存;回退 `codebuddy --help` 的 `Currently supported` |
|
|
118
118
|
| codebuddy-ai | 同上(独立配置目录 `~/.workbuddy-ai`) |
|
|
119
|
-
| trae | `
|
|
119
|
+
| trae | `traex models --json` |
|
|
120
120
|
| llm | `llm models` |
|
|
121
121
|
| codex | `codex debug models` |
|
|
122
122
|
| openclaw | `openclaw models list --json` |
|
|
@@ -151,7 +151,7 @@ magic-agent -o text -p "用一句话解释什么是熵"
|
|
|
151
151
|
# 切引擎、切模型
|
|
152
152
|
magic-agent -e codebuddy "写一首俳句" # codebuddy 默认 hy3
|
|
153
153
|
magic-agent -e codebuddy -m glm-5.3 "写一首俳句"
|
|
154
|
-
magic-agent -e trae -m My-MiniMax-M3 "..." #
|
|
154
|
+
magic-agent -e trae -m My-MiniMax-M3 "..." # 内部转 traex exec -m <model>
|
|
155
155
|
magic-agent -e llm -m minimax-m3 "问题" # llm 用自己注册的模型
|
|
156
156
|
|
|
157
157
|
# 工具与权限(--tools 默认 off = 纯 chat)
|
|
@@ -387,12 +387,13 @@ npm 包**同时装着** core(`magic-agent`)和客户端插件(`magic-clien
|
|
|
387
387
|
与 `/desk/*` 接口。手机要用它,就在装包的那台电脑上把插件跑成后台服务:
|
|
388
388
|
|
|
389
389
|
```bash
|
|
390
|
-
magic-agent client start
|
|
391
|
-
magic-agent client status
|
|
392
|
-
magic-agent client stop
|
|
390
|
+
magic-agent client start # 后台常驻(关掉终端也还在),默认手机可连
|
|
391
|
+
magic-agent client status # 在不在、端口、手机地址、健康检查(--json 给脚本)
|
|
392
|
+
magic-agent client stop # 停掉(本来没跑也算成功)
|
|
393
|
+
magic-agent client start --local # 只给本机用(绑 127.0.0.1)
|
|
393
394
|
```
|
|
394
395
|
|
|
395
|
-
`start
|
|
396
|
+
`start` 默认绑 `0.0.0.0:9100`,会打印每个网卡一条 `http://<局域网IP>:9100/?token=…`,以及 `/pair`
|
|
396
397
|
配对二维码页 —— 那条**在电脑上**打开,手机扫屏幕上的码(或把地址整行复制到手机 App)。
|
|
397
398
|
|
|
398
399
|
| 事实 | 位置 / 说明 |
|
|
@@ -405,12 +406,12 @@ magic-agent client stop # 停掉(本来没跑也算成功)
|
|
|
405
406
|
|
|
406
407
|
要点:
|
|
407
408
|
|
|
408
|
-
-
|
|
409
|
-
-
|
|
409
|
+
- **默认就绑 `0.0.0.0:9100`**,手机在同一 Wi-Fi 下可直接连;只想本机访问加 `--local`(绑 `127.0.0.1`)。跨网不在本命令范围内。
|
|
410
|
+
- 局域网模式下令牌是**唯一门锁**,短于 24 字符会被直接拒绝启动;默认生成的 32 位十六进制远超这条线。
|
|
410
411
|
- 令牌跨重启稳定是刻意的:手机里存的是「地址 + 令牌」,每次 `start` 换一个就等于配对作废。
|
|
411
412
|
- `status` 的判据是「pid 活着 **且** `/desk/health` 答得上」,不是只看进程;不健康时退出码 3。
|
|
412
|
-
-
|
|
413
|
-
- 排障要看前台日志就跑 `magic-client serve
|
|
413
|
+
- 默认端口固定 `9100`(`--addr` 可覆盖),跨重启稳定;被占用时启动会明确报错,不会悄悄换端口。
|
|
414
|
+
- 排障要看前台日志就跑 `magic-client serve`(同一个二进制,`serve` 不脱离终端)。
|
|
414
415
|
- `magic-agent client …` 只是转发层:core **不依赖**插件(`internal/boundary` 钉着这条),
|
|
415
416
|
所以参数集合由 `magic-client start --help` 定义。
|
|
416
417
|
|
|
@@ -471,7 +472,7 @@ magic-client serve --ui-dir magic-h5
|
|
|
471
472
|
手机访问启动时打印的 `http://<局域网IP>/?token=...`。嵌进自己的服务同理:按上面的
|
|
472
473
|
布局静态托管,令牌走 `?token=`。
|
|
473
474
|
|
|
474
|
-
(`magic-client` 随 npm 包安装,也可以只跑 `magic-agent client start
|
|
475
|
+
(`magic-client` 随 npm 包安装,也可以只跑 `magic-agent client start` 把插件后台拉起来,
|
|
475
476
|
界面资源会自动按包布局找到。)
|
|
476
477
|
|
|
477
478
|
**② 拷进自己的构建**(App/工程内嵌):把 `client-ui-mobile/` 内容与 `client-ui/shared/`
|
package/client-ui/pc/app.css
CHANGED
|
@@ -1921,6 +1921,36 @@ body::after{content:"";position:fixed;inset:0;z-index:60;pointer-events:none;opa
|
|
|
1921
1921
|
.qa-done .d{width:6px;height:6px;border-radius:50%;background:var(--jade)}
|
|
1922
1922
|
.qa-done.err .d{background:var(--cinnabar-hi)}
|
|
1923
1923
|
.qa-done.err{color:var(--cinnabar-hi)}
|
|
1924
|
+
|
|
1925
|
+
/* 错误显示卡(终态失败):朱砂描边 + 淡红底的卡片面(见 29-msg-variants 的 qaErrorCardNode)。
|
|
1926
|
+
⚠️ 根节点带 `.qa-done` 类(收尾行契约要数它),这里把它从「等宽灰字行」翻成「块级卡片」:
|
|
1927
|
+
display:block 盖掉 `.qa-done` 的 flex 行布局;颜色交给子元素,根只留卡片面。 */
|
|
1928
|
+
.qa-errcard{display:block;margin:8px 0 4px;padding:10px 12px;
|
|
1929
|
+
border:1px solid rgba(214,64,42,.38);border-radius:var(--r-md);background:rgba(214,64,42,.06)}
|
|
1930
|
+
|
|
1931
|
+
.qa-errcard + .qa-errcard{margin-top:0}
|
|
1932
|
+
|
|
1933
|
+
.qa-ec-hd{display:flex;align-items:center;gap:8px}
|
|
1934
|
+
|
|
1935
|
+
.qa-ec-ico{width:16px;height:16px;flex:none;display:grid;place-items:center;color:var(--cinnabar-hi)}
|
|
1936
|
+
|
|
1937
|
+
.qa-ec-ico svg{width:15px;height:15px;display:block;stroke:currentColor;fill:none;
|
|
1938
|
+
stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
|
|
1939
|
+
|
|
1940
|
+
.qa-ec-tt{flex:1;min-width:0;font-family:var(--font);font-size:13px;font-weight:600;color:var(--cinnabar-hi)}
|
|
1941
|
+
|
|
1942
|
+
.qa-ec-bd{margin-top:8px}
|
|
1943
|
+
|
|
1944
|
+
.qa-ec-reason{margin:0;font-size:12.5px;line-height:1.6;color:var(--paper-dim);
|
|
1945
|
+
white-space:pre-wrap;word-break:break-word}
|
|
1946
|
+
|
|
1947
|
+
.qa-ec-meta{margin-top:6px;font-family:var(--mono);font-size:10px;letter-spacing:.04em;color:var(--paper-mute)}
|
|
1948
|
+
|
|
1949
|
+
/* 卡片里的可展开详情(复用 .qa-mk 词汇);把它的左缩进让出来,贴着卡片内边距更顺 */
|
|
1950
|
+
.qa-errcard .qa-mk{margin:8px 0 0}
|
|
1951
|
+
|
|
1952
|
+
.qa-errcard .qa-mk-bd{padding-left:2px}
|
|
1953
|
+
|
|
1924
1954
|
/* 待验证那条对话末尾的**出路按钮**(人工确认完成 / 按审核意见返工重做,见 qaReqConvSnapshot):
|
|
1925
1955
|
与卡片脚注同一套颜色语言(玉色 = 就这么定了,朱砂 = 再跑一轮),但对话里宽度充足、用全称。
|
|
1926
1956
|
**贴右**(justify-content:flex-end):它作用于上面那一列审查内容(结论 + 报告都在右侧),
|
|
@@ -3912,6 +3942,8 @@ body::after{content:"";position:fixed;inset:0;z-index:60;pointer-events:none;opa
|
|
|
3912
3942
|
[data-theme="dark"] .qa-done .d{background:#34D399}
|
|
3913
3943
|
[data-theme="dark"] .qa-done.err{color:#F87171}
|
|
3914
3944
|
[data-theme="dark"] .qa-done.err .d{background:#F87171}
|
|
3945
|
+
[data-theme="dark"] .qa-errcard{background:rgba(248,113,113,.09);border-color:rgba(248,113,113,.42)}
|
|
3946
|
+
[data-theme="dark"] .qa-ec-reason{color:#E7E5E4}
|
|
3915
3947
|
|
|
3916
3948
|
/* 委派卡 */
|
|
3917
3949
|
[data-theme="dark"] .qa-agent-chip{color:#A8A29E;border-color:#3B3835;background:rgba(245,244,243,.05)}
|
|
@@ -129,10 +129,14 @@ function qaPermOf(id){ return QA_PERMS.find(x=>x.id === id) || QA_PERMS.find(x=>
|
|
|
129
129
|
function qaModelName(id, engine){
|
|
130
130
|
const key = String(id || "").replace(/^custom-local:/, "");
|
|
131
131
|
if(!key) return "";
|
|
132
|
+
const eng = String(engine || "").trim().toLowerCase();
|
|
133
|
+
/* dsh 的模型 id 是 `route/model`:路由前缀只在 dsh 配置内部有区分意义,显示时只留
|
|
134
|
+
后面的模型名(`-m` 与落库仍用完整 id,见 internal/agent/dsh.go::dshResolveRoute)。 */
|
|
135
|
+
const bare = (eng === "dsh") ? key.replace(/^[^/]+\//, "") : key;
|
|
132
136
|
const hit = (e)=> (e && e.modelLabels && e.modelLabels[key]) || "";
|
|
133
137
|
return hit(qaEngines.find(x=>x.engine === (engine || qaEngine)))
|
|
134
138
|
|| qaEngines.map(hit).find(Boolean)
|
|
135
|
-
||
|
|
139
|
+
|| bare;
|
|
136
140
|
}
|
|
137
141
|
/* ---------- 模型:链来自配置界面,选不选**两个实例都可以** ----------
|
|
138
142
|
用户 2026-09-21 四次改口,**这一版是定稿**(前面几版都写在这里,免得下次又来回改):
|
|
@@ -304,7 +304,7 @@ if(askAttBusy(from)){ toast("截图还在保存,稍等一下再发"); return f
|
|
|
304
304
|
taskTitle: (curTask && curTask.title) || "" });
|
|
305
305
|
if(!r || !r.ok){
|
|
306
306
|
qaRunEnd(h);
|
|
307
|
-
qaRenderDone(false, "调用失败 · " + ((r && r.error) || "桥不可用"), qaRunHost(run));
|
|
307
|
+
qaRenderDone(false, "调用失败 · " + ((r && r.error) || "桥不可用"), qaRunHost(run), null, r);
|
|
308
308
|
/* 失败原因也进消息台账(2026-09-22):持久层只存消息,若这一轮一个字都没吐、
|
|
309
309
|
失败原因又不记,点开历史就是「只有我说了话、引擎没反应」—— 与需求派发那边
|
|
310
310
|
把失败原因当一条回答渲染是同一条口径(见 qaReqConvSnapshot)。 */
|
|
@@ -316,9 +316,9 @@ if(askAttBusy(from)){ toast("截图还在保存,稍等一下再发"); return f
|
|
|
316
316
|
}
|
|
317
317
|
run.rid = r.rid;
|
|
318
318
|
qaRuns.delete(0); qaRuns.set(r.rid, run); // 用真实 rid 换掉占位(end 事件按 rid 路由)
|
|
319
|
-
|
|
319
|
+
}catch(e){
|
|
320
320
|
qaRunEnd(h);
|
|
321
|
-
qaRenderDone(false, "调用失败 · " + String(e && e.message || e).slice(0, 80), qaRunHost(run));
|
|
321
|
+
qaRenderDone(false, "调用失败 · " + String(e && e.message || e).slice(0, 80), qaRunHost(run), null, e);
|
|
322
322
|
run.msgText = "运行失败\n" + String((e && e.message) || e);
|
|
323
323
|
qaSaveSnapshotFor(run.taskId, "error");
|
|
324
324
|
qaRuns.delete(run.rid || 0);
|
|
@@ -54,6 +54,18 @@ function normalizeEngineRow(r){
|
|
|
54
54
|
models: models,
|
|
55
55
|
modelLabels: Object.assign(labels, r.modelLabels || {}),
|
|
56
56
|
modelCredits: r.model_credits || r.modelCredits || {},
|
|
57
|
+
modelVendors: r.model_vendors || r.modelVendors || {},
|
|
58
|
+
/* 账号额度 / 每日签到 / 模型级套餐额度:必须在这里显式带过去,否则组件拿不到
|
|
59
|
+
(normalize 只透传白名单字段)。 */
|
|
60
|
+
creditLeft: r.credit_left || r.creditLeft || "",
|
|
61
|
+
creditSuffix: r.credit_suffix || r.creditSuffix || "",
|
|
62
|
+
modelQuota: r.model_quota || r.modelQuota || {},
|
|
63
|
+
checkinToday: (typeof r.checkin_today === "boolean") ? r.checkin_today : r.checkinToday,
|
|
64
|
+
checkinCredit: r.checkin_credit || r.checkinCredit || "",
|
|
65
|
+
/* 多账号引擎(codebuddy 系):没登录的槽位不显示 —— 「登录几个账号就显示几个」。 */
|
|
66
|
+
loggedIn: (typeof r.logged_in === "boolean") ? r.logged_in : r.loggedIn,
|
|
67
|
+
/* 登录的账号昵称(如 Darren / 玲),选择器画在引擎名下面。 */
|
|
68
|
+
account: r.account || "",
|
|
57
69
|
modelsNote: r.models_note || r.modelsNote || "",
|
|
58
70
|
workspace: r.workspace || "",
|
|
59
71
|
streaming: r.streaming,
|
|
@@ -68,6 +80,9 @@ function normalizeEngineRow(r){
|
|
|
68
80
|
if (HOST_DESK) return; // 宿主已经给了 desk,别覆盖它
|
|
69
81
|
const subs = [];
|
|
70
82
|
let ridSeq = 0;
|
|
83
|
+
/* 服务端 ask id(= SSE open 帧里的 id)。审批回 control 必须用它:
|
|
84
|
+
客户端自己的 rid 只是渲染编号,/desk/ask/{id}/control 认不出(2026-10-25)。 */
|
|
85
|
+
const askIds = new Map(); // rid → server ask id
|
|
71
86
|
/* ⚠️ 事件**延后一个微任务**再推给订阅者 —— 这不是洁癖,是修一个真实的竞态:
|
|
72
87
|
SSE 常把 `open` 与紧随其后的若干事件**放在同一个网络块**里回来(一次 write 合并成一段),
|
|
73
88
|
transport 会在一个 forEach 里把它们连着派给 onEvent。若在此**同步**转发,
|
|
@@ -100,9 +115,9 @@ function normalizeEngineRow(r){
|
|
|
100
115
|
const done = (r) => { if (!settled) { settled = true; resolve(r); } };
|
|
101
116
|
try {
|
|
102
117
|
transport.ask(payload, {
|
|
103
|
-
onOpen() { done({ ok: true, rid: rid }); },
|
|
118
|
+
onOpen(id) { if(id) askIds.set(rid, String(id)); done({ ok: true, rid: rid }); },
|
|
104
119
|
onEvent(ev) { emit(rid, ev); },
|
|
105
|
-
onEnd(code) { emit(rid, { type: "end", code: code }); done({ ok: true, rid: rid }); },
|
|
120
|
+
onEnd(code) { askIds.delete(rid); emit(rid, { type: "end", code: code }); done({ ok: true, rid: rid }); },
|
|
106
121
|
onError(e) {
|
|
107
122
|
const msg = String((e && e.message) || e);
|
|
108
123
|
emit(rid, { type: "error", reason: msg });
|
|
@@ -132,6 +147,19 @@ function normalizeEngineRow(r){
|
|
|
132
147
|
.then(() => ({ ok: true }))
|
|
133
148
|
.catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
134
149
|
},
|
|
150
|
+
/* 待授权(计划审批 / 工具授权)的裁决(2026-10-25):引擎正阻塞在 control_response 上,
|
|
151
|
+
必须走 control 的 ask_answer 原地写回;当成一条新消息发进去它不会读。
|
|
152
|
+
id 要用**服务端 ask id**(open 帧带回的),客户端 rid 只是渲染编号。 */
|
|
153
|
+
askApprove(p) {
|
|
154
|
+
const q = p || {};
|
|
155
|
+
const id = (q.rid != null && askIds.get(q.rid)) || askIds.get(Number(q.rid)) || "";
|
|
156
|
+
if (!id) return Promise.resolve({ ok: false, error: "这一轮还没拿到轮次 id(open 事件未到)" });
|
|
157
|
+
return Promise.resolve(transport.control({
|
|
158
|
+
id: id, op: "ask_answer",
|
|
159
|
+
request_id: q.request_id || "", allowed: !!q.allowed,
|
|
160
|
+
message: q.message || "", tool_name: q.tool_name || "",
|
|
161
|
+
})).then(() => ({ ok: true }), (e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
162
|
+
},
|
|
135
163
|
onAskEvent(cb) { subs.push(cb); return () => { const i = subs.indexOf(cb); if (i >= 0) subs.splice(i, 1); }; },
|
|
136
164
|
|
|
137
165
|
/* 引擎清单:transport 给的是 CLI 原始行(可能带 snake_case)→ 归一成上游形状。 */
|
|
@@ -172,6 +200,13 @@ function normalizeEngineRow(r){
|
|
|
172
200
|
},
|
|
173
201
|
/* 任务本地库(SQLite):magic-agent 没有本地库 —— 空清单 + 写入如实回「跳过」。
|
|
174
202
|
04-task-list.js 的 qaDb* 会把这些结果当「桥不可用」,于是只走 localStorage。 */
|
|
203
|
+
/* 历史附件回读(2026-10-25):历史会话里的参考图只有**绝对路径**(core 落盘 {path, mime},
|
|
204
|
+
没有回读地址)—— 内核画历史缩略图要当场按路径换 URL(attHistUrl,19-attachments)。
|
|
205
|
+
fileUrl 按绝对路径回读本机媒体文件(/desk/media?path=…),上传目录内外的都通;
|
|
206
|
+
mediaUrl 只对上传目录内文件有效(/desk/uploads/<名字>)。与组件宿主的
|
|
207
|
+
installDeskProbe(shared/conversation.js)同一口径。 */
|
|
208
|
+
fileUrl(path) { return (transport && transport.fileUrl) ? transport.fileUrl(path) : ""; },
|
|
209
|
+
mediaUrl(path) { return (transport && transport.mediaUrl) ? transport.mediaUrl(path) : ""; },
|
|
175
210
|
tasksList() { return Promise.resolve({ ok: true, tasks: [] }); },
|
|
176
211
|
tasksCreate(t) { return Promise.resolve({ ok: true, task: t }); },
|
|
177
212
|
tasksUpdate() { return Promise.resolve({ ok: true }); },
|
|
@@ -119,13 +119,27 @@ function qaMdlMenuBuild(){
|
|
|
119
119
|
const e = qaEngines.find(x=>x.engine === qaEngine) || null;
|
|
120
120
|
const credits = (e && e.modelCredits) || {};
|
|
121
121
|
const crOf = (id)=>{ const v = String(credits[qaModelText(id)] || "").trim(); return v ? "x" + v : ""; };
|
|
122
|
-
const ids = (
|
|
122
|
+
const ids = qaModelsOrdered(qaEngines, qaEngine,
|
|
123
|
+
(e && Array.isArray(e.models) ? e.models : []).map(m=>String(m||"")).filter(Boolean));
|
|
124
|
+
/* dsh 的模型 id 是 `route/model`:选择器里补一层路由名(同名模型可能挂在多条路由下,
|
|
125
|
+
只给末段分不出来)—— 与移动端 `.pick__cd` 同一口径(见 shared/conv/03-chat-message.js)。 */
|
|
126
|
+
const routeOf = (id)=> (String(qaEngine).trim().toLowerCase() === "dsh" && String(id).indexOf("/") > 0)
|
|
127
|
+
? String(id).split("/")[0] : "";
|
|
123
128
|
const autoName = auto ? qaModelName(auto, qaEngine) : "引擎默认";
|
|
124
129
|
const autoCr = auto ? crOf(auto) : "";
|
|
125
|
-
|
|
130
|
+
/* 第二层:dsh 是路由名,其余引擎是 core 给的供应商名(model_vendors);
|
|
131
|
+
套餐额度(MiniMax token plan)与它们同一行(note 本来就是一行 · 拼接)。 */
|
|
132
|
+
const subOf = (id)=> routeOf(id) || qaModelVendor(qaEngines, qaEngine, id);
|
|
133
|
+
const quotaOf = (id)=> qaModelQuota(qaEngines, qaEngine, id);
|
|
134
|
+
const autoNote = [autoName, subOf(auto), quotaOf(auto), autoCr].filter(Boolean).join(" · ");
|
|
135
|
+
const opts = [{ id:"", name:"自动", note: autoNote }]
|
|
126
136
|
.concat(ids.map(id=>{
|
|
127
137
|
const bits = [];
|
|
128
138
|
if(auto === id) bits.push("链首");
|
|
139
|
+
const sub = subOf(id);
|
|
140
|
+
if(sub) bits.push(sub);
|
|
141
|
+
const quota = quotaOf(id);
|
|
142
|
+
if(quota) bits.push(quota);
|
|
129
143
|
const cr = crOf(id);
|
|
130
144
|
if(cr) bits.push(cr);
|
|
131
145
|
return { id, name: qaModelName(id, qaEngine), note: bits.join(" · ") };
|
|
@@ -354,8 +368,9 @@ function qaStreamBind(){
|
|
|
354
368
|
},
|
|
355
369
|
onSession: (sid)=>{ qaSessionBindFor(run.taskId, sid); },
|
|
356
370
|
onError: (e)=>{
|
|
357
|
-
/* 显示哪一句:**优先根因**(reason),退回完整错误链(error
|
|
358
|
-
|
|
371
|
+
/* 显示哪一句:**优先根因**(reason),退回完整错误链(error)——理由同上游。
|
|
372
|
+
把整份错误信封(e)作为 detail 传给收尾卡(见 29-msg-variants 的 qaErrorCardNode)。 */
|
|
373
|
+
qaRenderDone(false, "调用失败 · " + String(e.reason || e.error || "未知错误").slice(0, 80), qaRunHost(run), null, e);
|
|
359
374
|
qaTaskMarkSidLostFor(run.taskId);
|
|
360
375
|
qaSaveSnapshotFor(run.taskId, "error", { holder: run.holder });
|
|
361
376
|
qaRuns.delete(run.rid);
|
|
@@ -53,13 +53,15 @@ function qaRailState(s){
|
|
|
53
53
|
* `waiting_approval` 必须算**进行中** —— 它没在跑,但也没结束;归到「已完成」的话,
|
|
54
54
|
* 用户会看到「我还在等它回话,它却已完成」。
|
|
55
55
|
* `idle`(常驻会话本轮已结束、进程还在等追加)**不算进行中**,落到「已完成」—— 与
|
|
56
|
-
* PC 按事件流显示的口径一致(2026-10-21)。
|
|
56
|
+
* PC 按事件流显示的口径一致(2026-10-21)。
|
|
57
|
+
* `gone`(记录写着在跑、进程已不在)**不算失败**:服务重启 / 终端关闭把进程带走
|
|
58
|
+
* 就是这一档,与移动端同一口径(2026-10-24 用户:「会话全部断连 红色」)。 */
|
|
57
59
|
function qaRailGroup(s){
|
|
58
60
|
if(!s) return "done";
|
|
59
61
|
const st = qaRailState(s);
|
|
60
62
|
if(s.alive || st === "running") return "live";
|
|
61
63
|
if(st === "waiting_approval") return "live";
|
|
62
|
-
if(st === "failed"
|
|
64
|
+
if(st === "failed") return "failed";
|
|
63
65
|
return "done";
|
|
64
66
|
}
|
|
65
67
|
function qaRailGroupLabel(s){
|
|
@@ -68,7 +70,7 @@ function qaRailGroupLabel(s){
|
|
|
68
70
|
if(g === "live") return st === "waiting_approval" ? "等审批" : "进行中";
|
|
69
71
|
if(g === "failed") return "失败";
|
|
70
72
|
if(st === "cancelled" || st === "stopped") return "已取消";
|
|
71
|
-
if(st === "interrupted") return "已打断";
|
|
73
|
+
if(st === "interrupted" || st === "gone") return "已打断";
|
|
72
74
|
return "已完成";
|
|
73
75
|
}
|
|
74
76
|
/* 运行中的会话**自动临时置顶**(2026-10-22 用户:「运行中的会话自动临时置顶」):
|
|
@@ -260,7 +262,11 @@ function qaRailApply(ctx, events){
|
|
|
260
262
|
ctx.h = null;
|
|
261
263
|
/* 移动端「会话托管」自动发的提问带 hosted:true:PC 回放同一条历史时也画成
|
|
262
264
|
「托管发送」,否则同一会话在两端会长成两张脸。 */
|
|
263
|
-
|
|
265
|
+
/* 历史附件(参考图)随 user 事件落盘({path, mime},见 internal/agent/attachments.go):
|
|
266
|
+
透传下去由内核的 attHistUrl 按路径换回读地址 —— 硬编码 [] 的话点开历史会话
|
|
267
|
+
参考图整排消失(2026-10-25 用户报障)。 */
|
|
268
|
+
qaRenderMine(String(ev.text || ""),
|
|
269
|
+
Array.isArray(ev.attachments) ? ev.attachments : [], { hosted: !!ev.hosted });
|
|
264
270
|
continue;
|
|
265
271
|
}
|
|
266
272
|
if(!ctx.h){ ctx.h = qaRenderAnswer(); if(!ctx.h) break; }
|
|
@@ -443,7 +449,12 @@ async function qaRailOpen(id){
|
|
|
443
449
|
}
|
|
444
450
|
const ctx = qaRailReplay(r.events, s, live);
|
|
445
451
|
if(live) qaRailTailStart(s, ctx, null);
|
|
446
|
-
|
|
452
|
+
/* 打开历史 = 从底部开始看(2026-10-25 用户:「历史会话也应该默认到最底部」)。
|
|
453
|
+
换 qaHistoryStickBottom(不再是裸 qaScrollIntoView):回放那次贴底在缩略图 /
|
|
454
|
+
字体加载完成之前,图一到位画布就被撑高、停在半空 —— 三重兜底见内核;
|
|
455
|
+
它同时置 qaUserAway=false,切会话残留的「用户离开底部」也一并复位。 */
|
|
456
|
+
if(typeof qaHistoryStickBottom === "function") qaHistoryStickBottom();
|
|
457
|
+
else qaScrollIntoView();
|
|
447
458
|
}
|
|
448
459
|
|
|
449
460
|
/** 新建对话:把当前上下文清掉,下一句话就是**全新会话**(不带给引擎任何 --session)。 */
|
|
@@ -21,8 +21,8 @@ const QA_IMG_LINE = /^\[图片\]\s*(\S.*?)\s*$/; // 飞书侧:一行一
|
|
|
21
21
|
清单漂移的后果是「同一条路径在 PC 有缩略图、在手机没有」,最难查)。 */
|
|
22
22
|
const QA_IMG_EXT_SRC = "png|jpe?g|gif|webp|bmp|svg|heic|avif";
|
|
23
23
|
const QA_IMG_EXT = new RegExp("\\.(?:" + QA_IMG_EXT_SRC + ")$", "i");
|
|
24
|
-
const QA_ART_EXT = /\.(?:png|jpe?g|gif|webp|bmp|svg|heic|avif|mp4|mov|m4v|webm|mkv|avi|mp3|wav|m4a|aac|ogg|flac|md|markdown|txt|pdf|docx?|xlsx?|pptx?|csv|zip|glb|gltf|psd|fig)$/i;
|
|
25
|
-
const QA_ART_EXT_SRC = "png|jpe?g|gif|webp|bmp|svg|heic|avif|mp4|mov|m4v|webm|mkv|avi|mp3|wav|m4a|aac|ogg|flac|md|markdown|txt|pdf|docx?|xlsx?|pptx?|csv|zip|glb|gltf|psd|fig";
|
|
24
|
+
const QA_ART_EXT = /\.(?:png|jpe?g|gif|webp|bmp|svg|heic|avif|mp4|mov|m4v|webm|mkv|avi|mp3|wav|m4a|aac|ogg|flac|md|markdown|txt|pdf|docx?|xlsx?|pptx?|csv|zip|glb|gltf|psd|fig|html?|xhtml)$/i;
|
|
25
|
+
const QA_ART_EXT_SRC = "png|jpe?g|gif|webp|bmp|svg|heic|avif|mp4|mov|m4v|webm|mkv|avi|mp3|wav|m4a|aac|ogg|flac|md|markdown|txt|pdf|docx?|xlsx?|pptx?|csv|zip|glb|gltf|psd|fig|html?|xhtml";
|
|
26
26
|
/** 整行就是一个产物路径(可带 `- ` 列表前缀)→ 摘成卡。
|
|
27
27
|
* 相对路径只在这一档认(必须带目录分隔符,光一个 `report.md` 太像正文了)。 */
|
|
28
28
|
const QA_ART_PATH_SRC = "(?:file://|/|~/|[A-Za-z0-9_.@-]+/)"; // 绝对 / `~/` / `file://` / 相对(带目录)
|