neoctl 0.2.16 → 0.2.17

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 (54) hide show
  1. package/dist/context/prompts.js +1 -5
  2. package/dist/context/prompts.js.map +1 -1
  3. package/dist/core/query-engine.d.ts +4 -2
  4. package/dist/core/query-engine.js +22 -11
  5. package/dist/core/query-engine.js.map +1 -1
  6. package/dist/core/query.d.ts +1 -0
  7. package/dist/core/query.js +3 -120
  8. package/dist/core/query.js.map +1 -1
  9. package/dist/core/smoke-core-loop.js +9 -1
  10. package/dist/core/smoke-core-loop.js.map +1 -1
  11. package/dist/model/anthropic-mapper.js +1 -3
  12. package/dist/model/anthropic-mapper.js.map +1 -1
  13. package/dist/model/model-gateway.d.ts +1 -0
  14. package/dist/model/model-gateway.js.map +1 -1
  15. package/dist/model/openai-chat-mapper.js +1 -0
  16. package/dist/model/openai-chat-mapper.js.map +1 -1
  17. package/dist/model/openai-mappers.js +1 -3
  18. package/dist/model/openai-mappers.js.map +1 -1
  19. package/dist/model/openai-responses-mapper.js +1 -0
  20. package/dist/model/openai-responses-mapper.js.map +1 -1
  21. package/dist/model/smoke-responses-mapper.js +5 -3
  22. package/dist/model/smoke-responses-mapper.js.map +1 -1
  23. package/dist/repl/index.js +5 -38
  24. package/dist/repl/index.js.map +1 -1
  25. package/dist/session/session-store.d.ts +10 -0
  26. package/dist/session/session-store.js +22 -1
  27. package/dist/session/session-store.js.map +1 -1
  28. package/dist/session/smoke-session.js +3 -0
  29. package/dist/session/smoke-session.js.map +1 -1
  30. package/dist/web/index.d.ts +10 -16
  31. package/dist/web/index.js +96 -194
  32. package/dist/web/index.js.map +1 -1
  33. package/package.json +1 -1
  34. package/scripts/install-ripgrep.cjs +196 -196
  35. package/vendor/ripgrep/darwin-arm64/COPYING +3 -3
  36. package/vendor/ripgrep/darwin-arm64/LICENSE-MIT +21 -21
  37. package/vendor/ripgrep/darwin-arm64/UNLICENSE +24 -24
  38. package/vendor/ripgrep/darwin-arm64/manifest.json +7 -7
  39. package/vendor/ripgrep/darwin-x64/COPYING +3 -3
  40. package/vendor/ripgrep/darwin-x64/LICENSE-MIT +21 -21
  41. package/vendor/ripgrep/darwin-x64/UNLICENSE +24 -24
  42. package/vendor/ripgrep/darwin-x64/manifest.json +7 -7
  43. package/vendor/ripgrep/linux-arm64/COPYING +3 -3
  44. package/vendor/ripgrep/linux-arm64/LICENSE-MIT +21 -21
  45. package/vendor/ripgrep/linux-arm64/UNLICENSE +24 -24
  46. package/vendor/ripgrep/linux-arm64/manifest.json +7 -7
  47. package/vendor/ripgrep/linux-x64/COPYING +3 -3
  48. package/vendor/ripgrep/linux-x64/LICENSE-MIT +21 -21
  49. package/vendor/ripgrep/linux-x64/UNLICENSE +24 -24
  50. package/vendor/ripgrep/linux-x64/manifest.json +7 -7
  51. package/vendor/ripgrep/win32-arm64/COPYING +3 -3
  52. package/vendor/ripgrep/win32-arm64/LICENSE-MIT +21 -21
  53. package/vendor/ripgrep/win32-arm64/UNLICENSE +24 -24
  54. package/vendor/ripgrep/win32-arm64/manifest.json +7 -7
package/dist/web/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import http from "node:http";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { createReadStream, existsSync, readFileSync } from "node:fs";
5
+ import { existsSync, readFileSync } from "node:fs";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { createRequire } from "node:module";
8
8
  import { QueryEngine } from "../core/query-engine.js";
@@ -22,20 +22,14 @@ import { searchTool } from "../tools/builtins/search-tool.js";
22
22
  import { planTool } from "../tools/builtins/plan-tool.js";
23
23
  import { createOpenAIImageGenerationTool } from "../tools/builtins/image-generation-tool.js";
24
24
  import { createLoadImageTool } from "../tools/builtins/image-loader-tool.js";
25
- import { createImageNoteTool } from "../tools/builtins/image-note-tool.js";
26
25
  import { createAgentTool, resumeAgentTask } from "../agents/agent-tool.js";
27
26
  import { createTaskTools } from "../tasks/task-tools.js";
28
27
  import { TaskStore } from "../tasks/task-store.js";
29
28
  import { parseReplCommand, helpText, replCommandDefinitions } from "../repl/commands.js";
30
29
  import { writeSessionMarkdownExport } from "../session/session-export.js";
31
- import { DefaultContextManager } from "../context/context-manager.js";
32
- import { buildEffectiveSystemPrompt } from "../context/prompts.js";
33
30
  import { WEB_HTML } from "./html.js";
34
31
  import { openDirectory } from "../open-directory.js";
35
- import { getNeoctlHome } from "../paths.js";
36
- import { FileSystemSkillCatalog } from "../skills/skill-filesystem.js";
37
- import { createSkillTool } from "../skills/skill-tool.js";
38
- import { createSkillManagementTools } from "../skills/skill-management-tools.js";
32
+ import { resolveImageBlockDataSync } from "../core/image-storage.js";
39
33
  const require = createRequire(import.meta.url);
40
34
  const markedPackageDir = path.dirname(require.resolve("marked/package.json"));
41
35
  const highlightPackageDir = path.dirname(require.resolve("@highlightjs/cdn-assets/package.json"));
@@ -114,98 +108,13 @@ function parseWebArgs(argv) {
114
108
  port = 3000;
115
109
  return { host, port: Math.round(port) };
116
110
  }
117
- class SkillCatalogContextManager {
118
- catalog;
119
- base;
120
- constructor(catalog, base = new DefaultContextManager()) {
121
- this.catalog = catalog;
122
- this.base = base;
123
- }
124
- async build(input) {
125
- const runtimeContext = await this.base.build(input);
126
- const skillSection = await buildSkillCatalogPromptSection(this.catalog);
127
- if (!skillSection)
128
- return runtimeContext;
129
- const promptSections = [...runtimeContext.promptSections, skillSection];
130
- return {
131
- ...runtimeContext,
132
- promptSections,
133
- systemPrompt: buildEffectiveSystemPrompt(promptSections, input),
134
- };
135
- }
136
- }
137
- async function buildSkillCatalogPromptSection(catalog) {
138
- const skills = await catalog.list();
139
- if (skills.length === 0)
140
- return undefined;
141
- const visible = skills.slice(0, 80);
142
- const lines = visible.map((skill) => {
143
- const tags = skill.tags?.length ? `; tags=${skill.tags.join(",")}` : "";
144
- const tools = skill.allowedTools?.length ? `; allowedTools=${skill.allowedTools.join(",")}` : "";
145
- return `- ${skill.name}: ${skill.description} (execution=${skill.execution}${tags}${tools})`;
146
- });
147
- if (skills.length > visible.length)
148
- lines.push(`- ... ${skills.length - visible.length} more skills available; use skill_list for the full catalog.`);
149
- return {
150
- name: "Available Skills",
151
- cacheStable: false,
152
- content: [
153
- "Reusable skills are available through the `skill` tool and the /skill UI command.",
154
- "When the user's task matches a skill name, description, tags, or domain capability, proactively call the `skill` tool before doing the work directly.",
155
- "Do not wait for the user to explicitly say 'use skill'. Use skill_list/skill_read if you need to inspect details.",
156
- "Available skill catalog:",
157
- ...lines,
158
- ].join("\n"),
159
- };
160
- }
161
- function resolveSkillCatalogRoots(cwd, extraRoots = [], createRoot) {
162
- const userRoot = path.resolve(process.env.NEO_SKILL_CREATE_ROOT || createRoot || path.join(getNeoctlHome(), "skills"));
163
- const configuredRoots = splitPathList(process.env.NEO_SKILL_ROOTS);
164
- const workspaceRoot = path.resolve(cwd, ".neo", "skills");
165
- const roots = uniquePaths([
166
- userRoot,
167
- ...extraRoots,
168
- ...configuredRoots,
169
- workspaceRoot,
170
- ]).map((root) => ({
171
- root,
172
- kind: path.resolve(root) === userRoot ? "user" : "workspace",
173
- }));
174
- return { roots, createRoot: userRoot };
175
- }
176
- function splitPathList(value) {
177
- return String(value || "")
178
- .split(path.delimiter)
179
- .map((item) => item.trim())
180
- .filter(Boolean)
181
- .map((item) => path.resolve(item));
182
- }
183
- function uniquePaths(values) {
184
- const seen = new Set();
185
- const result = [];
186
- for (const value of values) {
187
- const resolved = path.resolve(value);
188
- const key = process.platform === "win32" ? resolved.toLowerCase() : resolved;
189
- if (seen.has(key))
190
- continue;
191
- seen.add(key);
192
- result.push(resolved);
193
- }
194
- return result;
195
- }
196
111
  export async function createWebRuntime(options = {}) {
197
112
  const envLoad = loadDefaultDotEnvFiles({ override: true });
198
- const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
199
113
  const modelConfig = readModelProviderConfig(process.env);
200
114
  const communicationLogger = new CommunicationLogger();
201
115
  const modelGateway = new LoggingModelGateway(createModelGatewayFromProcessEnv(process.env), communicationLogger);
202
116
  const taskStore = new TaskStore();
203
117
  const tools = new ToolRegistry();
204
- const { roots: skillRoots, createRoot: skillCreateRoot } = resolveSkillCatalogRoots(cwd, options.skillRoots, options.skillCreateRoot);
205
- const skills = new FileSystemSkillCatalog({
206
- roots: skillRoots,
207
- createRoot: skillCreateRoot,
208
- });
209
118
  tools.register(editTool);
210
119
  tools.register(writeTool);
211
120
  tools.register(createExecTool({ taskStore }));
@@ -214,13 +123,9 @@ export async function createWebRuntime(options = {}) {
214
123
  tools.register(grepTool);
215
124
  tools.register(searchTool);
216
125
  tools.register(createLoadImageTool());
217
- tools.register(createImageNoteTool());
218
126
  if (modelConfig?.provider === "openai")
219
127
  tools.register(createOpenAIImageGenerationTool());
220
128
  tools.register(planTool);
221
- tools.register(createSkillTool(skills));
222
- for (const tool of createSkillManagementTools(skills, { requireApproval: true, allowDelete: false }))
223
- tools.register(tool);
224
129
  for (const tool of options.externalTools ?? [])
225
130
  tools.register(tool);
226
131
  const agentRuntime = { modelGateway, tools, taskStore };
@@ -229,7 +134,7 @@ export async function createWebRuntime(options = {}) {
229
134
  const dummyContext = {
230
135
  agentId: "main",
231
136
  tools,
232
- appState: new InMemoryAppState("main", cwd),
137
+ appState: new InMemoryAppState("main"),
233
138
  emit: () => undefined,
234
139
  };
235
140
  return resumeAgentTask(taskId, directive, agentRuntime, taskStore, dummyContext);
@@ -239,7 +144,6 @@ export async function createWebRuntime(options = {}) {
239
144
  const appPromptStore = new InMemoryAppPromptStore();
240
145
  const engine = new QueryEngine({
241
146
  agentId: options.agentId ?? "main",
242
- cwd,
243
147
  model: modelConfig?.model,
244
148
  fallbackModel: modelConfig?.fallbackModel,
245
149
  reasoning: modelConfig?.defaultReasoning,
@@ -247,8 +151,6 @@ export async function createWebRuntime(options = {}) {
247
151
  modelGateway,
248
152
  tools,
249
153
  appPromptStore,
250
- contextManager: new SkillCatalogContextManager(skills),
251
- skills: (await skills.list()).map((skill) => skill.name),
252
154
  taskNotificationSource: createTaskNotificationSource(taskStore),
253
155
  commands: replCommandDefinitions.map((command) => command.usage),
254
156
  session: {
@@ -398,6 +300,7 @@ export class WebRepl {
398
300
  backgroundSessionRunCount: this.backgroundSessionRuns.size,
399
301
  runningSessionIds: [...this.backgroundSessionRuns.keys()],
400
302
  session: this.runtime.engine.snapshot().session,
303
+ fastMode: this.runtime.engine.isFastMode(),
401
304
  appPrompt: this.runtime.engine.getAppPrompt(),
402
305
  catalog: includeCatalog ? webCatalog(this.runtime) : undefined,
403
306
  interactive: includeCatalog ? webInteractiveCatalog(this.runtime) : undefined,
@@ -533,6 +436,16 @@ export class WebRepl {
533
436
  return { ok: false, error: message };
534
437
  }
535
438
  }
439
+ async setFastMode(enabled) {
440
+ try {
441
+ const fastMode = await this.runtime.engine.setFastMode(enabled);
442
+ this.broadcastSync();
443
+ return { ok: true, fastMode };
444
+ }
445
+ catch (error) {
446
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
447
+ }
448
+ }
536
449
  loginForm(providerValue) {
537
450
  const provider = parseLoginProvider(providerValue);
538
451
  return createLoginFormPayload(this.runtime.envPath, provider);
@@ -603,7 +516,7 @@ export class WebRepl {
603
516
  this.broadcastSync();
604
517
  }
605
518
  setStatus(next) {
606
- this.status = next.phase === "running_tools" ? next : { ...next, currentTool: undefined };
519
+ this.status = next;
607
520
  this.broadcastSync();
608
521
  }
609
522
  finalizeForegroundView() {
@@ -778,13 +691,10 @@ export class WebRepl {
778
691
  if (event.type === "tool.started") {
779
692
  this.finalizeLiveLine(this.assistantLineId);
780
693
  this.finalizeThinkingLine();
781
- this.broadcastSync();
782
694
  return;
783
695
  }
784
- if (event.type === "tool.finished") {
785
- this.broadcastSync();
696
+ if (event.type === "tool.finished")
786
697
  return;
787
- }
788
698
  if (event.type === "terminal") {
789
699
  this.finalizeLiveLine(this.assistantLineId);
790
700
  this.finalizeThinkingLine();
@@ -999,14 +909,10 @@ export class WebRepl {
999
909
  res.write(`event: ${event}\n`);
1000
910
  res.write(`data: ${JSON.stringify(data)}\n\n`);
1001
911
  }
1002
- sendSerialized(res, event, data) {
1003
- res.write(`event: ${event}\n`);
1004
- res.write(`data: ${data}\n\n`);
1005
- }
1006
912
  broadcastSync() {
1007
- const payload = JSON.stringify(this.snapshot(false));
913
+ const payload = this.snapshot(false);
1008
914
  for (const res of this.subscribers)
1009
- this.sendSerialized(res, "sync", payload);
915
+ this.send(res, "sync", payload);
1010
916
  }
1011
917
  }
1012
918
  function reqKeepAlive(res) {
@@ -1025,8 +931,6 @@ async function route(req, res, router) {
1025
931
  return sendFile(res, highlightAssetPath, "text/javascript; charset=utf-8");
1026
932
  if (req.method === "GET" && url.pathname === "/vendor/highlight-theme.css")
1027
933
  return sendFile(res, highlightThemeAssetPath, "text/css; charset=utf-8");
1028
- if (req.method === "GET" && url.pathname === "/api/images")
1029
- return sendImage(res, url.searchParams.get("path"), url.searchParams.get("mime"));
1030
934
  const scope = webRuntimeScopeFromUrl(url);
1031
935
  const repl = await router.get(scope);
1032
936
  if (req.method === "GET" && url.pathname === "/events")
@@ -1039,6 +943,10 @@ async function route(req, res, router) {
1039
943
  const body = await readJsonBody(req);
1040
944
  return sendJson(res, repl.setAppPrompt(body));
1041
945
  }
946
+ if (req.method === "POST" && url.pathname === "/api/fast-mode") {
947
+ const body = await readJsonBody(req);
948
+ return sendJson(res, await repl.setFastMode(body.enabled === true));
949
+ }
1042
950
  if (req.method === "POST" && url.pathname === "/api/submit") {
1043
951
  const body = await readJsonBody(req);
1044
952
  return sendJson(res, await repl.submit(String(body.text ?? ""), sanitizeWebAttachments(body.attachments)));
@@ -1090,23 +998,6 @@ async function sendFile(res, filepath, contentType) {
1090
998
  res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "public, max-age=3600" });
1091
999
  res.end(body);
1092
1000
  }
1093
- async function sendImage(res, encodedPath, mimeType) {
1094
- const filepath = decodeImagePath(encodedPath);
1095
- if (!filepath)
1096
- return sendJson(res, { error: "missing image path" }, 400);
1097
- const contentType = safeImageContentType(mimeType);
1098
- const binaryPath = filepath.endsWith(".base64.txt") ? filepath.slice(0, -".base64.txt".length) : filepath;
1099
- if (existsSync(binaryPath)) {
1100
- res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "no-store" });
1101
- createReadStream(binaryPath).pipe(res);
1102
- return;
1103
- }
1104
- if (!existsSync(filepath))
1105
- return sendJson(res, { error: "image not found" }, 404);
1106
- const base64 = (await fs.readFile(filepath, "utf8")).trim();
1107
- res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "no-store" });
1108
- res.end(Buffer.from(stripDataUrlPrefix(base64), "base64"));
1109
- }
1110
1001
  function sendJson(res, value, status = 200) {
1111
1002
  res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
1112
1003
  res.end(JSON.stringify(value));
@@ -1209,11 +1100,7 @@ function pushTextBlock(blocks, text) {
1209
1100
  }
1210
1101
  function reduceStatus(status, event) {
1211
1102
  if (event.type === "state")
1212
- return { ...status, phase: event.phase, detail: event.detail, currentTool: event.phase === "preparing" || event.phase === "calling_model" || event.phase === "ready" ? undefined : status.currentTool, usage: event.phase === "preparing" ? undefined : status.usage, streamedOutputTokens: event.phase === "preparing" ? 0 : status.streamedOutputTokens, inputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.inputTokenUpdatedAt, outputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.outputTokenUpdatedAt, retryCooldownUntil: event.phase === "preparing" ? undefined : status.retryCooldownUntil, activityTick: status.activityTick + 1 };
1213
- if (event.type === "tool.started")
1214
- return { ...status, phase: "running_tools", detail: event.toolUse.name, currentTool: { id: event.toolUse.id, name: event.toolUse.name, kind: toolKindForToolUse(event.toolUse.name, event.toolUse.input), startedAt: Date.now() }, activityTick: status.activityTick + 1 };
1215
- if (event.type === "tool.finished")
1216
- return { ...status, currentTool: status.currentTool?.id === event.toolUse.id ? undefined : status.currentTool, activityTick: status.activityTick + 1 };
1103
+ return { ...status, phase: event.phase, detail: event.detail, usage: event.phase === "preparing" ? undefined : status.usage, streamedOutputTokens: event.phase === "preparing" ? 0 : status.streamedOutputTokens, inputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.inputTokenUpdatedAt, outputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.outputTokenUpdatedAt, retryCooldownUntil: event.phase === "preparing" ? undefined : status.retryCooldownUntil, activityTick: status.activityTick + 1 };
1217
1104
  if (event.type === "context.metrics")
1218
1105
  return { ...status, metrics: event.metrics, inputTokenUpdatedAt: event.metrics.estimatedInputTokens !== status.metrics?.estimatedInputTokens ? Date.now() : status.inputTokenUpdatedAt, activityTick: status.activityTick + 1 };
1219
1106
  if (event.type === "usage")
@@ -1227,26 +1114,11 @@ function reduceStatus(status, event) {
1227
1114
  if (event.type === "retrying")
1228
1115
  return { ...status, phase: "calling_model", detail: `retrying in ${(event.delayMs / 1000).toFixed(1)}s`, retryCooldownUntil: Date.now() + event.delayMs, activityTick: status.activityTick + 1 };
1229
1116
  if (event.type === "terminal")
1230
- return { ...status, phase: "stopped", detail: event.reason, currentTool: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined, activityTick: status.activityTick + 1 };
1231
- if (event.type === "message" || event.type === "error")
1117
+ return { ...status, phase: "stopped", detail: event.reason, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined, activityTick: status.activityTick + 1 };
1118
+ if (event.type === "message" || event.type === "tool.started" || event.type === "tool.finished" || event.type === "error")
1232
1119
  return { ...status, activityTick: status.activityTick + 1 };
1233
1120
  return status;
1234
1121
  }
1235
- function toolKindForToolUse(toolName, input) {
1236
- if (toolName === "image2")
1237
- return isRecord(input) && input.mode === "edit" ? "修图" : "作图";
1238
- if (toolName === "edit" || toolName === "write" || toolName.includes("artifact_editor") || toolName.includes("apply_patch"))
1239
- return "编辑";
1240
- if (toolName === "exec" || toolName.includes("shell") || toolName.includes("command"))
1241
- return "执行";
1242
- if (toolName.includes("download"))
1243
- return "下载";
1244
- if (toolName === "read" || toolName === "list" || toolName === "grep" || toolName === "search" || toolName.includes("query") || toolName.includes("load"))
1245
- return "查询";
1246
- if (toolName === "plan")
1247
- return "计划";
1248
- return "工具";
1249
- }
1250
1122
  async function handleExportCommand(outputPath, runtime) {
1251
1123
  const snapshot = runtime.engine.snapshot();
1252
1124
  if (!snapshot.session)
@@ -1647,43 +1519,20 @@ function imageLineForBlock(role, block) {
1647
1519
  kind,
1648
1520
  text: block.label ?? `[image ${block.mimeType}]`,
1649
1521
  image: {
1650
- src: imageBlockToSrc(block),
1522
+ src: imageBlockToDataUrl(block),
1651
1523
  label: block.label,
1652
1524
  mimeType: block.mimeType,
1653
1525
  },
1654
1526
  };
1655
1527
  }
1656
- function imageBlockToSrc(block) {
1657
- if (block.storage?.path) {
1658
- return `/api/images?path=${encodeImagePath(block.storage.path)}&mime=${encodeURIComponent(block.mimeType)}`;
1659
- }
1660
- const data = block.data.trim();
1528
+ function imageBlockToDataUrl(block) {
1529
+ const data = resolveImageBlockDataSync(block);
1661
1530
  if (!data)
1662
1531
  return "";
1663
1532
  if (data.startsWith("data:"))
1664
1533
  return data;
1665
1534
  return `data:${block.mimeType};base64,${data}`;
1666
1535
  }
1667
- function encodeImagePath(filepath) {
1668
- return Buffer.from(filepath, "utf8").toString("base64url");
1669
- }
1670
- function decodeImagePath(value) {
1671
- if (!value)
1672
- return undefined;
1673
- try {
1674
- return Buffer.from(value, "base64url").toString("utf8");
1675
- }
1676
- catch {
1677
- return undefined;
1678
- }
1679
- }
1680
- function safeImageContentType(value) {
1681
- return value && /^image\/[a-z0-9.+-]+$/i.test(value) ? value : "application/octet-stream";
1682
- }
1683
- function stripDataUrlPrefix(value) {
1684
- const match = /^data:[^;]+;base64,(.*)$/is.exec(value);
1685
- return match ? match[1] : value;
1686
- }
1687
1536
  function assistantText(message) {
1688
1537
  const text = message.blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
1689
1538
  return text.length > 0 ? text : undefined;
@@ -1736,30 +1585,59 @@ function thinkingLine(text, live = false) {
1736
1585
  return { kind: "thinking", title: titleForKind("thinking"), text, previewStyle: "summary", summaryMaxLines: THINKING_SUMMARY_MAX_LINES, live };
1737
1586
  }
1738
1587
  function formatToolUse(toolUse) {
1739
- const toolKind = toolKindForToolUse(toolUse.name, toolUse.input);
1740
1588
  if (toolUse.name === "plan" && isPlanToolPayload(toolUse.input))
1741
- return { kind: "tool", title: toolTitle(toolUse.name, "running"), bodyTitle: planToolBodyTitle(toolUse.input), toolKind, text: formatPlanToolPayload(toolUse.input), collapsible: true };
1742
- const description = toolUse.name === "exec" ? execDescriptionFromInput(toolUse.input) : undefined;
1743
- return { kind: "tool", title: toolTitle(toolUse.name, "running"), bodyTitle: description, toolKind, text: formatReplData(toolUse.input, 1200), previewStyle: "summary", collapsible: true };
1589
+ return { kind: "tool", title: toolTitle(toolUse.name, "running"), bodyTitle: planToolBodyTitle(toolUse.input), text: formatPlanToolPayload(toolUse.input), collapsible: true };
1590
+ const summary = summarizeToolUse(toolUse.name, toolUse.input);
1591
+ return { kind: "tool", title: toolTitle(toolUse.name, "running"), bodyTitle: summary.bodyTitle, text: summary.text, previewStyle: "summary", collapsible: true };
1744
1592
  }
1745
1593
  function formatToolResultLine(toolName, output, ok) {
1746
1594
  const formatted = formatToolResult(toolName, output, ok);
1747
- return { kind: ok ? "tool" : "error", title: toolTitle(toolName, "finished"), bodyTitle: formatted.bodyTitle, titleStatus: ok ? "success" : "failure", toolKind: toolKindForToolUse(toolName, undefined), text: formatted.text, format: formatted.format, live: false, previewStyle: formatted.full ? undefined : "summary", summaryMaxLines: formatted.summaryMaxLines, collapsible: true, artifact: xhsArtifactFromToolOutput(toolName, output) };
1748
- }
1749
- function xhsArtifactFromToolOutput(toolName, output) {
1750
- if (toolName !== "open_xhs_artifact_editor" || !isRecord(output))
1751
- return undefined;
1752
- const artifact = output.artifact;
1753
- return isRecord(artifact) && typeof artifact.id === "string" ? artifact : undefined;
1595
+ return { kind: ok ? "tool" : "error", title: toolTitle(toolName, "finished"), bodyTitle: formatted.bodyTitle, titleStatus: ok ? "success" : "failure", text: formatted.text, format: formatted.format, live: false, previewStyle: formatted.full ? undefined : "summary", summaryMaxLines: formatted.summaryMaxLines, collapsible: true };
1754
1596
  }
1755
1597
  function toolTitle(toolName, _phase) {
1756
- return toolName;
1598
+ const labels = {
1599
+ agent: "子任务",
1600
+ edit: "编辑文件",
1601
+ exec: "执行命令",
1602
+ expose_downloads: "文件下载",
1603
+ grep: "搜索文本",
1604
+ image2: "图片生成",
1605
+ image_note: "记录图片说明",
1606
+ list: "列出文件",
1607
+ load_image: "读取图片",
1608
+ plan: "任务计划",
1609
+ read: "读取文件",
1610
+ search: "网络搜索",
1611
+ write: "写入文件",
1612
+ };
1613
+ return labels[toolName] ?? toolName;
1757
1614
  }
1758
- function execDescriptionFromInput(input) {
1615
+ function summarizeToolUse(toolName, input) {
1616
+ const purpose = toolUsePurpose(toolName, input);
1617
+ return {
1618
+ bodyTitle: purpose,
1619
+ text: purpose ? `目的:${purpose}` : "正在执行工具…",
1620
+ };
1621
+ }
1622
+ function toolUsePurpose(toolName, input) {
1759
1623
  if (!isRecord(input))
1760
1624
  return undefined;
1761
1625
  const description = typeof input.description === "string" ? input.description.trim() : "";
1762
- return description || undefined;
1626
+ if (description)
1627
+ return description;
1628
+ if (toolName === "expose_downloads")
1629
+ return "准备网页下载链接";
1630
+ if (toolName === "read" && typeof input.path === "string")
1631
+ return `读取 ${path.basename(input.path)}`;
1632
+ if (toolName === "list" && typeof input.path === "string")
1633
+ return `查看 ${input.path}`;
1634
+ if (toolName === "grep" && typeof input.query === "string")
1635
+ return `搜索 ${input.query}`;
1636
+ if (toolName === "write" && typeof input.path === "string")
1637
+ return `写入 ${path.basename(input.path)}`;
1638
+ if (toolName === "edit" && typeof input.path === "string")
1639
+ return `修改 ${path.basename(input.path)}`;
1640
+ return undefined;
1763
1641
  }
1764
1642
  function isPlanToolPayload(value) {
1765
1643
  if (!isRecord(value) || !Array.isArray(value.items))
@@ -1794,12 +1672,36 @@ function formatToolResult(toolName, output, ok) {
1794
1672
  return { text: formatWebSearchToolResult(output, ok), summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
1795
1673
  if (toolName === "image2" && isRecord(output))
1796
1674
  return { text: formatImageGenerationToolResult(output, ok), format: "plain", summaryMaxLines: 4 };
1675
+ if (toolName === "expose_downloads")
1676
+ return { text: formatExposeDownloadsToolResult(output, ok), full: true, bodyTitle: ok ? "请点击下载" : "下载准备失败" };
1797
1677
  if (toolName === "plan" && isPlanToolPayload(output))
1798
1678
  return { text: formatPlanToolPayload(output), full: true, bodyTitle: planToolBodyTitle(output) };
1799
1679
  if (typeof output === "string")
1800
1680
  return { text: output, format: hasAnsi(output) ? "ansi" : undefined, summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
1801
1681
  return { text: `${ok ? "ok" : "failed"}\n${formatReplData(output, 6000)}`, summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
1802
1682
  }
1683
+ function formatExposeDownloadsToolResult(output, ok) {
1684
+ const downloads = extractDownloadEntries(output);
1685
+ if (!ok)
1686
+ return downloads.length ? `下载准备失败\n${formatDownloadEntries(downloads)}` : "下载准备失败";
1687
+ if (!downloads.length)
1688
+ return "文件已准备好,请点击下载。";
1689
+ return `文件已准备好,请点击下载。\n${formatDownloadEntries(downloads)}`;
1690
+ }
1691
+ function formatDownloadEntries(downloads) {
1692
+ return downloads.map((entry) => {
1693
+ const filename = entry.filename || entry.name || "下载文件";
1694
+ const url = entry.url || (entry.id ? `/api/downloads/${encodeURIComponent(entry.id)}` : "");
1695
+ return url ? `- ${filename}: ${url}` : `- ${filename}`;
1696
+ }).join("\n");
1697
+ }
1698
+ function extractDownloadEntries(output) {
1699
+ if (!isRecord(output))
1700
+ return [];
1701
+ const candidates = [output.downloads, isRecord(output.output) ? output.output.downloads : undefined, isRecord(output.result) ? output.result.downloads : undefined];
1702
+ const downloads = candidates.find(Array.isArray);
1703
+ return Array.isArray(downloads) ? downloads.filter(isRecord).map((entry) => ({ ...entry })) : [];
1704
+ }
1803
1705
  function isEditToolOutput(value) {
1804
1706
  return typeof value.path === "string" && typeof value.operation === "string" && typeof value.replacements === "number" && Array.isArray(value.patch) && value.patch.every(isEditPatchHunk);
1805
1707
  }
@@ -2122,4 +2024,4 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
2122
2024
  process.exitCode = 1;
2123
2025
  });
2124
2026
  }
2125
- //# sourceMappingURL=index.js.map
2027
+ //# sourceMappingURL=index.js.map