magic-agent 1.0.3 → 1.0.49
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 +254 -8
- package/README.md +48 -5
- package/client-ui/index.html +32 -0
- package/client-ui/pc/app.css +89 -1
- package/client-ui/pc/index.html +18 -8
- package/client-ui/pc/js/00-base.js +8 -30
- package/client-ui/pc/js/05-task-list-render.js +4 -1
- package/client-ui/pc/js/07-chat-stream-render.js +2 -85
- package/client-ui/pc/js/10-markdown.js +3 -171
- package/client-ui/pc/js/18-askbox.js +2 -176
- package/client-ui/pc/js/90-glue.js +1 -1
- package/client-ui/pc/js/91-workbench-stubs.js +4 -10
- package/client-ui/pc/js/93-rail.js +10 -3
- package/client-ui/shared/conv/01-dom.js +52 -0
- package/client-ui/shared/conv/02-media-kinds.js +34 -0
- package/client-ui/shared/conv/03-chat-message.js +1561 -0
- package/client-ui/shared/conv/06-ask-answer.js +377 -0
- package/client-ui/shared/conv/07-stream-delegation.js +158 -0
- package/client-ui/{pc/js → shared/conv}/09-stream-apply.js +121 -6
- package/client-ui/shared/conv/10-markdown-render.js +252 -0
- package/client-ui/shared/conv/18-askbox-markup.js +185 -0
- package/client-ui/{pc/js → shared/conv}/19-attachments.js +365 -21
- package/client-ui/shared/conv/27-conv-ledger.js +381 -0
- package/client-ui/{pc/js → shared/conv}/29-msg-variants.js +112 -3
- package/client-ui/shared/conv/README.md +257 -0
- package/client-ui/shared/conv/conv.css +1638 -0
- package/client-ui/shared/conv/dom-map.md +170 -0
- package/client-ui/shared/conv/manifest.json +44 -0
- package/client-ui/shared/conversation.css +368 -190
- package/client-ui/shared/conversation.js +1722 -788
- package/client-ui/shared/host-glue.js +143 -0
- package/client-ui/shared/transport-http.js +130 -6
- package/client-ui-mobile/index.html +2417 -103
- package/cmd/magic-client/clientctl.go +273 -0
- package/cmd/magic-client/main.go +71 -5
- package/go.mod +6 -3
- package/go.sum +4 -1
- package/internal/agent/arkclaw.go +3 -2
- package/internal/agent/arkclaw_stream.go +126 -8
- package/internal/agent/arkclaw_stream_test.go +97 -15
- package/internal/agent/arkclaw_test.go +3 -3
- package/internal/agent/attachments.go +18 -5
- package/internal/agent/attachments_test.go +7 -1
- package/internal/agent/capability_test.go +11 -1
- package/internal/agent/claude.go +96 -158
- package/internal/agent/codebuddy.go +108 -133
- package/internal/agent/codebuddy_gateway.go +8 -4
- package/internal/agent/codebuddy_gateway_stream.go +82 -2
- package/internal/agent/codebuddy_gateway_test.go +236 -1
- package/internal/agent/codebuddy_stream_test.go +262 -0
- package/internal/agent/codex.go +50 -9
- package/internal/agent/codex_stream_test.go +30 -0
- package/internal/agent/codex_test.go +24 -0
- package/internal/agent/custom_models.go +109 -3
- package/internal/agent/custom_models_test.go +74 -0
- package/internal/agent/dsh.go +464 -130
- package/internal/agent/dsh_resume.go +258 -0
- package/internal/agent/dsh_sdk.go +68 -17
- package/internal/agent/dsh_sdk_test.go +102 -2
- package/internal/agent/dsh_test.go +409 -36
- package/internal/agent/engine.go +26 -6
- package/internal/agent/engine_base.go +46 -2
- package/internal/agent/engine_base_test.go +29 -0
- package/internal/agent/engine_test.go +101 -0
- package/internal/agent/env.go +69 -1
- package/internal/agent/itemtracker.go +271 -0
- package/internal/agent/launch.go +192 -0
- package/internal/agent/launch_test.go +206 -0
- package/internal/agent/launch_unix.go +15 -0
- package/internal/agent/launch_windows.go +24 -0
- package/internal/agent/limitnotice.go +103 -0
- package/internal/agent/limitnotice_test.go +211 -0
- package/internal/agent/llmengine.go +2 -2
- package/internal/agent/models.go +3 -2
- package/internal/agent/openclaw.go +4 -3
- package/internal/agent/openclaw_acp.go +7 -0
- package/internal/agent/openclaw_test.go +2 -2
- package/internal/agent/permission.go +100 -11
- package/internal/agent/permission_test.go +100 -36
- package/internal/agent/runcmd.go +13 -3
- package/internal/agent/runner.go +7 -1
- package/internal/agent/runner_test.go +1 -1
- package/internal/agent/stream.go +777 -43
- package/internal/agent/stream_test.go +3 -2
- package/internal/agent/stream_timeline_test.go +905 -0
- package/internal/agent/streamjson.go +36 -2
- package/internal/agent/toolkind.go +20 -1
- package/internal/agent/toolkind_test.go +7 -1
- package/internal/agent/tools_suffix_test.go +10 -3
- package/internal/agent/trae.go +5 -3
- package/internal/agent/workspace_test.go +52 -0
- package/internal/cli/ask.go +260 -33
- package/internal/cli/ask_test.go +15 -11
- package/internal/cli/attach_test.go +32 -20
- package/internal/cli/clientcmd.go +156 -0
- package/internal/cli/events.go +53 -8
- package/internal/cli/events_test.go +230 -0
- package/internal/cli/keepalive_test.go +101 -6
- package/internal/cli/permission_test.go +5 -1
- package/internal/cli/root.go +174 -32
- package/internal/cli/session.go +175 -28
- package/internal/cli/session_test.go +144 -5
- package/internal/cli/sessionlog.go +385 -38
- package/internal/cli/sessionlog_test.go +708 -0
- package/internal/cli/sessionlog_user_test.go +142 -1
- package/internal/cli/sessiontitle.go +141 -0
- package/internal/client/browse.go +456 -0
- package/internal/client/browse_test.go +199 -0
- package/internal/client/cors_test.go +35 -0
- package/internal/client/daemon.go +525 -0
- package/internal/client/daemon_test.go +212 -0
- package/internal/client/detach_unix.go +19 -0
- package/internal/client/detach_windows.go +27 -0
- package/internal/client/pair.go +3 -3
- package/internal/client/plugins.go +376 -0
- package/internal/client/plugins_test.go +223 -0
- package/internal/client/proxy.go +374 -0
- package/internal/client/proxy_test.go +212 -0
- package/internal/client/service.go +1464 -48
- package/internal/client/service_test.go +649 -206
- package/internal/client/session_events_test.go +130 -0
- package/internal/client/wsproxy.go +113 -0
- package/internal/client/wsproxy_test.go +102 -0
- package/internal/session/delete.go +151 -0
- package/internal/session/delete_test.go +103 -0
- package/internal/session/session.go +155 -20
- package/internal/session/session_append.go +79 -21
- package/internal/session/session_test.go +124 -10
- package/internal/session/sessionlog.go +572 -6
- package/internal/session/sessionlog_read_test.go +410 -0
- package/internal/session/title.go +486 -0
- package/internal/session/title_test.go +298 -0
- package/npm/bin/magic-agent.js +101 -10
- package/npm/bin/magic-client.js +118 -0
- 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/npm/install.js +36 -13
- package/npm/lib/platform.js +27 -5
- package/package.json +8 -3
- package/client-ui/pc/js/03-chat-message.js +0 -880
- package/client-ui/pc/js/06-ask-answer.js +0 -204
- /package/client-ui/{pc/js → shared/conv}/08-ask-event-map.js +0 -0
- /package/client-ui/{pc → shared}/vendor/highlight.min.js +0 -0
- /package/client-ui/{pc → shared}/vendor/marked.min.js +0 -0
- /package/client-ui/{pc → shared}/vendor/purify.min.js +0 -0
package/CLIENT-CONTRACT.md
CHANGED
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
| 面 | 命令 / 通道 | 状态 | 实现 |
|
|
13
13
|
| --- | --- | --- | --- |
|
|
14
14
|
| 引擎调用 | `-e <engine> -p "<prompt>"`(含超时、重试、退出码) | 稳定 | `internal/cli/ask.go` |
|
|
15
|
-
| 探测与能力 | `--engines [--no-models]`:`ok` / `bin` / `install` / `version` / `models` / `capabilities` … | 稳定 | `internal/cli/root.go` |
|
|
15
|
+
| 探测与能力 | `--engines [--no-models]`:`ok` / `bin` / `install` / `version` / `models` / `permission` / `reasoning` / `capabilities` … | 稳定 | `internal/cli/root.go` |
|
|
16
16
|
| 契约 envelope | `--contract`:`{contractVersion, features, engines[]}`,`engines[]` 与 `--engines` **同构** | v1 | `internal/cli/root.go` + `internal/agent/capability.go` |
|
|
17
|
-
| 事件流 | `--stream --events`:NDJSON,每行带 `v` / `seq`,首行 `ready` | v1 | `internal/cli/events.go` |
|
|
17
|
+
| 事件流 | `--stream --events`:NDJSON,每行带 `v` / `seq`,首行 `ready`;文本/轮次事件带状态收敛四元组(`item_id` / `item_revision` / `status` / `snapshot`,见 1.1.1) | v1 | `internal/cli/events.go` |
|
|
18
18
|
| 控制回传 | `--stream --control`:stdin 收 NDJSON 命令(`ping` / `interrupt` / `stop` / `answer`) | v1 | `internal/cli/control.go` |
|
|
19
19
|
| 会话追加 | `--stream --keep-alive` + `--append <id>`;`--sessions` / `--stop <id>` | 稳定 | `internal/cli/session.go` |
|
|
20
20
|
|
|
21
21
|
### 1.1 事件流形状
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
`--stream` 的事件形状(`--events` 只额外加 `v` / `seq` 与 core 自己的事件):
|
|
24
24
|
|
|
25
25
|
```json
|
|
26
26
|
{"type":"thinking","text":"…"}
|
|
@@ -28,11 +28,17 @@
|
|
|
28
28
|
{"type":"tool_use","text":"<args JSON>","name":"Bash","id":"toolu_x"}
|
|
29
29
|
{"type":"tool_result","text":"<output>","id":"toolu_x"}
|
|
30
30
|
{"type":"turn_end","text":"<该轮正文>","session_id":"…"}
|
|
31
|
+
{"type":"turn_failed","session_id":"…","error":"<完整错误链>","reason":"<根因>","status":"failed"}
|
|
31
32
|
{"type":"ask","text":"<一行摘要>","ask":{…}}
|
|
32
33
|
{"type":"result","engine":"…","model":"…","session_id":"…","text":"<全文>","thinking":"…","tools":[…]}
|
|
33
34
|
{"type":"error","engine":"…","reason":"<根因>","error":"<完整错误链>"}
|
|
34
35
|
```
|
|
35
36
|
|
|
37
|
+
⚠️ 上面省略了 1.1.1 的收敛字段(omitempty,**开不开 `--events` 都会出现**)。
|
|
38
|
+
1.1.1 之前这里写的是「历史形状(逐字节不变)」,实现并非如此 ——
|
|
39
|
+
`--stream` 与 `--events` 共用同一个 payload,两条路径都会带上新字段。
|
|
40
|
+
此处按实际实现更正;消费方按「忽略未知字段」处理即可向后兼容。
|
|
41
|
+
|
|
36
42
|
开 `--events` 后**字段集不变**,每行多 `v` / `seq`(`seq` 从 1 单调 +1),
|
|
37
43
|
并额外产出四类 core 自己的事件:`ready`(首行,带 `engine` / `model`)、
|
|
38
44
|
`pong`、`interrupted`、`stopped`、`answered`、`control_error`。
|
|
@@ -40,6 +46,87 @@
|
|
|
40
46
|
两条不变量(有测试钉住):**每轮一定以 `result` 或 `error` 收尾**;
|
|
41
47
|
`--events` 与 `--stream` 的字段集只差 `v` / `seq`。
|
|
42
48
|
|
|
49
|
+
### 1.1.1 状态收敛字段(2026-10-02 新增,只增字段不升版本)
|
|
50
|
+
|
|
51
|
+
`thinking` / `text` / `turn_end` / `turn_failed` 四类事件带一组收敛字段
|
|
52
|
+
(非空才出现,omitempty):
|
|
53
|
+
|
|
54
|
+
| 字段 | 含义 |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `item_id` | 稳定 item 标识。**思考与正文是两条 item**:`<engine>_msg_<原生 message id>` 与 `<engine>_think_<同一 id>`;无原生 id 时退化为 `<engine>_msg_local_<n>` |
|
|
57
|
+
| `item_revision` | 该 item 的第几个版本,从 1 起**单调递增**(每条 item 各自计数) |
|
|
58
|
+
| `status` | `running`(还在增长)/ `final`(已定型)/ `failed`(异常终止) |
|
|
59
|
+
| `snapshot` | 该 item 到此刻的**整段内容**(累积快照,不是增量)——⚠️ **只在实时流里**;落盘不写(见下方「落盘不存 snapshot」) |
|
|
60
|
+
| `error` / `reason` | 仅 `turn_failed`:完整错误链 / 最内层根因 |
|
|
61
|
+
|
|
62
|
+
⚠️ **`text` 仍是增量片段**,不是 snapshot。两者并存是刻意的:
|
|
63
|
+
`text` 服务终端直打与老消费者逐行拼接,`snapshot` 服务「按 item 覆盖」的
|
|
64
|
+
多端同步。消费方二选一即可。
|
|
65
|
+
|
|
66
|
+
⚠️ **为什么思考与正文分成两条 item**(别合并回去):共享一个 `item_id` 时,
|
|
67
|
+
后到的 `text` 帧会覆盖先到的 `thinking` 帧(revision 更高),
|
|
68
|
+
**思考内容静默消失**。`text` 是增量所以终端直打看不出来,
|
|
69
|
+
出问题的是走 `snapshot` 的历史回放与多端同步。
|
|
70
|
+
两条 item 内嵌**同一个原生 message id**,要合并展示时按它关联即可。
|
|
71
|
+
|
|
72
|
+
⚠️ **一轮里可能有多个 `item_id`**:工具调用后继续回答会产生多条
|
|
73
|
+
assistant message,每条各有一个 item(快照不跨 item 累积)。
|
|
74
|
+
把一轮当「一个 item」处理的消费方会漏掉前面的回复。
|
|
75
|
+
|
|
76
|
+
⚠️ **`turn_end` 只收尾正文 item**:result 行里是整条消息的正文,
|
|
77
|
+
不含思考链。给思考 item 也发 `final` 会让它的 `snapshot` 回退成空。
|
|
78
|
+
思考 item 的终止靠轮级信号(`turn_end` / `turn_failed`)判定。
|
|
79
|
+
|
|
80
|
+
**推荐的消费方式**(覆盖式 upsert):
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
按 item_id 维护一张表;收到事件时
|
|
84
|
+
若 item_revision <= 表中已有版本 → 丢弃(重复或乱序的旧帧,天然幂等)
|
|
85
|
+
否则 → 用 snapshot 覆盖,状态取 status
|
|
86
|
+
收到 status=final 或 status=failed → 该 item 生命周期结束,不再等增长
|
|
87
|
+
收到 turn_end / turn_failed → 本轮全部 item 结束
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
⚠️ 一轮里可能先出现 `turn_failed`(引擎没吐 result 行)、
|
|
91
|
+
再出现 `type:"error"`(core 层收尾)。二者语义不同:
|
|
92
|
+
前者是**流级**截断(含已流出的半截正文 + 其 item 状态),
|
|
93
|
+
后者是**进程级**错误报告。消费方按 `item_id` 归并时以 `turn_failed` 标记
|
|
94
|
+
那些 item 的终态即可,不要把 `error` 当成第二个 item 事件。
|
|
95
|
+
|
|
96
|
+
会话落盘(`magic-agent session log <id>` 与插件 `/desk/session/{id}/messages`)
|
|
97
|
+
落的是同一组字段(**除了 snapshot**,见下),所以**历史回放与实时渲染的收敛语义完全一致**。
|
|
98
|
+
|
|
99
|
+
⚠️ **落盘不存 snapshot(2026-10-20)**:磁盘存档里每条事件**没有** `snapshot` 键。
|
|
100
|
+
为什么:snapshot 是「该 item 到此刻的累积整段」,每条增量事件都带一份 ⇒
|
|
101
|
+
**二次方膨胀**。实测一条 18.6 万条事件的会话(思考流被切成 5799 个碎片):
|
|
102
|
+
日志 1.8GB,其中 1.83GB(95.6%)是 snapshot,真正的增量正文只有 16MB。
|
|
103
|
+
落盘只留**增量 `text` + 收敛三元组**(`item_id` / `item_revision` / `status`)——
|
|
104
|
+
回放侧本来就是只用增量自己拼(对话内核的 `appendThinking` / `appendText`),
|
|
105
|
+
全仓没有任何消费方读**落盘**的事件级 snapshot。
|
|
106
|
+
⚠️ 实时流(`--stream` 的 NDJSON)**仍然照旧带 snapshot** —— 那是「按 item 覆盖」的
|
|
107
|
+
多端同步需要的,与存档是两回事。
|
|
108
|
+
⚠️ 2026-10-20 之前写的**旧档仍带 snapshot**:读它们时靠 `--session-log-lean` /
|
|
109
|
+
`?lean=1` 裁掉(下面那条),否则一次读就能把几百 MB 灌进内存。
|
|
110
|
+
|
|
111
|
+
⚠️ **覆盖范围(2026-10-02)**:收敛字段目前覆盖
|
|
112
|
+
claude / codebuddy(+ai) / trae(走统一的 `streamAccumulator`)、
|
|
113
|
+
codebuddy-gateway 与 arkclaw(在自己的 emit 路径里直接持有 tracker)。
|
|
114
|
+
codex / dsh(+sdk) / llm / openclaw 暂不带 `item_id`,
|
|
115
|
+
消费方需按「无 item 的纯增量」兼容处理。
|
|
116
|
+
|
|
117
|
+
各引擎的**原生 item id 取法不同**,消费方不要按前缀反推语义,
|
|
118
|
+
只按 `item_id` 分组即可:
|
|
119
|
+
|
|
120
|
+
| 引擎 | item id | 取自 |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| claude / codebuddy / trae | `<engine>_msg_<native>` / `<engine>_think_<同一 id>` | 原生 assistant message id |
|
|
123
|
+
| codebuddy-gateway | `codebuddy-gateway_msg_<runId>` | 网关分配的 runId(一个 run 恒定) |
|
|
124
|
+
| arkclaw | `arkclaw_msg_<taskId>` | A2A `result.id`(**不是** `contextId`) |
|
|
125
|
+
|
|
126
|
+
⚠️ **arkclaw 的 `contextId` 是会话锚点、跨轮不变**,
|
|
127
|
+
拿它当 item id 会让同一会话每一轮共用一条 item、后一轮覆盖前一轮。
|
|
128
|
+
它只会出现在 `session_id` 字段里。
|
|
129
|
+
|
|
43
130
|
### 1.2 控制命令
|
|
44
131
|
|
|
45
132
|
```json
|
|
@@ -71,11 +158,18 @@
|
|
|
71
158
|
| 契约与引擎清单 | `GET /desk/contract` · `GET /desk/engines[?models=1]` | 已实现 |
|
|
72
159
|
| 应用配置 | `GET /desk/profile` | 已实现 |
|
|
73
160
|
| 模块清单 | `GET /desk/modules`(从 profile 派生:id / 入口 / 声明的表) | 已实现 |
|
|
74
|
-
| 会话登记表 | `GET /desk/sessions`(core 的 `--sessions
|
|
161
|
+
| 会话登记表 | `GET /desk/sessions`(core 的 `--sessions`,跨进程持久;**只回本客户端发起的会话**) | 已实现 |
|
|
75
162
|
| 项目业务接口 | `ANY /api/*` → 原样转发到 profile 的 `backend.url` | 已实现 |
|
|
76
163
|
| 发起一轮 | `POST /desk/ask`(SSE 中继 core 的 NDJSON,前置 `event: open`,收尾 `event: end`) | 已实现 |
|
|
77
164
|
| 控制与回审批 | `POST /desk/ask/{id}/control`(与 core 的 `--control` 同协议,插件不解释 op) | 已实现 |
|
|
78
|
-
|
|
|
165
|
+
| 断线续传 | `GET /desk/ask/{id}/events?after=<seq>`(按**本轮流 seq** 增量补拉;带 `done` / `code` / `stderr` / `snapshotRequired`;`after=0` 时首条是本轮**合成的用户提问**,见 2.4) | 已实现 |
|
|
166
|
+
| 运行中列表 | `GET /desk/asks`(**本进程**正在跑的轮次,每项 `{id, engine, started, session?, run?}`;与 sessions 的区别见下) | 已实现 |
|
|
167
|
+
| 本体文件出流 | `GET /desk/media?path=<绝对路径>[&w=<px>]`(`?token=` 供 `<img>` / `<video>` 直接引用;**只发 image / video / audio**,`Range` 可拖动)。带 `w` 时按宽缩放并重编码(`64..4096`,失败/更大则回退原图,结果内存缓存);移动端传输层自动追加 `w=720` | 已实现 |
|
|
168
|
+
| 上传文件回读 | `GET /desk/uploads/{name}[?w=<px>]`(同上 `w` 语义) | 已实现 |
|
|
169
|
+
| 产物存在性探测 | `POST /desk/path-probe`:`{base, paths[]}` → `{ok, items:[{path, abs, exists, size}]}`;相对路径按 `base` 拼、`~/` 展开、`file://` 剥前缀、只认 regular 文件、最多 50 条。对话内核的「本轮产物」靠它把正文里的路径变成卡片(宿主经 `window.desk.pathProbe` 供进来) | 已实现 |
|
|
170
|
+
| 产物文本回读 | `POST /desk/path-read`:`{path, base}` → `{ok:true, text, size, path}` / `{ok:true, tooBig:true, size}` / `{ok:true, binary:true, size}` / `{ok:false, error}`,统一 200。上限 512 KB;前 8000 字节含 NUL 判二进制;`~/`、`file://`、相对 `base` 同 `path-probe` 口径。移动端产物卡「查看」经 `window.desk.pathRead` 用它打开 Markdown / 文本 | 已实现 |
|
|
171
|
+
| 插件目录 | `GET /desk/plugins` → `{plugins:[{id, name, cli, tagline, description, requirements?, homepage?, installed, version?, bin?, sourcePath?, sourceExists, install, upgrade, examples[], usePrompt, note?}]}`。**这不是引擎**(core 的 `--engines` 里没有它们):它是「发现 → 插件」里那两个工程 CLI(`magic-shorts` / `magic-video`)的介绍与安装状态。`installed` / `version` 由服务端探测(`exec.LookPath` + 可执行文件所属 npm 包的 `package.json`,不跑 `--version`),客户端只显示不推断 | 已实现 |
|
|
172
|
+
| 插件安装/升级 | `POST /desk/plugins/{id}/{install\|upgrade}` → SSE:`event: open`(`{id, action, commands[]}`)→ 每条命令 `event: step` → 输出逐行 `event: line` → `event: end`(`{ok, code, error?, plugin}`)。**命令只来自服务端那张固定表**(argv,不过 shell),客户端只传 id 与动作,未知 id/动作一律 404 —— 这条接口不接受客户端传命令 | 已实现 |
|
|
79
173
|
| 静态资源 | `GET /`(`--ui-dir` + profile 的 `ui.entry`) | 已实现 |
|
|
80
174
|
| 鉴权 | `X-Magic-Token` 头或 `?token=`;默认只绑 `127.0.0.1`;`--lan` 时绑 `0.0.0.0` 且令牌强制 ≥24 字符(见 2.4) | 已实现 |
|
|
81
175
|
|
|
@@ -154,6 +248,7 @@
|
|
|
154
248
|
| `prompt` | 必填 |
|
|
155
249
|
| `model` / `workspace` | 可选,透传给 core 的 `-m` / `-w` |
|
|
156
250
|
| `permission` | 可选,四档授权模型(`manual` \| `accept-edits` \| `auto` \| `full`),透传给 core 的 `--permission`。**空 = 不传**,由 core 用它自己的默认档(`agent.DefaultPermissionTier`)—— 刻意没有默认预设,见下 |
|
|
251
|
+
| `reasoning` | 可选,思考档位(`off` \| `minimal` \| `low` \| `medium` \| `high` \| `xhigh` \| `max`),透传给 core 的 `--reasoning`。**空 = 不传**,core 自己的默认档是 **`max`**(开着)—— 所以「关闭思考」必须**显式传 `off`**,省略等于开着 + 最高档 |
|
|
157
252
|
| `session` | 可选,续接一个已有会话(传 `/desk/sessions` 里的 id)→ core 的 `--session` |
|
|
158
253
|
| `idle` | 可选,常驻会话空闲收工时长;默认 `0` = 本轮结束就收工 |
|
|
159
254
|
|
|
@@ -167,9 +262,63 @@
|
|
|
167
262
|
`capabilities` 里读 `permission`;读不到时前端应把选择器**禁用**而不是传过去。
|
|
168
263
|
`full` 等同 `--dangerously-skip-permissions`,两端界面都应把它显式标成危险档。
|
|
169
264
|
|
|
265
|
+
`reasoning`(2026-10-20 只增字段,不升 `contractVersion`)的能否下发,以 `/desk/engines`
|
|
266
|
+
每行的 `reasoning` 字段为准:老版本 core **没有**这个字段,也就**不认识** `--reasoning`
|
|
267
|
+
(传了会 exit 2 打死整轮),客户端此时应**不下发**。当前 `claude` / `codebuddy` /
|
|
268
|
+
`codebuddy-ai` / `codex` 为 `true`;其余引擎 core 静默忽略该参数(对**引擎**不报错,
|
|
269
|
+
与「老 CLI 不认识 flag」是两件事,别混)。
|
|
270
|
+
|
|
170
271
|
`asks` 是**本进程内存里**的运行中轮次,`sessions` 是 core 的持久登记表 —— 两者不是一回事,
|
|
171
272
|
界面上的「运行中 N」用前者,「最近会话」用后者。
|
|
172
273
|
|
|
274
|
+
`asks` 每项带 `session`(2026-10-20 只增字段,不升 `contractVersion`):这一轮挂在哪个会话上。
|
|
275
|
+
客户端打开某个会话时,若 `asks` 里有同 `session` 的项,说明该会话还有一轮在跑 —— 直接把画面
|
|
276
|
+
接回去:历史只回放到最后一条 `user` 事件,其余用 `GET /desk/ask/{id}/events?after=0` 补齐
|
|
277
|
+
(**不要**拿会话日志里的 seq 当游标:日志 seq 是文件级跨轮接号的,与 ask 缓冲的每轮 seq 不同口径)。
|
|
278
|
+
新会话在 core 吐出第一条带 `session_id` 的事件之前,`session` 可能为空。
|
|
279
|
+
|
|
280
|
+
`after=0` 补回的第一条是本轮**合成的用户提问**(2026-10-20 只增字段,不升 `contractVersion`):
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
{"type":"user","text":"用户原话","attachments":[{"path":"/abs/x.png"}],"prompt_id":"u1","at":"RFC3339"}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
core 的 stdout 事件流里没有 user 事件(它只写进会话日志),而续传补的就是 ask 缓冲,所以由插件
|
|
287
|
+
在 `POST /desk/ask` 启动前合成这一条。客户端必须照做三件事:
|
|
288
|
+
|
|
289
|
+
1. **只认 `after=0` 时给的这一条**(`after>0` 不再返回);它**没有 `seq`**,不能拿它推进游标;
|
|
290
|
+
2. 按 `prompt_id` 去重 —— core 首行到达前客户端会反复以 `after=0` 轮询,没有去重会画出多个气泡;
|
|
291
|
+
3. 若历史重放已经画过这条 user(日志 partial 回放),续传端要跳过它一次,别画两遍。
|
|
292
|
+
它只进 ask 缓冲、**不进** `/desk/ask` 的 SSE 直播流(实时路径上客户端自己已经画过用户气泡)。
|
|
293
|
+
|
|
294
|
+
`asks` 另有 `run`(同一批只增字段):core 本次运行的 `run_id`(取自 ready 行)。引擎整轮不吐
|
|
295
|
+
`session_id` 时(如 llm),登记表与客户端会话列表里这条会话只有 `run_id`,`session` 会一直是空的 ——
|
|
296
|
+
此时客户端要用 `run` 与会话 id 比对来认领这一轮(两个字段都要比)。
|
|
297
|
+
|
|
298
|
+
会话行上有两个「谁发起的」字段,**都不是客户端填的,由插件注入**(core 落进登记表):
|
|
299
|
+
|
|
300
|
+
| 字段 | 取值 | 谁写 | 界面怎么用 |
|
|
301
|
+
| --- | --- | --- | --- |
|
|
302
|
+
| `source` | `mobile` \| `pc` | 插件按请求 Origin / 客户端自报判定 | 显示手机 / 电脑标志 |
|
|
303
|
+
| `client` | profile 的 `appId`(`app.bundleId`,缺省取 `app.name` 规范化后的值,如 `magic-client`) | 插件按自己的身份注入 | 列表过滤:见下 |
|
|
304
|
+
|
|
305
|
+
⚠️ 登记表目录(`~/.magic-agent/sessions`)是**全机共享**的:用户手工跑的
|
|
306
|
+
`magic-agent -p …`、CI、别的 magic-* 应用都在往里写。所以 `/desk/sessions` 只回
|
|
307
|
+
**本客户端 + 同族 `magic-*` 来源**的行(`internal/client/service.go` 的
|
|
308
|
+
`extraSessionClients`),其余滤掉 —— 原口径见用户 2026-10-12「不是 magic-agent 客户端
|
|
309
|
+
调用的任务不显示」。
|
|
310
|
+
|
|
311
|
+
- 本插件自己注入的 `appId`:手机 / 桌面从本客户端发起的会话;
|
|
312
|
+
- `magic-test`:**掌天瓶桌面壳**发起的会话(它不经本插件,直接 exec core 并注入
|
|
313
|
+
`MAGIC_AGENT_CLIENT=magic-test`,见 magic-test 的 `desktop/agent-cli.cjs`)——
|
|
314
|
+
用户 2026-10-15:「通过 magic-test 新建的会话要在移动端 h5 看到,同时 h5 上要能同时
|
|
315
|
+
看到 magic-test 与 magic-agent 两种来源的会话」;
|
|
316
|
+
- `magic-agent`:core 自身 / 直连 CLI 的显式标识。
|
|
317
|
+
|
|
318
|
+
代价:名单之外的记录(匿名 `client` = 手工跑 / CI / 旧记录、以及不相干应用)**不出现在
|
|
319
|
+
客户端列表里**(磁盘上仍然留着,登记表**不再按时间清理**,见 3.4 的存档开关)——
|
|
320
|
+
想看全机视图用 core 的 `magic-agent --sessions`,那条命令**不过滤**。
|
|
321
|
+
|
|
173
322
|
### 2.2 扩展接口(业务项目对着写)
|
|
174
323
|
|
|
175
324
|
| 接口 | 状态 | 说明 |
|
|
@@ -200,15 +349,24 @@
|
|
|
200
349
|
|
|
201
350
|
| 文件 | 角色 |
|
|
202
351
|
| --- | --- |
|
|
352
|
+
| `client-ui/shared/conv/` | **对话内核**(分层分片):一条事件流 → 画成对话的**唯一实现**(消息 / 思考 / 工具组与「任务耗时」大组 / 提问卡 / 附件与产物 / 事件归一)。宿主只使用它,见 `client-ui/conv/README.md` 与 `manifest.json` 的 `hostSymbols`;校验 `npm run check:conv` |
|
|
203
353
|
| `client-ui/shared/conversation.js` | **组件本体**(渲染 + 流式 + 控制 + 三项选择器)。PC 与移动端共用,是唯一来源 |
|
|
204
354
|
| `client-ui/shared/conversation.css` | 组件样式。结构尺寸共用;颜色走 3.3 的变量契约,由宿主供值 |
|
|
205
355
|
| `client-ui/shared/transport-http.js` | 传输层的**可选参考实现**(HTTP 版,打 `/desk/*`)。组件不依赖它 |
|
|
206
356
|
| `client-ui/index.html` | PC 宿主:oklch 令牌 + 一条顶栏 + 挂组件 |
|
|
207
|
-
| `client-ui-mobile/index.html` | 移动端宿主:AA 安卓端那套十六进制令牌 + 移动形态顶栏 +
|
|
357
|
+
| `client-ui-mobile/index.html` | 移动端宿主:AA 安卓端那套十六进制令牌 + 移动形态顶栏 + 挂组件;「我的」里可设默认引擎/模型(`localStorage` 的 `magic.engine` / `magic.model`,新建任务作为 `engine` / `model` 初值,会话内落定后由 `onPick` 回写);「发现 → 插件」是 `GET /desk/plugins` 的目录页(介绍 / 安装状态 / 安装升级 / 让它干活) |
|
|
208
358
|
|
|
209
359
|
移动端打包时(magic-test 的 `tools/build-mobile-www.cjs`)会把 `client-ui/shared/`
|
|
210
360
|
一并复制进 `www/shared/` —— 页面按同级 `shared/` 引用它,缺了这个复制对话区会是空白。
|
|
211
361
|
|
|
362
|
+
> **组件宿主页必须先引**:`shared/vendor/marked.min.js` + `shared/vendor/purify.min.js`
|
|
363
|
+
> (并设 `window.QA_HLJS_URL` 指向 highlight.js),再引内核分片 `shared/conv/01-dom.js`、
|
|
364
|
+
> `02-media-kinds.js`、`08-ask-event-map.js`、`10-markdown-render.js`、`29-msg-variants.js`
|
|
365
|
+
> —— 事件归一、工具归类与人话标题、diff 判定、markdown 渲染栈、`escHtml`、产物后缀清单的唯一
|
|
366
|
+
> 实现都在内核里(阶段三起),组件只转发;缺了 `create()` 开场就报错(见 `shared/conv/README.md`)。
|
|
367
|
+
> 宿主只引一部分分片是允许的,`tests/check-conv-shards.mjs --host-html=<宿主页>` 会按这一页
|
|
368
|
+
> 实际引的那几片算它要提供哪些符号。
|
|
369
|
+
|
|
212
370
|
### 3.2 组件 API
|
|
213
371
|
|
|
214
372
|
```js
|
|
@@ -221,6 +379,7 @@ const conv = MagicConversation.create({
|
|
|
221
379
|
titleEl, // 可选:宿主显示会话标题的元素
|
|
222
380
|
onEvent, // 可选:每个 core 事件透传一份(只读,不许改写对话流)
|
|
223
381
|
onBusy, // 可选:一轮开始/结束
|
|
382
|
+
onPick, // 可选:用户落定引擎/模型时回调 (engine, model),宿主可记成默认值;只浏览左列不回调
|
|
224
383
|
});
|
|
225
384
|
conv.send('…');
|
|
226
385
|
conv.interrupt(); conv.stop(); conv.answer('…');
|
|
@@ -269,17 +428,104 @@ magic-agent 把会话的查询与修改都做成 CLI 命令,调用方 exec 即
|
|
|
269
428
|
|
|
270
429
|
| 操作 | 命令 |
|
|
271
430
|
| --- | --- |
|
|
272
|
-
| 列出会话 | `magic-agent --sessions
|
|
431
|
+
| 列出会话 | `magic-agent --sessions`(只列**已存档**的会话) |
|
|
273
432
|
| **读会话历史** | `magic-agent --session-log <session_id\|run_id>` → `{session, events}` |
|
|
274
433
|
| 增量续读 | 追加 `--after <seq>` → 另带 `count / after / lastSeq / snapshotRequired` |
|
|
275
|
-
|
|
|
434
|
+
| 向前翻更早 | 追加 `--before <skip> --limit <n>` → 另带 `count / before / nextBefore / firstSeq / more`(与 `--after` 互斥) |
|
|
435
|
+
| 读历史(精简) | 追加 `--session-log-lean`:输出省掉 `snapshot`(**只增参数**,不传则行为与旧版一致) |
|
|
436
|
+
| 停止会话 | `magic-agent --stop <session_id\|run_id>`(只停得掉**已存档**的会话) |
|
|
437
|
+
| **删除会话** | `magic-agent --delete-session <session_id\|run_id>` → `{id, records_removed, logs_removed, titles_removed}` |
|
|
276
438
|
| 追加消息 | `magic-agent --append <session_id\|run_id> -p "…"` |
|
|
277
439
|
| 跑一轮(流式 + 控制) | `magic-agent --stream --events --control -e <engine> -p "…"`,控制命令从 stdin 进 |
|
|
440
|
+
| **存档这一轮** | 追加 `--save-registry`(登记表)与 `--save-history`(历史 jsonl),**两者默认都关** |
|
|
441
|
+
| **归纳会话标题** | `magic-agent --summarize-title <session_id\|run_id>` → `{id, title, cached?}` |
|
|
442
|
+
| 读已存标题 | `magic-agent --session-title <id>` → `{id, title}`(不调 LLM) |
|
|
278
443
|
|
|
279
444
|
`--session-log` 的输出形状与插件同名的 HTTP 接口**逐字段对齐**,两条路可互换。
|
|
445
|
+
HTTP 侧的精简开关是**查询参数**(2026-10-20,只增参数,不传则与旧版一致):
|
|
446
|
+
`GET /desk/session/{id}/messages?lean=1` 与 `GET /desk/session/{id}/events?after=<seq>&lean=1`
|
|
447
|
+
会删掉每条事件的 `snapshot` 键(与 `--session-log-lean` 同一口径;CLI 侧该字段 `omitempty`,
|
|
448
|
+
所以两边仍然逐字段对齐),把单条 `tool_result` 的 `text` 截到 **12000 个字符**
|
|
449
|
+
(与对话内核 `QA_TOOL_OUT_MAX` 同一个值 —— 渲染侧本来就不显示更多,实测一条 338MB 会话
|
|
450
|
+
里 404 条工具输出占 26.9MB),并把**相邻的 `thinking` 增量合并成一条**(`text` 拼接,
|
|
451
|
+
`seq`/`at`/`status` 取末条 —— 客户端游标按 seq 前进,取首条会重复拉取)。**浏览器端一律带它**:
|
|
452
|
+
snapshot 是二次方膨胀的累积字段(实测单会话 283MB),移动端 / PC Web UI 都不消费事件级
|
|
453
|
+
snapshot(回放只用增量 `text`),思考区本来也是「一段连续 thinking 一块」—— 实测一条
|
|
454
|
+
95838 事件的会话里 94015 条是 ~3 字符的思考分片,逐条回放要 16s,合并后 2020 条、画面完全等价。
|
|
455
|
+
⚠️ lean 下 `events` 的**条数与 seq 不再是逐条日志**(只保证内容与先后顺序),
|
|
456
|
+
要逐帧对齐的消费方别开 lean。
|
|
457
|
+
老服务端忽略未知参数 ⇒ 客户端带上它不会把老版本弄坏。
|
|
280
458
|
边界语义:`after == lastSeq` 是**追平**(正常空增量,`snapshotRequired=false`);
|
|
281
459
|
`after > lastSeq` 才要求重拉全量。
|
|
282
460
|
|
|
461
|
+
⚠️ **`--before` 的游标是「跳过条数」而不是 seq(2026-10-20)**:日志是多段 run 追加,
|
|
462
|
+
旧 writer 的 `seq` 每轮从 1 重新计数(实测一份 15737 行日志有 4 处回落),拿 seq 当反向
|
|
463
|
+
游标跨不过 run 边界。现在 `before` 表示**从文件尾跳过多少条非空行**(`0` = 最新一页),
|
|
464
|
+
调用方必须用返回的 `nextBefore` 作为下一页的 `before`(坏行占位、页头对齐会让「本页条数」
|
|
465
|
+
与实际跳过数不等,`skip += count` 会重复取页)。页头对齐也由 core 做:一页内取**最早的
|
|
466
|
+
那条 `user`** 作为页首(被裁掉的几条由下一页补回,不重不漏);`firstSeq` 只是本页最早
|
|
467
|
+
事件的 seq(供显示/找锚点,别拿它当游标);`more=false` 表示已到文件头。窗口里一条都
|
|
468
|
+
解析不出来时返回 `count=0 / more=false`(调用方收掉入口,不要原地重试)。
|
|
469
|
+
|
|
470
|
+
⚠️ **删除是「全机生效」的(2026-10-19)**:`--delete-session` 与插件的
|
|
471
|
+
`DELETE /desk/session/{id}` 是同一份实现(`internal/session/delete.go`),删的是
|
|
472
|
+
**登记记录 + 事件日志(`<session_id>.jsonl` 与 `<run_id>.jsonl` 两个名字)+ 标题覆盖**,
|
|
473
|
+
幂等(id 不认识也算成功)。为什么强调:会话列表的权威来源是登记表,各端都读它 ——
|
|
474
|
+
客户端**不要**把删除做成「只在本机藏起来」(本地记墓碑那种),
|
|
475
|
+
否则在 A 端删掉的会话在 B 端照旧列着(2026-10-19 用户报的
|
|
476
|
+
「从pc端删除的会话移动端还在显示」就是这么来的)。删除不可回放,界面提示要如实说。
|
|
477
|
+
|
|
478
|
+
⚠️ **`session` 字段回的是翻译后的 id(2026-10-15)**:日志文件按 `session_id` 命名,
|
|
479
|
+
但正在跑的会话在引擎吐出 id 之前只有 `run_id` —— core 一开跑就先按 `<run_id>.jsonl`
|
|
480
|
+
落盘、拿到 id 后改名为 `<session_id>.jsonl`,读侧(`--session-log` 与
|
|
481
|
+
`/desk/session/{id}/messages`)会把 `run_id` 翻成当前的 `session_id`。
|
|
482
|
+
所以:**正在跑的会话按 `run_id` 当场就读得到内容**(不必等终点事件),
|
|
483
|
+
而响应里的 `session` 是翻译后的规范 id —— 客户端可以顺手拿它当会话锚点。
|
|
484
|
+
|
|
485
|
+
#### 会话标题:归纳在 core,客户端只读(2026-10-15)
|
|
486
|
+
|
|
487
|
+
用户定调:「应该 magic-agent 统一归纳作为会话标题」。此前归纳只在业务项目里做,
|
|
488
|
+
core 侧只有一张空的 sid→title 覆盖表,于是:
|
|
489
|
+
|
|
490
|
+
- 同一会话在 PC 显示本地归纳的要点、在移动端显示首问原文 —— **两端标题不同**;
|
|
491
|
+
- **从本插件(`/desk/ask`)发起的会话没人替它归纳**,只能回落 `prompt_head`(列表里一片「继续」)。
|
|
492
|
+
|
|
493
|
+
现在归纳口径收在 core(`internal/session/title.go`),两端只读不自己算:
|
|
494
|
+
|
|
495
|
+
| 事实 | 说明 |
|
|
496
|
+
| --- | --- |
|
|
497
|
+
| `--sessions` 给 `title` | core 已合并「标题表覆盖 → `prompt_head`」,客户端**直接渲染这个字段**即可,不要再自己拼优先级 |
|
|
498
|
+
| `--sessions` 给 `title_summarized` | 这个 title 是**真归纳过的**还是仅仅回落成了 `prompt_head`。⚠️ `title` 恒非空,客户端**不能**用「title 为空」判断「还没归纳」——那样自动归纳会永远不触发,且在界面上只表现为「标题一直是首问原文」,看不出是 bug |
|
|
499
|
+
| 归纳走 `--summarize-title` | 幂等:已有覆盖直接回(`cached:true`),不重复调 LLM,所以列表刷新时可逐条放心试 |
|
|
500
|
+
| 输入由 core 自己取 | 从会话历史读**首条 user 消息**,调用方不传原文 —— 各端各截一段,截断长度不同就会归纳出不同标题 |
|
|
501
|
+
| 归纳用 `llm` 引擎 | 纯文本、不带工具;`--title-engine` 可换 |
|
|
502
|
+
| 覆盖表路径不变 | `~/.magic-agent/session-titles.json`(与插件时代同一个文件,已有重命名结果不丢) |
|
|
503
|
+
| 人工重命名 | 插件的 `PUT /desk/session/{id}/title` 不变,现在只是转发到 core 的同一份存储 |
|
|
504
|
+
|
|
505
|
+
**存量会话怎么补**:两端都在列表加载后挑出 `title_summarized == false` 的行各补一次归纳
|
|
506
|
+
(有上限、逐条限流、静默失败)。core 幂等,重复调用不花钱 —— 所以历史会话不需要一次性迁移脚本,
|
|
507
|
+
界面刷一次就自己补齐了。
|
|
508
|
+
|
|
509
|
+
插件侧对应接口:`POST /desk/session/{id}/title/summarize`(body `{force?}`,允许空体)。
|
|
510
|
+
|
|
511
|
+
⚠️ 老版本 core 没有 `--summarize-title` / `--sessions.title`(exit 1)—— 客户端应**如实回落**
|
|
512
|
+
(PC 用本地缓存的标题,移动端用 `prompt_head`),不要静默显示空标题。
|
|
513
|
+
|
|
514
|
+
⚠️ **存档是 opt-in 的**(用户 2026-10-12:「加一个参数 默认不存档」+「会话登记也不需要」):
|
|
515
|
+
CLI 默认**什么都不落**,因为绝大多数调用(手工跑一轮 / CI / 脚本编排)事后没人寻址。两份东西
|
|
516
|
+
互相独立,按需各开一个:
|
|
517
|
+
|
|
518
|
+
| 开关 | 落什么 | 不开的后果 |
|
|
519
|
+
| --- | --- | --- |
|
|
520
|
+
| `--save-registry` | 会话登记表 `<run_id>.json`(引擎/模型/工作目录/**pid**/来源),**永久保留**(只清读不出来的坏文件) | `--sessions` 看不见、`--stop` 停不掉(pid 只存在这里)、`--append` 只能按 `run_id` |
|
|
521
|
+
| `--save-history` | 会话内容 `<session_id>.jsonl`(每条事件),**永久保留** | 列表里 `has_log=false` —— 能看到、能续接,但点开没有历史内容 |
|
|
522
|
+
|
|
523
|
+
**插件的 `/desk/ask` 两个都恒传**(客户端本来就要列会话 + 回放历史),所以界面里的会话
|
|
524
|
+
既在列表里、也有历史。**掌天瓶桌面壳的对话框**(`magic-test/desktop/agent-cli.cjs` 的
|
|
525
|
+
`SAVE_FLAGS`)同样传这两个开关并注入 `MAGIC_AGENT_SOURCE=pc` / `MAGIC_AGENT_CLIENT=magic-test`
|
|
526
|
+
(2026-10-15:它的会话要能在移动端 H5 上被列出并点开)。`--append` 在没有登记记录时会按约定找 `<dir>/<run_id>.sock`
|
|
527
|
+
(`session.AppendSocketPath`),所以「默认开着常驻、却追加不了」不会发生。
|
|
528
|
+
|
|
283
529
|
### 3.5 两条不变量
|
|
284
530
|
|
|
285
531
|
- **事件只读**:组件不接受对接方改写对话流,否则基础版行为会随对接方漂移。
|
package/README.md
CHANGED
|
@@ -194,7 +194,7 @@ magic-agent --append <run_id> -p "追加:顺便把 lint 也跑了"
|
|
|
194
194
|
| `-f, --file` | 空 | 从文件读 prompt(`-` = stdin) |
|
|
195
195
|
| `-a, --attach` | 空 | 附件(截图 / 图片),可重复或逗号分隔 |
|
|
196
196
|
| `--tools` | `off` | `off`(纯 chat)\| `on`(agent 模式)\| 逗号分隔白名单 |
|
|
197
|
-
| `-t, --timeout` | `
|
|
197
|
+
| `-t, --timeout` | `1h` | 单次尝试超时(如 `90s` / `3m`) |
|
|
198
198
|
| `-r, --retries` | `0` | 失败重试次数(总尝试 = 1 + retries) |
|
|
199
199
|
| `--backoff` | `2s` | 首次重试退避(指数翻倍,上限 30s,带抖动) |
|
|
200
200
|
| `-o, --output` | `json` | 输出格式:`json` \| `text` |
|
|
@@ -203,10 +203,12 @@ magic-agent --append <run_id> -p "追加:顺便把 lint 也跑了"
|
|
|
203
203
|
| `--engines` | 关 | 列出引擎、可用性、模型、版本与一键安装命令 |
|
|
204
204
|
| `--no-models` | 关 | 配合 `--engines`:跳过模型与版本探测 |
|
|
205
205
|
| `--json` | 关 | 兼容保留(行为同 `--engines` 的默认 JSON 输出) |
|
|
206
|
-
| `--stop` | 空 | 停止指定会话 / 运行(传 `session_id` 或 `run_id
|
|
207
|
-
| `--sessions` | 关 |
|
|
206
|
+
| `--stop` | 空 | 停止指定会话 / 运行(传 `session_id` 或 `run_id`);只停得掉**已存档**的会话 |
|
|
207
|
+
| `--sessions` | 关 | 列出会话登记表(只列**已存档**的会话) |
|
|
208
|
+
| `--save-registry` | 关 | 把会话登记落盘(`<run_id>.json`),让 `--sessions` / `--stop` 事后能按 id 寻址。**默认关**:不留痕;不留档的会话停不掉 |
|
|
208
209
|
| `--session-log` | 空 | 读一段会话的事件历史(传 `session_id` 或 `run_id`);输出 `{session, events}` |
|
|
209
210
|
| `--after` | `0` | 配合 `--session-log` 增量续读:只回 `seq > N` 的事件(另附 `count/after/lastSeq/snapshotRequired`) |
|
|
211
|
+
| `--save-history` | 关 | 把流式会话的事件落盘存档(`<session_id>.jsonl`),供 `--session-log` 回看。**默认不存**:一次性调用不留痕 |
|
|
210
212
|
| `--keep-alive` | **开** | 常驻会话(仅 `claude`/`codebuddy` 的 `--stream` 调用生效) |
|
|
211
213
|
| `--append` | 空 | 向常驻会话追加一条消息(传 `session_id` 或 `run_id`) |
|
|
212
214
|
| `--idle` | `5m` | 常驻会话空闲收工时长(`0` = 本轮结束就收工) |
|
|
@@ -239,7 +241,7 @@ magic-agent -e claude --stream --keep-alive=false -p "..." # 要「一轮
|
|
|
239
241
|
| 支持范围 | 只有 `claude` / `codebuddy` 的 `--stream` 调用;其它情况自动忽略(显式传 `--keep-alive` 却不满足条件 → `exit 2` 报错) |
|
|
240
242
|
| 追加语义 | 消息写进引擎 stdin 成为**下一轮 user 消息**:本轮跑完后接着处理,不打断当前轮 |
|
|
241
243
|
| 收工 | 最后一轮结束后 `--idle`(默认 5m)内无人追加则优雅收尾;`--idle 0` = 本轮结束就收工 |
|
|
242
|
-
| 超时 | 常驻生效期间不套 `-t` 的默认
|
|
244
|
+
| 超时 | 常驻生效期间不套 `-t` 的默认 1h(长任务 + 追加不该被砍),显式给 `-t` 才生效 |
|
|
243
245
|
|
|
244
246
|
> ⚠️ **默认常驻带来的行为变化**:进程会在最后一轮结束后继续存活 `--idle` 等待追加。调用方若要「一轮完成」的信号,请读事件流里的 **`turn_end`** 事件(而不是等进程退出),或启动时传 `--keep-alive=false`。
|
|
245
247
|
|
|
@@ -251,7 +253,7 @@ magic-agent --stop <session_id> # 按会话 id 停
|
|
|
251
253
|
magic-agent --stop <run_id> # 按运行 id 停(新会话还没拿到 session_id 时)
|
|
252
254
|
```
|
|
253
255
|
|
|
254
|
-
引擎 CLI 在**独立进程组**里跑,所以调用方对 magic-agent 发信号带不走它 —— 界面显示「已停止」而引擎还在后台跑。因此每次调用都落一条记录(`~/.magic-agent/sessions/<run_id>.json`,`MAGIC_AGENT_SESSIONS`
|
|
256
|
+
引擎 CLI 在**独立进程组**里跑,所以调用方对 magic-agent 发信号带不走它 —— 界面显示「已停止」而引擎还在后台跑。因此每次调用都落一条记录(`~/.magic-agent/sessions/<run_id>.json`,`MAGIC_AGENT_SESSIONS` 可改目录,**永久保留**),`--stop` 按记录里的 pid 杀整个进程组(先 `SIGTERM`,2s 内不退再 `SIGKILL`)。
|
|
255
257
|
|
|
256
258
|
| 情况 | 退出码 |
|
|
257
259
|
|---|---|
|
|
@@ -350,6 +352,39 @@ EOF
|
|
|
350
352
|
| `reason` | 最内层根因(stderr 摘要 / 超时 / 非零退出码),面向程序分支判断 |
|
|
351
353
|
| `attempts` | 实际执行次数;`0` = 参数校验阶段即失败 |
|
|
352
354
|
|
|
355
|
+
## 手机 / 平板连接(`client` 子命令)
|
|
356
|
+
|
|
357
|
+
npm 包**同时装着** core(`magic-agent`)和客户端插件(`magic-client`),后者提供 Web UI
|
|
358
|
+
与 `/desk/*` 接口。手机要用它,就在装包的那台电脑上把插件跑成后台服务:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
magic-agent client start --lan # 后台常驻(关掉终端也还在),打印手机可用的地址
|
|
362
|
+
magic-agent client status # 在不在、端口、手机地址、健康检查(--json 给脚本)
|
|
363
|
+
magic-agent client stop # 停掉(本来没跑也算成功)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
`start --lan` 会打印每个网卡一条 `http://<局域网IP>:<端口>/?token=…`,以及 `/pair`
|
|
367
|
+
配对二维码页 —— 那条**在电脑上**打开,手机扫屏幕上的码(或把地址整行复制到手机 App)。
|
|
368
|
+
|
|
369
|
+
| 事实 | 位置 / 说明 |
|
|
370
|
+
|------|------------|
|
|
371
|
+
| 状态文件(pid / 端口 / 令牌) | `~/.magic-client/client.json`,0600;`--state` 或 `MAGIC_CLIENT_STATE` 可改 |
|
|
372
|
+
| 令牌(**跨重启稳定**) | `~/.magic-client/token`,0600;删掉它才会换新的 |
|
|
373
|
+
| 日志 | `~/.magic-client/logs/client.log`;`--log` 或 `MAGIC_CLIENT_LOG` 可改 |
|
|
374
|
+
| 界面资源 | 自动取与二进制同包的 `client-ui/` `client-ui-mobile/`(`--ui-dir` / `--mobile-dir` 可覆盖) |
|
|
375
|
+
| core 路径 | 自动用**这一份** `magic-agent`(`MAGIC_AGENT_BIN`),不取 PATH 里可能更旧的 |
|
|
376
|
+
|
|
377
|
+
要点:
|
|
378
|
+
|
|
379
|
+
- **必须同一 Wi-Fi**。`--lan` 只是把监听从 `127.0.0.1` 改成 `0.0.0.0`,跨网不在本命令范围内。
|
|
380
|
+
- `--lan` 时令牌是**唯一门锁**,短于 24 字符会被直接拒绝启动;默认生成的 32 位十六进制远超这条线。
|
|
381
|
+
- 令牌跨重启稳定是刻意的:手机里存的是「地址 + 令牌」,每次 `start` 换一个就等于配对作废。
|
|
382
|
+
- `status` 的判据是「pid 活着 **且** `/desk/health` 答得上」,不是只看进程;不健康时退出码 3。
|
|
383
|
+
- 端口是系统分配的(`0`),因为**每次 start 会变**,所以别把端口写死进任何配置 —— 以打印的地址为准。
|
|
384
|
+
- 排障要看前台日志就跑 `magic-client serve --lan`(同一个二进制,`serve` 不脱离终端)。
|
|
385
|
+
- `magic-agent client …` 只是转发层:core **不依赖**插件(`internal/boundary` 钉着这条),
|
|
386
|
+
所以参数集合由 `magic-client start --help` 定义。
|
|
387
|
+
|
|
353
388
|
## 退出码
|
|
354
389
|
|
|
355
390
|
| 码 | 含义 |
|
|
@@ -357,6 +392,7 @@ EOF
|
|
|
357
392
|
| 0 | 成功 |
|
|
358
393
|
| 1 | 调用失败(引擎错误 / 超时耗尽 / 重试耗尽) |
|
|
359
394
|
| 2 | 参数或输入错误(未知引擎、非法格式、空 prompt 等) |
|
|
395
|
+
| 3 | 仅 `client status`:服务未运行(或进程在但健康检查失败),便于脚本判活 |
|
|
360
396
|
|
|
361
397
|
## 重试语义
|
|
362
398
|
|
|
@@ -376,6 +412,10 @@ EOF
|
|
|
376
412
|
| Linux x64 / arm64 | `npm/dist/linux-x64` / `linux-arm64` |
|
|
377
413
|
| Windows x64 | `npm/dist/win32-x64` |
|
|
378
414
|
|
|
415
|
+
每个目录下**并排两个二进制**:`magic-agent`(core)与 `magic-client`(客户端插件)。
|
|
416
|
+
`magic-agent client start` 按「与自己同目录」找后者,所以两个必须是**同一次构建**
|
|
417
|
+
(插件启动时会比对 core 契约版本,跨版本会明确拒绝启动)。
|
|
418
|
+
|
|
379
419
|
## 集成 H5 对话组件(其他工程)
|
|
380
420
|
|
|
381
421
|
npm 包自带可复用的前端组件,`npm install magic-agent` 后在 `node_modules/magic-agent/` 下:
|
|
@@ -402,6 +442,9 @@ magic-client serve --ui-dir magic-h5
|
|
|
402
442
|
手机访问启动时打印的 `http://<局域网IP>/?token=...`。嵌进自己的服务同理:按上面的
|
|
403
443
|
布局静态托管,令牌走 `?token=`。
|
|
404
444
|
|
|
445
|
+
(`magic-client` 随 npm 包安装,也可以只跑 `magic-agent client start --lan` 把插件后台拉起来,
|
|
446
|
+
界面资源会自动按包布局找到。)
|
|
447
|
+
|
|
405
448
|
**② 拷进自己的构建**(App/工程内嵌):把 `client-ui-mobile/` 内容与 `client-ui/shared/`
|
|
406
449
|
复制进 `www/`(Vite/Capacitor 同理,`require.resolve('magic-agent/client-ui-mobile/index.html')`
|
|
407
450
|
定位源路径),再注入 `window.MAGIC_BASE` / `window.MAGIC_TOKEN` 指向 magic-client 地址。
|
package/client-ui/index.html
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
POST /desk/ask /desk/ask/{id}/control 一轮与打断/收工/回答案
|
|
26
26
|
-->
|
|
27
27
|
<link rel="stylesheet" href="shared/conversation.css">
|
|
28
|
+
<!-- 对话内核那套 DOM(.qa-*)的**共用皮肤**:三端同一份(见 shared/conv/conv.css 的文件头)。
|
|
29
|
+
取值全走令牌,本页只负责给令牌(下面的 :root)—— 皮肤本身不定义颜色。 -->
|
|
30
|
+
<link rel="stylesheet" href="shared/conv/conv.css">
|
|
28
31
|
<style>
|
|
29
32
|
/* ===== 令牌:AA 桌面工作台的 oklch 值,映射到共享组件的颜色契约 =====
|
|
30
33
|
契约里的每个名字都必须在宿主这一层给出值 —— 共享样式只用不定义。 */
|
|
@@ -72,6 +75,12 @@
|
|
|
72
75
|
--r-card: 14px; --r: 9px; --r-full: 999px;
|
|
73
76
|
--font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
74
77
|
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
78
|
+
|
|
79
|
+
/* 对话皮肤的两个基线值(共用皮肤 shared/conv/conv.css 取它们,与主题无关)——
|
|
80
|
+
桌面这套是整圆角气泡 + 更紧的行高(13px 字号配 28px 会松);
|
|
81
|
+
移动端那套见 client-ui-mobile/index.html(ZCode 基线,tests/check-ui-spec.mjs 钉的就是它)。 */
|
|
82
|
+
--qa-bubble-r: 22px;
|
|
83
|
+
--qa-body-lh: 26px;
|
|
75
84
|
}
|
|
76
85
|
[data-theme="dark"] {
|
|
77
86
|
color-scheme: dark;
|
|
@@ -165,6 +174,29 @@ svg { display: block; }
|
|
|
165
174
|
</div>
|
|
166
175
|
<div id="conv"></div>
|
|
167
176
|
|
|
177
|
+
<!-- 第三方库归口 shared/vendor(与内核共用一份):markdown 渲染栈要用 marked + DOMPurify;
|
|
178
|
+
highlight.js 由内核按需加载,地址在下面给(内核自己不知道页面的相对根) -->
|
|
179
|
+
<script src="shared/vendor/marked.min.js"></script>
|
|
180
|
+
<script src="shared/vendor/purify.min.js"></script>
|
|
181
|
+
<script>window.QA_HLJS_URL = "shared/vendor/highlight.min.js";</script>
|
|
182
|
+
<!-- 对话内核(**唯一实现**,见 shared/conv/README.md):三端共用同一份脚本,
|
|
183
|
+
宿主只给外壳、样式令牌与「宿主符号」(本壳缺的那批在下面 host-glue.js 里,见块①)。
|
|
184
|
+
顺序按 shared/conv/manifest.json,别在这里调换。 -->
|
|
185
|
+
<script src="shared/conv/01-dom.js"></script>
|
|
186
|
+
<script src="shared/conv/02-media-kinds.js"></script>
|
|
187
|
+
<script src="shared/conv/03-chat-message.js"></script>
|
|
188
|
+
<script src="shared/conv/06-ask-answer.js"></script>
|
|
189
|
+
<script src="shared/conv/07-stream-delegation.js"></script>
|
|
190
|
+
<script src="shared/conv/08-ask-event-map.js"></script>
|
|
191
|
+
<script src="shared/conv/09-stream-apply.js"></script>
|
|
192
|
+
<script src="shared/conv/10-markdown-render.js"></script>
|
|
193
|
+
<script src="shared/conv/18-askbox-markup.js"></script>
|
|
194
|
+
<script src="shared/conv/19-attachments.js"></script>
|
|
195
|
+
<script src="shared/conv/27-conv-ledger.js"></script>
|
|
196
|
+
<script src="shared/conv/29-msg-variants.js"></script>
|
|
197
|
+
<!-- 宿主符号适配层:内核只用不定义的那 12 个(任务表 / 需求板 / 工作区 / 模型 / 发送 / 提示)——
|
|
198
|
+
本壳真有的走 window.MagicHost 转发(块② 才挂),没有的给**诚实桩**,不假装成功 -->
|
|
199
|
+
<script src="shared/host-glue.js"></script>
|
|
168
200
|
<script src="shared/conversation.js"></script>
|
|
169
201
|
<script src="shared/transport-http.js"></script>
|
|
170
202
|
<script>
|