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.
Files changed (153) hide show
  1. package/CLIENT-CONTRACT.md +254 -8
  2. package/README.md +48 -5
  3. package/client-ui/index.html +32 -0
  4. package/client-ui/pc/app.css +89 -1
  5. package/client-ui/pc/index.html +18 -8
  6. package/client-ui/pc/js/00-base.js +8 -30
  7. package/client-ui/pc/js/05-task-list-render.js +4 -1
  8. package/client-ui/pc/js/07-chat-stream-render.js +2 -85
  9. package/client-ui/pc/js/10-markdown.js +3 -171
  10. package/client-ui/pc/js/18-askbox.js +2 -176
  11. package/client-ui/pc/js/90-glue.js +1 -1
  12. package/client-ui/pc/js/91-workbench-stubs.js +4 -10
  13. package/client-ui/pc/js/93-rail.js +10 -3
  14. package/client-ui/shared/conv/01-dom.js +52 -0
  15. package/client-ui/shared/conv/02-media-kinds.js +34 -0
  16. package/client-ui/shared/conv/03-chat-message.js +1561 -0
  17. package/client-ui/shared/conv/06-ask-answer.js +377 -0
  18. package/client-ui/shared/conv/07-stream-delegation.js +158 -0
  19. package/client-ui/{pc/js → shared/conv}/09-stream-apply.js +121 -6
  20. package/client-ui/shared/conv/10-markdown-render.js +252 -0
  21. package/client-ui/shared/conv/18-askbox-markup.js +185 -0
  22. package/client-ui/{pc/js → shared/conv}/19-attachments.js +365 -21
  23. package/client-ui/shared/conv/27-conv-ledger.js +381 -0
  24. package/client-ui/{pc/js → shared/conv}/29-msg-variants.js +112 -3
  25. package/client-ui/shared/conv/README.md +257 -0
  26. package/client-ui/shared/conv/conv.css +1638 -0
  27. package/client-ui/shared/conv/dom-map.md +170 -0
  28. package/client-ui/shared/conv/manifest.json +44 -0
  29. package/client-ui/shared/conversation.css +368 -190
  30. package/client-ui/shared/conversation.js +1722 -788
  31. package/client-ui/shared/host-glue.js +143 -0
  32. package/client-ui/shared/transport-http.js +130 -6
  33. package/client-ui-mobile/index.html +2417 -103
  34. package/cmd/magic-client/clientctl.go +273 -0
  35. package/cmd/magic-client/main.go +71 -5
  36. package/go.mod +6 -3
  37. package/go.sum +4 -1
  38. package/internal/agent/arkclaw.go +3 -2
  39. package/internal/agent/arkclaw_stream.go +126 -8
  40. package/internal/agent/arkclaw_stream_test.go +97 -15
  41. package/internal/agent/arkclaw_test.go +3 -3
  42. package/internal/agent/attachments.go +18 -5
  43. package/internal/agent/attachments_test.go +7 -1
  44. package/internal/agent/capability_test.go +11 -1
  45. package/internal/agent/claude.go +96 -158
  46. package/internal/agent/codebuddy.go +108 -133
  47. package/internal/agent/codebuddy_gateway.go +8 -4
  48. package/internal/agent/codebuddy_gateway_stream.go +82 -2
  49. package/internal/agent/codebuddy_gateway_test.go +236 -1
  50. package/internal/agent/codebuddy_stream_test.go +262 -0
  51. package/internal/agent/codex.go +50 -9
  52. package/internal/agent/codex_stream_test.go +30 -0
  53. package/internal/agent/codex_test.go +24 -0
  54. package/internal/agent/custom_models.go +109 -3
  55. package/internal/agent/custom_models_test.go +74 -0
  56. package/internal/agent/dsh.go +464 -130
  57. package/internal/agent/dsh_resume.go +258 -0
  58. package/internal/agent/dsh_sdk.go +68 -17
  59. package/internal/agent/dsh_sdk_test.go +102 -2
  60. package/internal/agent/dsh_test.go +409 -36
  61. package/internal/agent/engine.go +26 -6
  62. package/internal/agent/engine_base.go +46 -2
  63. package/internal/agent/engine_base_test.go +29 -0
  64. package/internal/agent/engine_test.go +101 -0
  65. package/internal/agent/env.go +69 -1
  66. package/internal/agent/itemtracker.go +271 -0
  67. package/internal/agent/launch.go +192 -0
  68. package/internal/agent/launch_test.go +206 -0
  69. package/internal/agent/launch_unix.go +15 -0
  70. package/internal/agent/launch_windows.go +24 -0
  71. package/internal/agent/limitnotice.go +103 -0
  72. package/internal/agent/limitnotice_test.go +211 -0
  73. package/internal/agent/llmengine.go +2 -2
  74. package/internal/agent/models.go +3 -2
  75. package/internal/agent/openclaw.go +4 -3
  76. package/internal/agent/openclaw_acp.go +7 -0
  77. package/internal/agent/openclaw_test.go +2 -2
  78. package/internal/agent/permission.go +100 -11
  79. package/internal/agent/permission_test.go +100 -36
  80. package/internal/agent/runcmd.go +13 -3
  81. package/internal/agent/runner.go +7 -1
  82. package/internal/agent/runner_test.go +1 -1
  83. package/internal/agent/stream.go +777 -43
  84. package/internal/agent/stream_test.go +3 -2
  85. package/internal/agent/stream_timeline_test.go +905 -0
  86. package/internal/agent/streamjson.go +36 -2
  87. package/internal/agent/toolkind.go +20 -1
  88. package/internal/agent/toolkind_test.go +7 -1
  89. package/internal/agent/tools_suffix_test.go +10 -3
  90. package/internal/agent/trae.go +5 -3
  91. package/internal/agent/workspace_test.go +52 -0
  92. package/internal/cli/ask.go +260 -33
  93. package/internal/cli/ask_test.go +15 -11
  94. package/internal/cli/attach_test.go +32 -20
  95. package/internal/cli/clientcmd.go +156 -0
  96. package/internal/cli/events.go +53 -8
  97. package/internal/cli/events_test.go +230 -0
  98. package/internal/cli/keepalive_test.go +101 -6
  99. package/internal/cli/permission_test.go +5 -1
  100. package/internal/cli/root.go +174 -32
  101. package/internal/cli/session.go +175 -28
  102. package/internal/cli/session_test.go +144 -5
  103. package/internal/cli/sessionlog.go +385 -38
  104. package/internal/cli/sessionlog_test.go +708 -0
  105. package/internal/cli/sessionlog_user_test.go +142 -1
  106. package/internal/cli/sessiontitle.go +141 -0
  107. package/internal/client/browse.go +456 -0
  108. package/internal/client/browse_test.go +199 -0
  109. package/internal/client/cors_test.go +35 -0
  110. package/internal/client/daemon.go +525 -0
  111. package/internal/client/daemon_test.go +212 -0
  112. package/internal/client/detach_unix.go +19 -0
  113. package/internal/client/detach_windows.go +27 -0
  114. package/internal/client/pair.go +3 -3
  115. package/internal/client/plugins.go +376 -0
  116. package/internal/client/plugins_test.go +223 -0
  117. package/internal/client/proxy.go +374 -0
  118. package/internal/client/proxy_test.go +212 -0
  119. package/internal/client/service.go +1464 -48
  120. package/internal/client/service_test.go +649 -206
  121. package/internal/client/session_events_test.go +130 -0
  122. package/internal/client/wsproxy.go +113 -0
  123. package/internal/client/wsproxy_test.go +102 -0
  124. package/internal/session/delete.go +151 -0
  125. package/internal/session/delete_test.go +103 -0
  126. package/internal/session/session.go +155 -20
  127. package/internal/session/session_append.go +79 -21
  128. package/internal/session/session_test.go +124 -10
  129. package/internal/session/sessionlog.go +572 -6
  130. package/internal/session/sessionlog_read_test.go +410 -0
  131. package/internal/session/title.go +486 -0
  132. package/internal/session/title_test.go +298 -0
  133. package/npm/bin/magic-agent.js +101 -10
  134. package/npm/bin/magic-client.js +118 -0
  135. package/npm/dist/darwin-arm64/magic-agent +0 -0
  136. package/npm/dist/darwin-arm64/magic-client +0 -0
  137. package/npm/dist/darwin-x64/magic-agent +0 -0
  138. package/npm/dist/darwin-x64/magic-client +0 -0
  139. package/npm/dist/linux-arm64/magic-agent +0 -0
  140. package/npm/dist/linux-arm64/magic-client +0 -0
  141. package/npm/dist/linux-x64/magic-agent +0 -0
  142. package/npm/dist/linux-x64/magic-client +0 -0
  143. package/npm/dist/win32-x64/magic-agent.exe +0 -0
  144. package/npm/dist/win32-x64/magic-client.exe +0 -0
  145. package/npm/install.js +36 -13
  146. package/npm/lib/platform.js +27 -5
  147. package/package.json +8 -3
  148. package/client-ui/pc/js/03-chat-message.js +0 -880
  149. package/client-ui/pc/js/06-ask-answer.js +0 -204
  150. /package/client-ui/{pc/js → shared/conv}/08-ask-event-map.js +0 -0
  151. /package/client-ui/{pc → shared}/vendor/highlight.min.js +0 -0
  152. /package/client-ui/{pc → shared}/vendor/marked.min.js +0 -0
  153. /package/client-ui/{pc → shared}/vendor/purify.min.js +0 -0
@@ -0,0 +1,143 @@
1
+ /* 组件宿主的「内核宿主符号」适配层 —— 块①
2
+ *
3
+ * ── 它是什么 ──
4
+ * 对话内核(`shared/conv/*`)里有一批符号是**只用不定义**的:它们代表「宿主这一侧才知道的事」
5
+ * (当前任务表、需求板、工作区、模型选择器、发送、提示……)。magic-test(掌天瓶)在自己的分片里
6
+ * 实现了这些;PC 工作台与移动端这两个**组件宿主**则没有这些业务件。
7
+ * 这份文件把缺的那 12 个补齐,让**同一份内核脚本**在组件宿主里也能加载并运行
8
+ * (内核契约清单见 `manifest.json` 的 hostSymbols,校验:`node tests/check-conv-shards.mjs`)。
9
+ *
10
+ * ── 两条纪律 ──
11
+ * ① **不假装成功**:组件壳确实没有的能力(任务表 / 需求板 / 工作区切换),
12
+ * 返回空值并在注释里写明「这里没有」,绝不返回一个看起来成功的假结果(AGENTS.md 硬规则)。
13
+ * ② **真有的能力就真转发**:发送、模型、提示这三样组件壳是有的,但状态在组件闭包里 ——
14
+ * 由宿主页把自己的实现挂到 `window.MagicHost` 上(见两个宿主页里的那段),这里只做转发。
15
+ */
16
+ (function (global) {
17
+ 'use strict';
18
+
19
+ /** 宿主页挂上来的能力桥:`{ send(text), model:{ name(), pick(), set(id) }, toast(msg) }`
20
+ * 没挂(或某一项缺)时,下面的转发一律**如实失败**(返回 false / 空串),不冒名顶替。 */
21
+ function bridge() { return global.MagicHost || null; }
22
+
23
+ /* ── 一、组件壳没有的业务件:诚实桩 ────────────────────────────────
24
+ 这些桩只保证「内核拿到的回答是实话」,不代表这里真有那些功能。
25
+ 函数型与值型分开给(见第三节的说明:值被当变量读,给函数会读出源码字符串)。 */
26
+
27
+ /** 「给任务设模型」:没有任务表,自然也没有这件事 → 明确回 false(调用方据此不做后续动作)。 */
28
+ function qaTaskSetModel() { return false; }
29
+
30
+ /** 工作区的短目录名(给界面显示):本壳没有工作区切换 → 空串。 */
31
+ function wsDirOf() { return ''; }
32
+
33
+ /** 需求看板:只有 magic-test 工作台有它 → 本壳一律「没有这条需求」。 */
34
+ function reqHas() { return false; }
35
+
36
+ /** 需求看板的远端拉取:没有这条通路 → 回 null(调用方按「取不到」处理,不是抛错)。 */
37
+ function reqBoardFetch() { return Promise.resolve(null); }
38
+
39
+ /** 需求会话里给消息加的那点业务标注:本壳没有 → 空串(内核侧原来就是「无标注就留空」)。 */
40
+ function qaReqConvMsg() { return ''; }
41
+
42
+ /* ── 二、组件壳真的有的能力:转发给宿主页挂的桥(缺桥 → 如实失败)───────── */
43
+
44
+ /** 把一段话发进当前会话(内核在「重试」「追问」「提问卡作答」这些动作里会调)。
45
+ * `opts.echo === false` = 「这条气泡已经画过了,别再画一遍」(内核的重试路径)。 */
46
+ function qaSend(text, opts) {
47
+ const b = bridge();
48
+ if (!b || typeof b.send !== 'function') return false;
49
+ b.send(text == null ? '' : String(text), opts || {});
50
+ return true;
51
+ }
52
+
53
+ /** 模型显示名(内核拿它渲染角色行 / 「这一轮用的哪个模型」):没桥 → 空串 = 显示「自动」。 */
54
+ function qaModelName(id, eng) {
55
+ const b = bridge();
56
+ if (b && b.model && typeof b.model.name === 'function') return b.model.name(id, eng) || '';
57
+ return '';
58
+ }
59
+
60
+ /** 切换模型:没桥 → false(**不静默吞掉**,调用方会据此提示用户)。 */
61
+ function qaModelSet(id) {
62
+ const b = bridge();
63
+ if (!b || !b.model || typeof b.model.set !== 'function') return false;
64
+ b.model.set(id);
65
+ return true;
66
+ }
67
+
68
+ /** 轻提示:组件壳的提示由组件自己的提示位负责;
69
+ * 页面没挂桥时退到 console(**带前缀,让人一眼看出这是桩**),不假装弹过。 */
70
+ function toast(msg) {
71
+ const b = bridge();
72
+ if (b && typeof b.toast === 'function') { b.toast(String(msg == null ? '' : msg)); return; }
73
+ if (global.console && console.info) console.info('[host-glue] 本壳没有 toast:', msg);
74
+ }
75
+
76
+ /* ── 二·补:内核里剩下的那几个函数型宿主符号(本壳没有对应业务 → 如实降级)── */
77
+
78
+ /** 引擎/模型胶囊的提示同步:本壳的选择器由组件自己刷(syncPickers),这里无事可做。 */
79
+ function qaEngHintSync() { /* 没有胶囊提示可同步 */ }
80
+ /** 取某引擎的模型链(「自动」档的候选):本壳的模型由 core 用配置链自己降级 → 空链。 */
81
+ function qaModelChainLoad() { return []; }
82
+ /** 「某个选择器实例会先用哪个模型」:本壳只有一个会话、模型来自 state → 读组件写回的 `qaModelPick`。 */
83
+ function qaModelFor() { return global.qaModelPick || ''; }
84
+
85
+ /* ── 三、皮肤件 / 状态件 ───────────────────────────────────────────
86
+ ⚠️ 这一批是**值**,不是函数:内核把它们当变量读 ——
87
+ `qaEngine || ""`、`qaWhoHtml(qaEngine, qaModel)`、`Array.isArray(qaTasks)`、
88
+ `qaModelPick && qaModelPick !== mdl` …
89
+ 给成函数会得到「function …」这种字符串当引擎名(角色行上直接显示一段源码),
90
+ 而且 `Array.isArray(函数)` 恒为 false(任务相关的降级路径全走错)。
91
+ 值由**组件**在状态变化时写回 window(见 shared/conversation.js 的 syncHostState)。 */
92
+
93
+ /** 内核画委派卡 / 提问卡 / 工具卡用的三枚图标。这三枚是**皮**:给自洽的内联 SVG
94
+ * (不引 magic-test 的 sprite,也不依赖本壳的 `<use>` 图集 —— 图标万一缺,卡片会秃一块)。 */
95
+ global.QA_ICONS = {
96
+ agent: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"'
97
+ + ' stroke-linecap="round"><rect x="4" y="8" width="16" height="11" rx="3"/><path d="M12 8V5"/>'
98
+ + '<circle cx="9" cy="13" r="1"/><circle cx="15" cy="13" r="1"/></svg>',
99
+ ask: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"'
100
+ + ' stroke-linecap="round"><path d="M12 4a5 5 0 0 1 5 5c0 2-1.5 3-2.5 4-.6.6-1 1-1 2"/>'
101
+ + '<circle cx="12" cy="18.5" r="1"/></svg>',
102
+ tool: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"'
103
+ + ' stroke-linecap="round"><path d="M4 7h16M4 12h10M4 17h13"/></svg>',
104
+ };
105
+
106
+ /** 提问卡的「停靠位」清单 / 配置:magic-test 工作台有两处(home / home2),本壳只有一处,
107
+ * 且走固定的 `#home-askdock`(内核按 id 找它)→ 空清单:内核遍历它时什么也不做。 */
108
+ global.ASK_INSTANCES = [];
109
+ global.ASK_CFG = {};
110
+
111
+ /** PRD 后端的基址:本壳没有这个后端(产物图片走组件自己的 transport 回读)→ 空串。
112
+ * 内核侧既有口径是「基址为空就按相对路径处理」,不会拼出假地址。 */
113
+ global.prdBackendBase = '';
114
+
115
+ /* 下面这几个由组件写回真值(这里给的是「还没接上」时的初值)。 */
116
+ global.qaEngine = ''; // 当前引擎名
117
+ global.qaModel = ''; // 当前模型 id
118
+ global.qaModelPick = ''; // 用户手选过的模型('' = 自动);内核据此在降级后改选
119
+ global.qaEngines = []; // 引擎清单
120
+ global.qaActiveId = ''; // 当前会话 id(内核的落盘 / 对账要用)
121
+ global.qaTasks = []; // 任务列表:本壳是**单会话**壳,没有任务表 → 空
122
+ global.qaWorkspace = ''; // 本会话的工作目录:服务端维护、页面拿不到 → 空串
123
+
124
+ /* 头像(**可选**符号,内核读不到会退到自己的中性兜底,见 shared/conv/03-chat-message.js):
125
+ · bot 侧:本壳的资产目录里**没有** magic-test 那张应用图标 `assets/icon.png`
126
+ → 给空串 = 「没有这张图」,内核改用一枚中性机器人头(否则每条助手消息左上角是裂图);
127
+ · 用户侧:本壳没有工作台那个 `#nu-name` 显示名 → 不给 `QA_USER_INITIAL`,
128
+ 内核退到「我」(**不会**顶出别人的姓名首字)。 */
129
+ global.QA_BOT_AVATAR_SRC = '';
130
+
131
+ global.qaSend = qaSend;
132
+ global.qaModelName = qaModelName;
133
+ global.qaModelSet = qaModelSet;
134
+ global.qaModelFor = qaModelFor;
135
+ global.qaModelChainLoad = qaModelChainLoad;
136
+ global.qaEngHintSync = qaEngHintSync;
137
+ global.qaTaskSetModel = qaTaskSetModel;
138
+ global.qaReqConvMsg = qaReqConvMsg;
139
+ global.wsDirOf = wsDirOf;
140
+ global.reqHas = reqHas;
141
+ global.reqBoardFetch = reqBoardFetch;
142
+ global.toast = toast;
143
+ })(window);
@@ -10,8 +10,15 @@
10
10
  * GET /desk/engines?models=1 引擎 + 模型 + 每引擎能力
11
11
  * POST /desk/ask SSE:open / 事件 / end
12
12
  * POST /desk/ask/{id}/control 打断 / 收工 / 回答案
13
+ * GET /desk/asks 本进程正在跑的轮次(打开运行中会话续传用)
14
+ * GET /desk/ask/{id}/events?after=<seq> 断线续传:按 seq 增量补拉本轮事件
13
15
  * GET /desk/session/{id}/messages 全量历史
14
16
  * GET /desk/session/{id}/events?after=<seq> 增量续读
17
+ * POST /desk/upload 原子化上传附件(返回本机路径 + 回读 url)
18
+ * GET /desk/uploads/{name}?token=… 回读附件(缩略图;<img> 只能走 ?token=)
19
+ * GET /desk/media?path=<abs>&w=<px> 按绝对路径回读媒体(w = 移动端缩略图宽度)
20
+ * POST /desk/path-probe 一批本机路径的存在性探测(画产物用)
21
+ * POST /desk/path-read 读文本文件(产物卡「查看」阅读用)
15
22
  *
16
23
  * ── 用法 ──
17
24
  * const transport = MagicTransportHttp({ base: '', token: '…' }); // 同源时 base 传空串
@@ -24,12 +31,23 @@
24
31
  opts = opts || {};
25
32
  var BASE = String(opts.base || '').replace(/\/+$/, '');
26
33
  var TOKEN = opts.token || '';
34
+ // 发起方自报的客户端类型('mobile' | 'pc'):随每个 ask 请求带给服务端,
35
+ // 用于会话列表上的**来源标志**(同源时 Origin 分不出来,只能靠自报)。
36
+ var CLIENT = String(opts.client || '').trim();
27
37
 
28
38
  function hdr(extra) {
29
39
  var h = extra || {};
30
40
  if (TOKEN) h['X-Magic-Token'] = TOKEN;
31
41
  return h;
32
42
  }
43
+
44
+ /* 移动端慢网取图统一走 720 宽缩略图(服务端按需缩放;解不了 / 非图片自动回原文件)。
45
+ 返回 '' 或 '<sep>w=720':由调用方拼在已有 query 之后。 */
46
+ function thumbQuery(path, sep) {
47
+ if (CLIENT !== 'mobile') return '';
48
+ if (!/\.(?:png|jpe?g|gif|bmp|webp)$/i.test(String(path))) return '';
49
+ return sep + 'w=720';
50
+ }
33
51
  function api(path, o) {
34
52
  o = o || {};
35
53
  o.headers = hdr(o.headers);
@@ -39,7 +57,11 @@
39
57
  o.body = JSON.stringify(o.json);
40
58
  }
41
59
  return fetch(BASE + path, o).then(function (r) {
42
- if (!r.ok) throw new Error(r.status + ' ' + r.statusText);
60
+ if (!r.ok) {
61
+ var err = new Error(r.status + ' ' + r.statusText);
62
+ err.status = r.status; // 调用方要区分 404(无法续传)和网络抖动
63
+ throw err;
64
+ }
43
65
  return r;
44
66
  });
45
67
  }
@@ -50,8 +72,81 @@
50
72
  return api('/desk/engines?models=1').then(function (r) { return r.json(); });
51
73
  },
52
74
 
75
+ /* 上传附件:返回 {path, name, size, mime, url}。path 是 core 所在机器的绝对路径,
76
+ 由 ask 的 payload.attachments 透传给 -a/--attach;url 是回读地址(缩略图用,
77
+ 这里补成绝对地址并带上令牌)。multipart 的 Content-Type 交给浏览器带 boundary,不要自己设。 */
78
+ upload: function (file) {
79
+ var fd = new FormData();
80
+ fd.append('file', file, file.name || 'upload');
81
+ return fetch(BASE + '/desk/upload', { method: 'POST', headers: hdr(), body: fd })
82
+ .then(function (r) {
83
+ if (r.ok) return r.json().then(function (j) {
84
+ /* 回读地址补成绝对 + 带令牌:<img src> 带不了 X-Magic-Token 自定义头。 */
85
+ if (j && j.url) {
86
+ j.url = BASE + j.url + (TOKEN ? (j.url.indexOf('?') >= 0 ? '&' : '?') + 'token=' + encodeURIComponent(TOKEN) : '');
87
+ }
88
+ return j;
89
+ });
90
+ return r.json().catch(function () { return {}; }).then(function (j) {
91
+ throw new Error(j.error || (r.status + ' ' + r.statusText));
92
+ });
93
+ });
94
+ },
95
+
96
+ /* 附件回读地址:由本机绝对路径推出(上传目录内文件才有)。历史回放的缩略图 src 用它;
97
+ <img> 带不了 X-Magic-Token 自定义头,所以令牌走 ?token=。 */
98
+ mediaUrl: function (path) {
99
+ if (!path) return '';
100
+ var base = String(path).split(/[\\/]/).pop();
101
+ if (!base) return '';
102
+ return BASE + '/desk/uploads/' + encodeURIComponent(base) +
103
+ (TOKEN ? '?token=' + encodeURIComponent(TOKEN) : '') + thumbQuery(path, TOKEN ? '&' : '?');
104
+ },
105
+
106
+ /* 本机任意图片文件的回读地址(工具卡缩略图用):按**绝对路径**取。
107
+ <img> 带不了 X-Magic-Token 自定义头,所以令牌走 ?token=。 */
108
+ fileUrl: function (path) {
109
+ if (!path) return '';
110
+ return BASE + '/desk/media?path=' + encodeURIComponent(path) +
111
+ (TOKEN ? '&token=' + encodeURIComponent(TOKEN) : '') +
112
+ thumbQuery(path, '&');
113
+ },
114
+
115
+ /* 一批本机路径的**存在性探测**(内核画「会话产物」用,见 shared/conv/19-attachments.js):
116
+ 入参 {base, paths[]},回 {ok, items:[{path, abs, exists, size}]}。
117
+ 相对路径由服务端按 base(会话的 workspace)解析;服务端就在 core 那台机器上。 */
118
+ pathProbe: function (req) {
119
+ var body = {
120
+ base: (req && req.base) || '',
121
+ paths: (req && Array.isArray(req.paths)) ? req.paths : [],
122
+ };
123
+ return api('/desk/path-probe', { json: body }).then(function (r) { return r.json(); });
124
+ },
125
+
126
+ /* 按路径读一个**文本**文件(内核产物卡「查看」用):入参 {path, base},
127
+ 出参与桌面端 desk:path-read 一致 —— {ok, text, size, path} /
128
+ {ok, tooBig, size} / {ok, binary, size} / {ok:false, error}。 */
129
+ pathRead: function (req) {
130
+ var body = {
131
+ path: (req && req.path) || '',
132
+ base: (req && req.base) || '',
133
+ };
134
+ return api('/desk/path-read', { json: body }).then(function (r) { return r.json(); });
135
+ },
136
+
137
+ /* 会话标题覆盖表 {sid: title}(插件侧持久化,重命名用)。 */
138
+ listTitles: function () {
139
+ return api('/desk/titles').then(function (r) { return r.json(); });
140
+ },
141
+ setTitle: function (id, title) {
142
+ return api('/desk/session/' + encodeURIComponent(id) + '/title',
143
+ { method: 'PUT', json: { title: title || '' } }).then(function (r) { return r.json(); });
144
+ },
145
+
53
146
  /* 一轮:把 SSE 拆成三个回调交给组件,组件不关心里面是 SSE 还是别的。 */
54
147
  ask: function (payload, h) {
148
+ var body = payload || {};
149
+ if (CLIENT && !body.client) body.client = CLIENT;
55
150
  var aborted = false;
56
151
  var ctl = typeof AbortController !== 'undefined' ? new AbortController() : null;
57
152
  var ended = false;
@@ -60,7 +155,7 @@
60
155
  ended = true;
61
156
  if (h && h.onEnd) h.onEnd(code);
62
157
  };
63
- api('/desk/ask', { json: payload, signal: ctl ? ctl.signal : undefined })
158
+ api('/desk/ask', { json: body, signal: ctl ? ctl.signal : undefined })
64
159
  .then(function (resp) {
65
160
  var reader = resp.body.getReader(), dec = new TextDecoder(), buf = '';
66
161
  return (function pump() {
@@ -105,15 +200,44 @@
105
200
  .then(function () { return true; });
106
201
  },
107
202
 
203
+ /* 本进程**正在跑**的轮次(每项 {id, engine, started, session?})。
204
+ 打开一条「还在跑」的会话时用它认出同一会话的运行中轮次,把画面接回实时流:
205
+ 历史只重放到最后一条 user,其余从 ask 缓冲按 seq 补齐(见 conversation.js 的
206
+ tailAsk / attachRunningAsk)。拿不到 session 的旧服务端就当没有 —— 退回全量回放。 */
207
+ listAsks: function () {
208
+ return api('/desk/asks').then(function (r) { return r.json(); });
209
+ },
210
+
211
+ /* 断线续传:按 seq 增量拿本轮还没画过的事件。
212
+ 返回 {id, events, count, after, lastSeq, done, code, stderr, snapshotRequired}。
213
+ events 是 core 原样的事件对象,形状与 SSE 里逐条到达的完全一致;
214
+ done 之后 code/stderr 才有意义。服务端只保留有限时间窗,过期返回 404。 */
215
+ askEvents: function (id, after) {
216
+ var q = after == null ? '' : '?after=' + encodeURIComponent(after);
217
+ return api('/desk/ask/' + encodeURIComponent(id) + '/events' + q)
218
+ .then(function (r) { return r.json(); });
219
+ },
220
+
108
221
  readSession: function (id, o) {
109
222
  o = o || {};
110
- /* 有游标就走增量接口:长会话下差一个数量级。
111
- 形状与全量接口不同(多 lastSeq / snapshotRequired),一并原样交给组件。 */
223
+ /* 一律带 lean=1:服务端会裁掉每条事件的累积 snapshot(与 --session-log-lean 同口径)。
224
+ 长会话的 snapshot 是二次方膨胀(实测 9.4 万条 thinking = 283MB),不裁就要在浏览器里
225
+ 解析/传输整份 300MB+ JSON —— 移动端打开历史卡十几秒的主因。回放只用增量 text,裁掉不影响画面。
226
+ 老服务端会忽略这个未知参数,行为退回旧版(只是慢)。 */
112
227
  if (o.after != null) {
113
- return api('/desk/session/' + encodeURIComponent(id) + '/events?after=' + o.after)
228
+ return api('/desk/session/' + encodeURIComponent(id) + '/events?after=' + o.after + '&lean=1')
229
+ .then(function (r) { return r.json(); });
230
+ }
231
+ /* 反向分页:before = 从文件尾跳过多少条(不是 seq),limit = 本次最多要多少条。
232
+ 服务端保证一页至少含一个轮次起点(user),所以相邻页不会把同一轮切成两半。
233
+ 老服务端忽略未知参数 → 仍返回全量且**没有 more 字段**,调用方据此退回旧行为。 */
234
+ if (o.before != null) {
235
+ var q = '?lean=1&before=' + encodeURIComponent(o.before);
236
+ if (o.limit != null) q += '&limit=' + encodeURIComponent(o.limit);
237
+ return api('/desk/session/' + encodeURIComponent(id) + '/messages' + q)
114
238
  .then(function (r) { return r.json(); });
115
239
  }
116
- return api('/desk/session/' + encodeURIComponent(id) + '/messages')
240
+ return api('/desk/session/' + encodeURIComponent(id) + '/messages?lean=1')
117
241
  .then(function (r) { return r.json(); });
118
242
  }
119
243
  };