magic-agent 2.3.0 → 2.3.9
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 +78 -13
- package/README.md +10 -4
- package/client-ui/pc/app.css +14 -0
- package/client-ui/pc/js/93-rail.js +3 -1
- package/client-ui/shared/conv/03-chat-message.js +20 -3
- package/client-ui/shared/conv/07-stream-delegation.js +15 -0
- package/client-ui/shared/conv/27-conv-ledger.js +1 -1
- package/client-ui/shared/conv/conv.css +17 -0
- package/client-ui/shared/conversation.js +209 -39
- package/client-ui/shared/transport-http.js +193 -38
- package/client-ui-mobile/index.html +733 -107
- package/client-ui-mobile/reqs.js +42 -29
- package/internal/agent/capability.go +8 -1
- package/internal/agent/claude.go +5 -3
- package/internal/agent/codebuddy.go +6 -2
- package/internal/agent/codex.go +7 -2
- package/internal/agent/dsh_sdk.go +8 -0
- package/internal/agent/dsh_sdk_test.go +17 -0
- package/internal/agent/engine.go +20 -0
- package/internal/agent/engine_test.go +52 -0
- package/internal/agent/llmengine.go +6 -0
- package/internal/agent/llmengine_test.go +7 -0
- package/internal/agent/llmhttp.go +6 -0
- package/internal/agent/llmhttp_test.go +27 -0
- package/internal/agent/llmmodels.go +13 -0
- package/internal/agent/permission.go +5 -0
- package/internal/cli/ask.go +32 -17
- package/internal/cli/requirements.go +2 -0
- package/internal/cli/sessionlog.go +5 -0
- package/internal/cli/sessionlog_user_test.go +49 -0
- package/internal/client/cors_test.go +32 -0
- package/internal/client/jobs.go +1160 -0
- package/internal/client/jobs_test.go +369 -0
- package/internal/client/requirements.go +27 -7
- package/internal/client/requirements_test.go +51 -0
- package/internal/client/service.go +259 -142
- package/internal/client/service_test.go +78 -0
- package/internal/requirements/consolidate.go +86 -24
- package/internal/requirements/requirements.go +9 -0
- package/internal/requirements/requirements_test.go +129 -0
- package/internal/requirements/store.go +11 -3
- package/internal/session/session.go +15 -1
- package/internal/session/workspace_lock.go +165 -0
- package/internal/session/workspace_lock_test.go +90 -0
- package/internal/suggest/suggest.go +101 -7
- package/internal/suggest/suggest_test.go +44 -0
- package/npm/apk/magic-agent-mobile-release.apk +0 -0
- package/npm/apk/version.json +3 -3
- 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 +3 -2
package/CLIENT-CONTRACT.md
CHANGED
|
@@ -165,7 +165,7 @@ codex / dsh(+sdk) / llm / openclaw 暂不带 `item_id`,
|
|
|
165
165
|
|
|
166
166
|
| 集合 | 位置 | 回答的问题 |
|
|
167
167
|
| --- | --- | --- |
|
|
168
|
-
| `features[]` | `--contract` 顶层 | 这台 core 支不支持某条**通道**(`feature.session.events` / `feature.session.control` / `feature.session.team` / `feature.requirements`) |
|
|
168
|
+
| `features[]` | `--contract` 顶层 | 这台 core 支不支持某条**通道**(`feature.session.events` / `feature.session.control` / `feature.session.hosted` / `feature.session.team` / `feature.requirements`) |
|
|
169
169
|
| `engines[].capabilities[]` | 每个引擎 | 这个**引擎**能做什么(`session.stream` / `attachment.native` / `engine.install` …) |
|
|
170
170
|
|
|
171
171
|
静态与运行态严格分开:`capabilities` / `features` 是静态声明,`ok` / `streaming` / `models` /
|
|
@@ -175,6 +175,11 @@ codex / dsh(+sdk) / llm / openclaw 暂不带 `item_id`,
|
|
|
175
175
|
`--team`。客户端据它决定**要不要显示团队模式开关** —— 没有就整个不显示(点了必然 exit 2 的
|
|
176
176
|
开关比没有更糟)。插件对带 `team` 的请求也先查这个位,缺了回 **501 + 中文原因**。
|
|
177
177
|
|
|
178
|
+
`feature.session.hosted`(2026-10-23 只增量,**不**进 `requiredFeatures`):这台 core 认
|
|
179
|
+
`--hosted`。它只给会话历史里的 `kind:"user"` 事件加 `hosted:true`(客户端回放画成
|
|
180
|
+
「托管发送」),**不改变执行语义**。插件在能力位缺失时忽略请求里的 `hosted`、照常发送 ——
|
|
181
|
+
这是纯显示字段,不能因为老 core 不认识就阻断用户的任务;实时气泡仍由客户端自己标。
|
|
182
|
+
|
|
178
183
|
`feature.requirements`(2026-10-22 只增量,**不**进 `requiredFeatures`):这台 core 认
|
|
179
184
|
`--requirements` / `--consolidate-requirements`(见 §3.4.1)。插件对 `GET /desk/requirements`
|
|
180
185
|
与 `POST /desk/requirements/consolidate` 都先查这个位,缺了回 **501 + 中文原因**。
|
|
@@ -346,7 +351,8 @@ notice {text:"团队模式:追加 N 个补充任务(a1、a2),继续
|
|
|
346
351
|
| 模块清单 | `GET /desk/modules`(从 profile 派生:id / 入口 / 声明的表) | 已实现 |
|
|
347
352
|
| 会话登记表 | `GET /desk/sessions`(core 的 `--sessions`,跨进程持久;**只回本客户端发起的会话**) | 已实现 |
|
|
348
353
|
| 项目业务接口 | `ANY /api/*` → 原样转发到 profile 的 `backend.url` | 已实现 |
|
|
349
|
-
| 发起一轮 | `POST /desk/ask`(SSE 中继 core 的 NDJSON,前置 `event: open`,收尾 `event: end
|
|
354
|
+
| 发起一轮 | `POST /desk/ask`(SSE 中继 core 的 NDJSON,前置 `event: open`,收尾 `event: end`)。**服务端会先入队**:同 workspace 串行 + `request_id` 幂等;真在等时先推 `event: queued`(`{job, state, position}`,客户端可忽略),准入后照旧 `open` | 已实现 |
|
|
355
|
+
| 作业队列(多客户端) | `POST /desk/jobs`:提交(不挂长连接,2xx 即可);`GET /desk/jobs[?workspace=][&limit=]`:队列快照(含他人任务与 `position`);`GET /desk/jobs/{id}`;`GET /desk/jobs/{id}/events?after=<seq>`:按 seq 回放本轮事件(形状同 `ask/{id}/events` + `job` 字段);`POST /desk/jobs/{id}/cancel`;`POST /desk/jobs/cancel {request_id}`(还没拿到 job id 时取消);`GET /desk/jobs/events?[workspace=]`:队列变更 SSE(每 2s 一帧 `event: jobs`)。持久化在 `<profile data dir>/jobs/`,服务重启后 queued 保留、running 残留标失败 | 已实现 |
|
|
350
356
|
| 控制与回审批 | `POST /desk/ask/{id}/control`(与 core 的 `--control` 同协议,插件不解释 op) | 已实现 |
|
|
351
357
|
| 断线续传 | `GET /desk/ask/{id}/events?after=<seq>`(按**本轮流 seq** 增量补拉;带 `done` / `code` / `stderr` / `snapshotRequired`;`after=0` 时首条是本轮**合成的用户提问**,见 2.4) | 已实现 |
|
|
352
358
|
| 运行中列表 | `GET /desk/asks`(**本进程**正在跑的轮次,每项 `{id, engine, started, session?, run?}`;与 sessions 的区别见下) | 已实现 |
|
|
@@ -442,6 +448,8 @@ notice {text:"团队模式:追加 N 个补充任务(a1、a2),继续
|
|
|
442
448
|
| `session` | 可选,续接一个已有会话(传 `/desk/sessions` 里的 id)→ core 的 `--session` |
|
|
443
449
|
| `idle` | 可选,常驻会话空闲收工时长;默认 `0` = 本轮结束就收工 |
|
|
444
450
|
| `team` | 可选,团队模式(见下)。**nil / 缺省 = 关闭**(默认关)|
|
|
451
|
+
| `request_id` | 可选(2026-10-23,只增字段),客户端生成的**幂等键**。同一个 id 重复提交(断线重连 / App 重启 / 两个标签页)只执行一次:服务端返回原作业并接回去。空 = 旧客户端,仍参与 workspace 串行,但没有幂等保护 |
|
|
452
|
+
| `serial` | 可选(2026-10-23,只增字段),默认 `true`:参与「同一 workspace 单槽」串行。客户端「并行模式」显式传 `false`(服务端仍套全局并发上限,`MAGIC_CLIENT_MAX_RUNNING`,默认 4)|
|
|
445
453
|
|
|
446
454
|
`team`(2026-10-21 只增字段,不升 `contractVersion`):
|
|
447
455
|
|
|
@@ -490,6 +498,10 @@ notice {text:"团队模式:追加 N 个补充任务(a1、a2),继续
|
|
|
490
498
|
用户要看的是「此刻连着的这份桌面端是哪一版」。老 core 没有该字段 → 空串,界面显示「—」,
|
|
491
499
|
客户端不写死版本号。
|
|
492
500
|
|
|
501
|
+
`/desk/health.jobs`(2026-10-23 只增字段,不升 `contractVersion`):插件是否提供
|
|
502
|
+
**服务端作业队列**(`true` / 缺失)。有它时客户端应把排队交给服务端(`/desk/jobs` 或带
|
|
503
|
+
`request_id` 的 `/desk/ask`),本地不再自行判忙;缺失时客户端退回本地串行门(老插件)。
|
|
504
|
+
|
|
493
505
|
⚠️ **`/desk/health.version` 是插件启动时缓存的 core 版本**:只升级 CLI、没重启插件时它会
|
|
494
506
|
一直报旧值(2026-10-22 实测:CLI 已 2.1.3,手机仍显示 1.0.111)。要显示「此刻真正的
|
|
495
507
|
core 版本」必须读 `/desk/contract.version`(每次都重新问 core);`health.version` 只适合
|
|
@@ -738,6 +750,25 @@ beforeAsk(info) // info: {isNew, session, workspace, engine, model, permission
|
|
|
738
750
|
|
|
739
751
|
掌天瓶那一侧的 transport 走 IPC → CLI;`transport-http.js` 是走 `/desk/*` 的现成实现。
|
|
740
752
|
|
|
753
|
+
### 3.2.1.1 transport 的可选能力:按会话停止(跨进程)
|
|
754
|
+
|
|
755
|
+
```js
|
|
756
|
+
stopSession(id) // id: session_id 或 run_id → Promise<{stopped, reason, record}>
|
|
757
|
+
// 对应 POST /desk/session/{id}/stop
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
`control({op:'interrupt'})` 需要 **ask id**,而 ask id 只存在于**拉起 core 的那个进程**里。
|
|
761
|
+
当本端只是尾随别人进程里跑的那一轮(桌面壳直接 exec core、另一台设备起的、宿主队列派发的),
|
|
762
|
+
`state.askId` 永远是空的 —— 没有这条可选能力,用户点「停止」只会被回一句
|
|
763
|
+
「这一轮还没拿到轮次 id」,按几次报几次,这一轮根本停不下来。
|
|
764
|
+
|
|
765
|
+
组件的兜底顺序(`control('interrupt')` 且没有 askId):① `onInterrupt(prompt)` 让宿主撤掉排队;
|
|
766
|
+
② `listAsks()` 里按 session_id / run_id 认出**这一轮还在跑**的那条 ask,用它打断
|
|
767
|
+
(跨端也是这条:悬浮球远程控制对面机器时,对面 `/desk/asks` 里就有它,对面不必升级);
|
|
768
|
+
③ `stopSession(state.session)`(**只在 `state.busy` 且有会话 id 时**);④ 才如实报「还没拿到轮次 id」。
|
|
769
|
+
服务端实现走 core 的 `--stop`(登记表 pid + SIGTERM 进程组),语义幂等:
|
|
770
|
+
`stopped:false` 只表示「本来就结束了 / 进程已不在」,不是失败。
|
|
771
|
+
|
|
741
772
|
### 3.2.2 transport 的可选能力:插件(加号 → 插件)
|
|
742
773
|
|
|
743
774
|
会话输入框的 + 弹层里,`插件` 这一行**只在 transport 提供下面两个方法时出现**
|
|
@@ -802,6 +833,9 @@ magic-agent 把会话的查询与修改都做成 CLI 命令,调用方 exec 即
|
|
|
802
833
|
| **归纳需求** | `magic-agent --consolidate-requirements <ws_id\|目录\|.>` → 一轮归纳结果(**会调模型**) |
|
|
803
834
|
|
|
804
835
|
`--session-log` 的输出形状与插件同名的 HTTP 接口**逐字段对齐**,两条路可互换。
|
|
836
|
+
其中 `kind:"user"` 事件新增可选 `hosted:true`(2026-10-23 只增字段,`omitempty`):
|
|
837
|
+
true = 这条提问由客户端「会话托管」自动发出,客户端回放时画成机器人头像 + 「托管发送」小标;
|
|
838
|
+
缺省 = 用户亲手发的。它只影响画法,不参与执行;老日志 / 老 core 没有该字段时行为不变。
|
|
805
839
|
HTTP 侧的精简开关是**查询参数**(2026-10-20,只增参数,不传则与旧版一致):
|
|
806
840
|
`GET /desk/session/{id}/messages?lean=1` 与 `GET /desk/session/{id}/events?after=<seq>&lean=1`
|
|
807
841
|
会删掉每条事件的 `snapshot` 键(与 `--session-log-lean` 同一口径;CLI 侧该字段 `omitempty`,
|
|
@@ -875,8 +909,17 @@ event: end data: {"session":"<id>","after":<末位置>,"state":"done|faile
|
|
|
875
909
|
丢掉当前画面,按 `messages` 重拉一遍全量,再用新游标重新 follow。不要在半截上续写。
|
|
876
910
|
4. `event: end` 一定收摊:`reason=terminal` 按 `state` 落收尾(`failed`/`cancelled`/
|
|
877
911
|
`interrupted`/`gone` 不写作「已完成」,`done`/`idle` 写作「已完成」);
|
|
878
|
-
`reason=max_duration` 是服务端 30
|
|
879
|
-
|
|
912
|
+
`reason=max_duration` 是服务端 30 分钟的单连接寿命(**不是这一轮结束**)——
|
|
913
|
+
客户端保留游标、自动重新 follow 续上,不要提示用户「重开会话」。
|
|
914
|
+
5. 连接断开(网络 / WebView 切后台 / 反向代理空闲超时 / 换会话 / 组件销毁)**不是终态**,
|
|
915
|
+
别据此判定任务失败。除主动收摊(换会话 / 销毁)外,客户端必须**自动重连**:
|
|
916
|
+
- 游标 = 最后一条已收到事件的 `seq`,重连请求带 `?after=<游标>`(断点续传,不重放已画过的帧);
|
|
917
|
+
- 指数退避:0.8s 起、翻倍、单次 15s 封顶;连续 12 次或 6 分钟仍连不上才按失败收尾;
|
|
918
|
+
- 重连期间那一轮**不封口**(不调 `finish` / `qaRunEnd`),底部继续显示运行态;
|
|
919
|
+
界面插一行「连接中断,正在自动重连…」,恢复后就地改成「已重新连接,继续同步」;
|
|
920
|
+
- 4xx(404 老网关 / 401 令牌失效等)是确定性失败,**不重试**,立即 fallback / 如实报错。
|
|
921
|
+
参考实现:`client-ui/shared/transport-http.js` 的 `followSession`(`onRetry` / `onResume`
|
|
922
|
+
可选回调,老宿主不实现也不影响续流);闸门 `npm run check:follow`。
|
|
880
923
|
6. 服务端没有这个路由(老版本,404)→ 退回旧行为(一次性快照),不要报错弹窗。
|
|
881
924
|
|
|
882
925
|
服务端行为:`?lean=1` 与 `messages/events` 同一口径;`after` 缺省 = 从文件头读。
|
|
@@ -970,11 +1013,16 @@ core 侧只有一张空的 sid→title 覆盖表,于是:
|
|
|
970
1013
|
| 事实 | 说明 |
|
|
971
1014
|
| --- | --- |
|
|
972
1015
|
| 生成在 core | `magic-agent --suggest-followups <file\|->`(`internal/suggest`):提示词、截断、JSON 解析、去重与长度上限都只有一份;移动端 / PC 拿到的建议不会各说各话 |
|
|
1016
|
+
| **默认关闭** | 建议引擎**不配置 = 不开启建议功能**(2026-10-23 用户定调)。组件 `suggestEngine` 为空时不发 `/desk/suggest`、不画建议、不补历史建议、托管也不自动执行;老用户升级后默认没有建议,需去「我的」里显式选引擎 |
|
|
1017
|
+
| 配置入口 | 移动端「我的 → 建议引擎 / 建议模型」两行,点开的是与「默认引擎 / 模型」相同的**二列抽屉**:左列首项「不开启」(默认),其余是 `/desk/engines` 探测到的可用引擎;点左列只切右列,点右列才落定。引擎存 `magic.suggest.engine`、模型存 `magic.suggest.model`,都属于环境配置(`ENV_SCOPE_KEYS` + `/desk/prefs` 同步);改完立刻 `conv.setSuggestEngine(engine, model)` 生效,下一轮收尾开始生成 |
|
|
1018
|
+
| 选引擎 / 模型 | `llm` 显式选中的模型只传 `reasoning_effort=none`,不换模型;`MiniMax-M3.1-Flash` 拒绝该值(要求 adaptive thinking,HTTP 400)。**建议模型留空**时用 `models.json` 第一条 `thinking.disabled` 条目作默认值(本机 `minimax-nothink`),避免默认首条 400。`claude` / `codex` 可用各自的关闭档 |
|
|
1019
|
+
| **思考固定关闭** | 建议调用一律带 `NoThinking`,各引擎走官方开关:`claude` → `--thinking disabled`;`codex` → `model_reasoning_effort="none"`;`llm` → `reasoning_effort="none"`(直连请求体;CLI 委托路径用 `-o reasoning_effort none`);显式模型不换模型,空建议模型用 `thinking.disabled` 变体作默认值;`codebuddy` → settings `alwaysThinkingEnabled=false`;`dsh` → SDK `InitializeParams.reasoningEffort="off"`。`trae`/`openclaw`/`arkclaw` 没有已知开关,仍保留 `NoThinking` 标记,失败后再降级 `llm` |
|
|
973
1020
|
| **不续接会话** | 独立进程、新会话、`ToolsOff`、不带 `SessionID` / `Continue` —— 「请生成建议」这类指令不会写进用户正在聊的上下文,也不进会话登记表 / 日志 |
|
|
974
|
-
|
|
|
1021
|
+
| 引擎走「无工具 + JSON」通道 | core 的 `suggest.Generate` 固定 **`ToolsOff` + `JSONSchema` + `NoThinking`**,引擎以 `--print --output-format json` 返回一小段结果,不走流式、不带工具。默认选 `llm`;建议模型留空时用 `models.json` 第一条 `thinking.disabled` 变体(本机 `minimax-nothink`),显式模型则只传 `reasoning_effort=none` |
|
|
1022
|
+
| 慢引擎不拖界面 | `claude` / `codex` / `codebuddy` / `dsh` 等非 `llm` 引擎给 10s 主引擎预算;失败/超时才降级 `llm`,并清掉当前引擎的 model。界面默认不传 engine,直接走快路径,20s 超时不会触发 |
|
|
975
1023
|
| **允许空数组** | 模型判断「没有下一步」时回 `[]`,这是**合法结果**:客户端不画任何按钮,不拿模板凑 |
|
|
976
1024
|
| 成功收尾后问 | 失败 / 被用户停止 / 还在跑的轮次都不问;本轮出现过提问卡(引擎在等作答)时也跳过,避免与作答卡抢注意力 |
|
|
977
|
-
| 历史最后一轮补一次 | 打开一条**已收尾、不在跑**的历史会话时,回放完对最后一轮补一次建议;更早的轮次不问(它的「下一步」早已被后续对话覆盖)。缓存按 `(sid, 最后一条 user 的 at/seq)` 放 `localStorage`,30 分钟内重开不重复调模型;空数组也缓存 |
|
|
1025
|
+
| 历史最后一轮补一次 | 打开一条**已收尾、不在跑**的历史会话时,回放完对最后一轮补一次建议;更早的轮次不问(它的「下一步」早已被后续对话覆盖)。缓存按 `(sid, 最后一条 user 的 at/seq, 建议引擎, 建议模型)` 放 `localStorage`,30 分钟内重开不重复调模型;空数组也缓存 |
|
|
978
1026
|
| 历史建议只展示 | 历史补出来的建议**不排托管计时器、不自动发送**,只有用户亲手点才走正常发送路径;否则一打开旧会话,托管会立刻把几天前的一句建议发出去 |
|
|
979
1027
|
| 输入是这一轮 | `user` = 用户上一句,`reply` = 本轮回答正文(core 侧再截断:user 1500 / reply 6000 runes,取回答**尾部**) |
|
|
980
1028
|
| 点击 = 发送 | 界面把建议文本交给正常发送路径(用户气泡、排队、附件闸门、串行门全部一致),**不是**只填进输入框 |
|
|
@@ -987,28 +1035,34 @@ core 侧只有一张空的 sid→title 覆盖表,于是:
|
|
|
987
1035
|
|
|
988
1036
|
#### 会话托管:在「建议」之上自动发第一条(2026-10-23,纯客户端策略)
|
|
989
1037
|
|
|
990
|
-
|
|
1038
|
+
用户定调:「托管模式下自动按照最佳建议提交到会话中执行」;后续把「建议引擎」配置放到「我的」tab(默认不配置 = 建议功能关闭),托管开关仍在会话页运行设置、按会话记。
|
|
1039
|
+
2026-10-23 再定:「跟团队模式一样改成挂在会话上,子会话继承,不再是全局」。(前者已被后者取代:
|
|
1040
|
+
开关不再是「我的」里的设备级全局项,入口与团队模式并排放进会话页的「任务运行设置」。)
|
|
991
1041
|
|
|
992
1042
|
这**不是**新的 wire 契约:没有新接口、没有新字段,core / 插件完全不需要知道托管是否开启。
|
|
993
1043
|
它只是组件在「拿到建议」与「用户点建议」之间加的一条本地策略:
|
|
994
1044
|
|
|
995
1045
|
| 事实 | 说明 |
|
|
996
1046
|
| --- | --- |
|
|
997
|
-
| 开关位置 |
|
|
1047
|
+
| 开关位置 | 移动端会话页右上角 →「任务运行设置」→「会话托管」;**按会话**记在 `magic.hosting.sessions.v1`(`sid → 开/关`),默认关。与团队模式同一套形状,归属这个环境(`ENV_SCOPE_KEYS` + `/desk/prefs` 同步),「我的」里不再有全局开关 |
|
|
1048
|
+
| 启用条件 | 必须先有**建议引擎**:未配置时运行设置那行显示「未配置建议引擎」,点击只提示去「我的」配置;组件 `setHosting(true)` 也会拒绝,避免没有「最佳建议」却自己跑 |
|
|
1049
|
+
| 子会话继承 | 子会话(团队模式拆出的子任务)**没有自己的记录**时,沿 `parent_session_id` / `parent_run_id` 向上找最近一条显式记录;都没有才算关。主会话改开关,没单独设过的子会话跟着变;在子会话里手动改一次 = 写自己的记录,从此显式覆盖(不再继承) |
|
|
998
1050
|
| 自动发哪条 | 建议数组的**第一条**(模型排序即优先级);不拼接、不改写、不自己生成 |
|
|
999
1051
|
| 发送路径 | 与用户点建议同一条:内核画用户气泡、串行门 `beforeAsk`、权限、排队、附件闸门全部照旧;不经过输入框 |
|
|
1052
|
+
| 发送标识 | 托管自动发的那条**不是「我」**(2026-10-23 用户:「是托管发送的要标识 显示时区别于“我”发送」):实时气泡用机器人头像 + 虚线气泡 + 「托管发送」小标;ask 请求带 `hosted:true`,core 写进 `kind:"user"` 历史事件,重开会话回放仍是同一标识。标识只影响画法,不改提示词 / 引擎 / 权限 |
|
|
1000
1053
|
| 先给一眼 | 建议画出来后等 1.2s 再发;这 1.2s 内输入框有字 / 有附件 / 已开新轮 / 已切会话 / 已关托管 → 放弃本次自动发送 |
|
|
1001
|
-
| 连续上限 | 12 轮。到顶 / 本轮失败 / 本轮被停止 →
|
|
1054
|
+
| 连续上限 | 12 轮。到顶 / 本轮失败 / 本轮被停止 → **自动暂停**并回调宿主把**这条会话**的开关关掉;回放历史里的旧失败不算 |
|
|
1002
1055
|
| 空建议 | 不自动发,也不拿模板凑;托管保持开启,等下一轮(用户手动发的那句)结束后再看 |
|
|
1003
1056
|
| 历史建议 | 打开历史会话补出来的「最后一轮」建议**只展示**,不进托管;托管只对本次实时新收尾的轮次生效 |
|
|
1004
1057
|
| 交付完成 | 小说 / 故事 / 文章 / 报告等**完整成品**交付完、AI 没有提问也没列下一步 → 建议必须为空;「再来一篇 / 换个题材 / 再写一个 / 继续写」由服务端确定性过滤(除非 AI 明确邀请),托管不得自动续写 |
|
|
1005
1058
|
| 提问卡 | 托管开着时,卡出现 1.2s 后自动选**第一项**交卷(优先「继续 / 下一步 / 开始」措辞);用户碰卡、关闭卡、自己输入 → 取消这次自动答,不抢人的操作。只处理 `kind:"question"`,待授权(permission)绝不自动点,避免代用户授权 |
|
|
1006
1059
|
| 成功判据 | 不只看收尾回调的 `ok`:`llm` 这类**不发 `turn_end`** 的引擎走兜底 `hardFinish(0,'已完成')`,也必须算成功。否则建议永远不请求、托管第一轮就误暂停(2026-10-23 实测修复) |
|
|
1007
|
-
| 停止 |
|
|
1060
|
+
| 停止 | 运行设置里的按会话开关、建议行上的「托管中」标记、或正常的中断按钮(本轮被停 → 自动暂停) |
|
|
1008
1061
|
| 可见性 | 待发时建议行前有「托管中」标记、第一条建议加描边;发送后建议行收起。不做「静默自己跑」 |
|
|
1009
1062
|
|
|
1010
1063
|
⚠️ 托管是**宿主级**能力(`client-ui/shared/conversation.js` 的 `hosting` / `setHosting`),
|
|
1011
|
-
|
|
1064
|
+
不是内核渲染的一部分;**开关按会话**,组件只执行宿主导出的当前值(子会话的继承也在宿主侧算)。
|
|
1065
|
+
magic-test 桌面端那份内联 `app.js` 不加载这个组件,因此不会出现托管开关。
|
|
1012
1066
|
|
|
1013
1067
|
#### 会话状态:与标题同一套存法,客户端只读(2026-10-21)
|
|
1014
1068
|
|
|
@@ -1064,9 +1118,16 @@ magic-agent --consolidate-requirements ~/work/proj # 手动整理一轮(**会
|
|
|
1064
1118
|
magic-agent --consolidate-requirements . --requirement-force # 没有新记忆也跑一轮
|
|
1065
1119
|
magic-agent --consolidate-requirements . --requirement-all # 全量重放(回填/校正)
|
|
1066
1120
|
magic-agent --consolidate-requirements . --requirement-auto # 自动归纳:只增量,绝不初始化
|
|
1121
|
+
magic-agent --consolidate-requirements . --requirement-module 登录 # 只整理「登录」这个模块
|
|
1067
1122
|
magic-agent --consolidate-requirements . --requirement-engine claude # 用配置的默认引擎整理
|
|
1068
1123
|
```
|
|
1069
1124
|
|
|
1125
|
+
**模块级整理(2026-10-23)**:`--requirement-module <模块>` 只把增量写回该模块,
|
|
1126
|
+
**不推进空间游标** —— 其它模块的记忆仍留在 `pending`,留给空间级整理 / 自动归纳;
|
|
1127
|
+
因此可以重复点、不会吞掉别的模块的需求。它**不触发首次目录初始化**(目录基线是空间级的),
|
|
1128
|
+
模块不存在会明确报错。HTTP 侧对应 `POST /desk/requirements/consolidate` 的
|
|
1129
|
+
`{"workspace":"…","module":"登录"}`;`module` 留空 = 整理整个空间。
|
|
1130
|
+
|
|
1070
1131
|
**引擎口径(2026-10-22 用户)**:整理的引擎就是**客户端配置的默认引擎**。移动端/PC 点「整理」
|
|
1071
1132
|
时把「我的 → 默认引擎/模型」放进 `POST /desk/requirements/consolidate` 的
|
|
1072
1133
|
`{"engine":"claude","model":"…"}`,插件原样组成 `--requirement-engine` / `--requirement-model`。
|
|
@@ -1104,8 +1165,9 @@ CLI)才按 `claude → codebuddy → codebuddy-ai → codex → trae → dsh`
|
|
|
1104
1165
|
"final_preview":"用户用手机号登录,验证码 60 秒可重发","updated_at":1761090000000}]}]}]}
|
|
1105
1166
|
```
|
|
1106
1167
|
|
|
1107
|
-
`--consolidate-requirements` 的输出:`{ok, workspace, dir, noop?, bootstrap?, bootstrap_engine?,
|
|
1108
|
-
scanned?, updated, created, points, conclusions, consumed, replayed?, calls, pending, error?}
|
|
1168
|
+
`--consolidate-requirements` 的输出:`{ok, workspace, dir, module?, noop?, bootstrap?, bootstrap_engine?,
|
|
1169
|
+
scanned?, updated, created, points, conclusions, consumed, replayed?, calls, pending, error?}`
|
|
1170
|
+
(`module` 只在模块级整理时出现;此时 `consumed` 恒为 0)。
|
|
1109
1171
|
`noop=true` 是「没有未归纳记忆、也没 `--requirement-force`」的正常结果,不是错;
|
|
1110
1172
|
`bootstrap=true` 表示这一轮做了首次目录初始化(`bootstrap_engine` 是实际读目录的引擎,
|
|
1111
1173
|
`scanned` 是扫描到的条目数,排障用)。
|
|
@@ -1124,8 +1186,11 @@ scanned?, updated, created, points, conclusions, consumed, replayed?, calls, pen
|
|
|
1124
1186
|
**客户端要做的事**:列表页只调 `--requirements`(每刷一次都调,绝不顺手触发归纳);
|
|
1125
1187
|
「整理」按钮调 `--consolidate-requirements`,并**传它要整理的那个空间 + 当前配置的默认引擎**
|
|
1126
1188
|
(移动端需求页同时列出多个空间时,组头各给一枚「整理」,不要把「当前会话的空间」当成用户点的那个);
|
|
1189
|
+
二级页(点进某个模块)的「整理」必须带 `module`,**只整理当前模块** —— 不带就会退化成整空间,
|
|
1190
|
+
用户点一个模块却看到所有模块都变了(2026-10-23 报障)。
|
|
1127
1191
|
顶栏另给「全部整理」:按空间**串行**逐个调同一个接口(有待整理的 → 未初始化的 → 其它),
|
|
1128
1192
|
一个空间失败不阻断后面的,全部结束后汇总「成功/失败/新建/更新」。
|
|
1193
|
+
忙态只标**本次真正在整理**的那一个入口;其余按钮只置灰、保持原文案(否则看起来像全部在跑)。
|
|
1129
1194
|
按钮在首次初始化时要允许几分钟等待(文案「初始化中…」),完成后再 `--requirements` 刷新。
|
|
1130
1195
|
打开一篇文档读 `file` 绝对路径的正文即可(普通 markdown,走既有的阅读器)。插件在每轮 `/desk/ask` 收尾后**防抖 12s 自动归纳**
|
|
1131
1196
|
该轮所在工作区(`MAGIC_CLIENT_NO_REQ_AUTO=1` 关闭),所以客户端不必自己轮询触发。
|
package/README.md
CHANGED
|
@@ -173,7 +173,8 @@ magic-agent -p "对比两张图" -a a.png -a b.png
|
|
|
173
173
|
|
|
174
174
|
# 流式输出
|
|
175
175
|
magic-agent --stream -e claude -o text "复杂问题"
|
|
176
|
-
magic-agent --stream --no-thinking -e claude "问题"
|
|
176
|
+
magic-agent --stream --no-thinking -e claude "问题" # 只隐藏思考事件
|
|
177
|
+
magic-agent --disable-thinking -e claude "问题" # 请求模型真正关闭思考
|
|
177
178
|
|
|
178
179
|
# 会话
|
|
179
180
|
magic-agent --sessions # 会话登记表
|
|
@@ -199,7 +200,8 @@ magic-agent --append <run_id> -p "追加:顺便把 lint 也跑了"
|
|
|
199
200
|
| `--backoff` | `2s` | 首次重试退避(指数翻倍,上限 30s,带抖动) |
|
|
200
201
|
| `-o, --output` | `json` | 输出格式:`json` \| `text` |
|
|
201
202
|
| `--stream` | 关 | 流式输出(text 模式思考走 stderr) |
|
|
202
|
-
| `--no-thinking` | 关 |
|
|
203
|
+
| `--no-thinking` | 关 | 流式模式下不转发思考过程增量(只隐藏,不关闭模型思考) |
|
|
204
|
+
| `--disable-thinking` / `--no-think` | 关 | 请求引擎真正关闭模型思考:claude `--thinking disabled`、codex `effort=none`、codebuddy `alwaysThinkingEnabled=false`、dsh `reasoningEffort=off`、llm 传 `reasoning_effort=none`(不自动换模型) |
|
|
203
205
|
| `--engines` | 关 | 列出引擎、可用性、模型、版本与一键安装命令 |
|
|
204
206
|
| `--no-models` | 关 | 配合 `--engines`:跳过模型与版本探测 |
|
|
205
207
|
| `--json` | 关 | 兼容保留(行为同 `--engines` 的默认 JSON 输出) |
|
|
@@ -255,6 +257,7 @@ magic-agent --requirements all # 列全部工作区(只读
|
|
|
255
257
|
magic-agent --requirements ~/work/proj # 只列这个目录(也认 ws id)
|
|
256
258
|
magic-agent --consolidate-requirements ~/work/proj # 手动整理:首次=按目录初始化,之后只处理新会话
|
|
257
259
|
magic-agent --consolidate-requirements . --requirement-all # 全量重放(回填/校正)
|
|
260
|
+
magic-agent --consolidate-requirements . --requirement-module 登录 # 只整理「登录」这个模块
|
|
258
261
|
```
|
|
259
262
|
|
|
260
263
|
- **第一次整理 = 初始化**:工作区还没初始化过时,先有界扫描空间目录,用**客户端配置的默认
|
|
@@ -263,8 +266,11 @@ magic-agent --consolidate-requirements . --requirement-all # 全量重放(
|
|
|
263
266
|
默认引擎不能读目录(如 `llm`)会明确报错并提示换引擎。目录为空不算失败,会回落到会话增量。
|
|
264
267
|
- **自动归纳只增量**:插件在每轮对话收尾后防抖 12s 自动归纳(`MAGIC_CLIENT_NO_REQ_AUTO=1`
|
|
265
268
|
关闭),它带 `--requirement-auto`,**绝不触发首次目录初始化**。
|
|
266
|
-
-
|
|
267
|
-
|
|
269
|
+
- **模块级整理**:`--requirement-module <模块>` 只把这个模块的增量写回该模块,
|
|
270
|
+
**不推进空间游标**(别的模块的记忆仍留在待整理),可以重复点;它不触发首次目录初始化。
|
|
271
|
+
- 需求页每个空间组头有独立「整理」,只整理那个空间;点进某个模块后,二级页的「整理」
|
|
272
|
+
只整理当前模块;顶栏「全部整理」按空间串行跑一遍,一键触发全部(某个空间失败不影响后面的)。
|
|
273
|
+
没有新会话时返回 `noop`,不会空跑一次模型。
|
|
268
274
|
|
|
269
275
|
详细契约见 [CLIENT-CONTRACT.md](CLIENT-CONTRACT.md) §3.4.1。
|
|
270
276
|
|
package/client-ui/pc/app.css
CHANGED
|
@@ -1540,6 +1540,18 @@ body::after{content:"";position:fixed;inset:0;z-index:60;pointer-events:none;opa
|
|
|
1540
1540
|
.qa-msg.qa-mine .qa-bd{padding:10px 14px;
|
|
1541
1541
|
border:1px solid var(--line);border-radius:14px 5px 14px 14px;
|
|
1542
1542
|
background:linear-gradient(180deg,#FBF9F7,#F5F1ED)}
|
|
1543
|
+
/* 托管自动发送(2026-10-23):PC 没有托管开关,但同一条历史可能在手机端被托管发出。
|
|
1544
|
+
回放时按 `hosted:true` 画成机器人头像 + 「托管发送」小标。
|
|
1545
|
+
⚠️ 与 client-ui/shared/conv/conv.css 的 `.qa-hosted` 那段同步改(两份皮肤目前各存一份)。 */
|
|
1546
|
+
.qa-msg.qa-hosted .qa-bd{border-style:dashed;border-color:var(--cinnabar-hi);
|
|
1547
|
+
background:linear-gradient(180deg,#FFF8F5,#FDF0EA)}
|
|
1548
|
+
.qa-msg.qa-hosted .qa-ico{color:var(--cinnabar-hi);--h:var(--halo-c-red)}
|
|
1549
|
+
.qa-hosted-tag{display:inline-flex;align-items:center;gap:4px;margin:0 0 6px;
|
|
1550
|
+
padding:2px 7px;border-radius:999px;background:rgba(240,105,83,.10);
|
|
1551
|
+
color:var(--cinnabar-hi);font-size:10.5px;line-height:15px;font-weight:600;
|
|
1552
|
+
letter-spacing:.02em;user-select:none;white-space:nowrap}
|
|
1553
|
+
.qa-hosted-tag::before{content:"";width:4px;height:4px;border-radius:50%;
|
|
1554
|
+
background:currentColor;flex:0 0 auto}
|
|
1543
1555
|
/* ===== 审核用户的回复(2026-09-20 加审查轮;2026-09-22 **去卡片**)=====
|
|
1544
1556
|
用户原话(2026-09-20):「验证不通过的返回建议 再代表审核用户发送到对话中」——
|
|
1545
1557
|
它不是需求作者、也不是引擎,所以既不能复用用户气泡(朱砂「超」字),也不能复用 bot 气泡(大喵)。
|
|
@@ -3582,6 +3594,8 @@ body::after{content:"";position:fixed;inset:0;z-index:60;pointer-events:none;opa
|
|
|
3582
3594
|
[data-theme="dark"] .qa-t.md-body blockquote{background:rgba(245,244,243,.04);border-color:#3B3835}
|
|
3583
3595
|
[data-theme="dark"] .qa-t.md-body blockquote p{color:#A8A29E}
|
|
3584
3596
|
[data-theme="dark"] .qa-msg.qa-mine .qa-bd{background:#1C1C1E;border-color:#3B3835}
|
|
3597
|
+
[data-theme="dark"] .qa-msg.qa-hosted .qa-bd{background:#241B18;border-color:#8A5144}
|
|
3598
|
+
[data-theme="dark"] .qa-hosted-tag{background:rgba(240,105,83,.16);color:#F2A08E}
|
|
3585
3599
|
[data-theme="dark"] .qa-av-txt{background:linear-gradient(180deg,#2B2B2E,#1C1C1E)}
|
|
3586
3600
|
/* 审核用户的回复(深色):**不需要另给一套** —— 它已经与 bot 答复同形(无底无框),
|
|
3587
3601
|
与下面「审核引擎的回复」同一条口径。原来那条 `background:#241F16;border-color:琥珀` 的
|
|
@@ -258,7 +258,9 @@ function qaRailApply(ctx, events){
|
|
|
258
258
|
if(Number.isFinite(sq) && sq > ctx.seq) ctx.seq = sq;
|
|
259
259
|
if(kind === "user"){
|
|
260
260
|
ctx.h = null;
|
|
261
|
-
|
|
261
|
+
/* 移动端「会话托管」自动发的提问带 hosted:true:PC 回放同一条历史时也画成
|
|
262
|
+
「托管发送」,否则同一会话在两端会长成两张脸。 */
|
|
263
|
+
qaRenderMine(String(ev.text || ""), [], { hosted: !!ev.hosted });
|
|
262
264
|
continue;
|
|
263
265
|
}
|
|
264
266
|
if(!ctx.h){ ctx.h = qaRenderAnswer(); if(!ctx.h) break; }
|
|
@@ -460,9 +460,16 @@ function qaMsgRetry(msg){
|
|
|
460
460
|
toast("正在重试 · " + escHtml(text.slice(0, 24)) + (text.length > 24 ? "…" : ""));
|
|
461
461
|
qaSend(text, { followup: true, echo: false });
|
|
462
462
|
}
|
|
463
|
-
|
|
463
|
+
/* opts(可选,2026-10-23 只增参数):
|
|
464
|
+
· hosted=true —— 这条是「会话托管」自动选的第一条建议,不是用户亲手打的。
|
|
465
|
+
仍然是右侧的用户轮次(留 `.qa-mine`,排队 / 重试 / 取文都照旧),但头像换成
|
|
466
|
+
数字分身的机器人、气泡虚线描边,并在正文上方挂一枚「托管发送」小标 ——
|
|
467
|
+
用户 2026-10-23 报:「是托管发送的要标识 显示时区别于“我”发送」。
|
|
468
|
+
老调用方一律不传第三参,画出来与以前逐像素一致。 */
|
|
469
|
+
function qaRenderMine(text, atts, opts){
|
|
464
470
|
const s = $("#qa-stream"); if(!s) return;
|
|
465
471
|
s.hidden = false;
|
|
472
|
+
const hosted = !!(opts && opts.hosted);
|
|
466
473
|
/* 空态那句「还没有事件」得先让位:它只在画布一条都还没有时该在。
|
|
467
474
|
⚠️ 以前只有**事件流**那条路径会摘它(conversation.js 的 ingest),而用户气泡
|
|
468
475
|
是宿主直接调这里画的、不经过事件流 —— 于是发出第一句之后,画布上同时挂着
|
|
@@ -471,8 +478,18 @@ function qaRenderMine(text, atts){
|
|
|
471
478
|
const empty = s.querySelector(".conv__empty");
|
|
472
479
|
if(empty) empty.remove();
|
|
473
480
|
const div = document.createElement("div");
|
|
474
|
-
div.className = "qa-msg qa-mine";
|
|
475
|
-
div.innerHTML = `<div class="qa-ico">${qaAvatarHtml("mine")}</div><div class="qa-bd"><div class="qa-t md-body"></div></div>`;
|
|
481
|
+
div.className = "qa-msg qa-mine" + (hosted ? " qa-hosted" : "");
|
|
482
|
+
div.innerHTML = `<div class="qa-ico">${qaAvatarHtml(hosted ? "sim" : "mine")}</div><div class="qa-bd"><div class="qa-t md-body"></div></div>`;
|
|
483
|
+
/* 小标放在 .qa-t **前面**(正文之外):取文 / 重试只看 .qa-t,不会把这四个字
|
|
484
|
+
当成用户说的话再发一遍。 */
|
|
485
|
+
if(hosted){
|
|
486
|
+
const bd0 = div.querySelector(".qa-bd");
|
|
487
|
+
const tag = document.createElement("div");
|
|
488
|
+
tag.className = "qa-hosted-tag";
|
|
489
|
+
tag.textContent = "托管发送";
|
|
490
|
+
tag.title = "会话托管自动选择的第一条建议,不是你亲手输入的";
|
|
491
|
+
bd0.insertBefore(tag, bd0.firstChild);
|
|
492
|
+
}
|
|
476
493
|
// 用户消息按 markdown 渲染:marked + DOMPurify(沿用产品文档同一管线,输入是用户可控,
|
|
477
494
|
// 但消毒仍保留以防恶意粘贴)。
|
|
478
495
|
const host = div.querySelector(".qa-t");
|
|
@@ -125,6 +125,21 @@ function bindQaStreamDelegation(){
|
|
|
125
125
|
ts.classList.toggle("open");
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
|
+
/* 长消息折叠的「展开全文 / 收起」(2026-10-23 用户报:「点击展开全文没效果」)。
|
|
129
|
+
按钮由 29-msg-variants 的 qaFoldMaybe 生成、**不带监听**,夹在被折叠正文 host
|
|
130
|
+
的紧后面(`previousElementSibling` 就是它)。此前委托迁进 07 时漏了这条分支,
|
|
131
|
+
按钮就成了死的(PC / 移动端同一份内核,两边一起哑)。
|
|
132
|
+
开合态只切 host 上的 `.on`(CSS 见 conv.css),文字在「展开全文(共 N 行)」与
|
|
133
|
+
「收起」之间来回;行数仍从 data-qa-lines 取回。 */
|
|
134
|
+
const foldBtn = e.target.closest && e.target.closest(".qa-fold-btn");
|
|
135
|
+
if(foldBtn){
|
|
136
|
+
const host = foldBtn.previousElementSibling;
|
|
137
|
+
if(!host) return;
|
|
138
|
+
const on = host.classList.toggle("on");
|
|
139
|
+
foldBtn.textContent = on ? "收起"
|
|
140
|
+
: "展开全文(共 " + (foldBtn.getAttribute("data-qa-lines") || "") + " 行)";
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
128
143
|
/* ⑥ 任务列表开合:面板是应答里的一块**常驻**节点(一条应答只有一块,每次 TodoWrite
|
|
129
144
|
原地更新它的内容,见 03-chat-message 的 todoList)—— 开合态只切类,点击必须走委托,
|
|
130
145
|
理由与工具卡 / 思考条同:快照恢复(`s.innerHTML = t.snapshot`)会把节点整个重建,
|
|
@@ -313,7 +313,7 @@ function qaSessionPlayer(){
|
|
|
313
313
|
}catch(e){ /* 收尾失败不影响回放 */ }
|
|
314
314
|
h = null;
|
|
315
315
|
}else if(arts){ arts.flush(null); } // 没有答复卡的一轮:清掉残留,别串到下一轮
|
|
316
|
-
qaRenderMine(String(ev.text || ""), []);
|
|
316
|
+
qaRenderMine(String(ev.text || ""), [], { hosted: !!ev.hosted });
|
|
317
317
|
t0 = atMs(ev);
|
|
318
318
|
continue;
|
|
319
319
|
}
|
|
@@ -446,6 +446,21 @@
|
|
|
446
446
|
.qa-msg.qa-mine .qa-bd{padding:10px 14px;
|
|
447
447
|
border:1px solid var(--line);border-radius:var(--qa-bubble-r,14px 5px 14px 14px);
|
|
448
448
|
background:linear-gradient(180deg,#FBF9F7,#F5F1ED)}
|
|
449
|
+
/* 托管自动发送的用户气泡(2026-10-23 用户:「是托管发送的要标识 显示时区别于“我”发送」):
|
|
450
|
+
还是右侧的用户轮次,但头像换成数字分身的机器人(qaRenderMine 的 opts.hosted),
|
|
451
|
+
气泡虚线描边 + 朱砂淡底,正文上方一枚「托管发送」小标 —— 一眼能看出这句不是手打的。 */
|
|
452
|
+
.qa-msg.qa-hosted .qa-bd{border-style:dashed;border-color:var(--cinnabar-hi);
|
|
453
|
+
background:linear-gradient(180deg,#FFF8F5,#FDF0EA)}
|
|
454
|
+
.qa-msg.qa-hosted .qa-ico{color:var(--cinnabar-hi);--h:var(--halo-c-red)}
|
|
455
|
+
.qa-hosted-tag{display:inline-flex;align-items:center;gap:4px;margin:0 0 6px;
|
|
456
|
+
padding:2px 7px;border-radius:var(--r-full,999px);
|
|
457
|
+
background:rgba(240,105,83,.10);color:var(--cinnabar-hi);
|
|
458
|
+
font-size:10.5px;line-height:15px;font-weight:600;letter-spacing:.02em;
|
|
459
|
+
user-select:none;white-space:nowrap}
|
|
460
|
+
.qa-hosted-tag::before{content:"";width:4px;height:4px;border-radius:50%;
|
|
461
|
+
background:currentColor;flex:0 0 auto}
|
|
462
|
+
[data-theme="dark"] .qa-msg.qa-hosted .qa-bd{background:#241B18;border-color:#8A5144}
|
|
463
|
+
[data-theme="dark"] .qa-hosted-tag{background:rgba(240,105,83,.16);color:#F2A08E}
|
|
449
464
|
/* 排队中的用户气泡(`is-queued`):已经发出去、但还在等前一轮跑完。
|
|
450
465
|
气泡照常可读(用户要能确认自己写的是什么),只把底色压淡 + 虚线边 + 底下补一行
|
|
451
466
|
「排队中 · 上一轮结束后自动发送」—— **不隐藏、不挪走**,否则就是这次报障的
|
|
@@ -1022,6 +1037,8 @@
|
|
|
1022
1037
|
[data-theme="dark"] .qa-t.md-body blockquote p{color:#A8A29E}
|
|
1023
1038
|
|
|
1024
1039
|
[data-theme="dark"] .qa-msg.qa-mine .qa-bd{background:#1C1C1E;border-color:#3B3835}
|
|
1040
|
+
/* 放在上面那条**之后**:同为 4 个类级选择器,靠后才会赢(托管气泡的暗色底不被盖回灰)。 */
|
|
1041
|
+
[data-theme="dark"] .qa-msg.qa-hosted .qa-bd{background:#241B18;border-color:#8A5144}
|
|
1025
1042
|
|
|
1026
1043
|
[data-theme="dark"] .qa-av-txt{background:linear-gradient(180deg,#2B2B2E,#1C1C1E)}
|
|
1027
1044
|
|