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.
- package/dist/context/prompts.js +1 -5
- package/dist/context/prompts.js.map +1 -1
- package/dist/core/query-engine.d.ts +4 -2
- package/dist/core/query-engine.js +22 -11
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +1 -0
- package/dist/core/query.js +3 -120
- package/dist/core/query.js.map +1 -1
- package/dist/core/smoke-core-loop.js +9 -1
- package/dist/core/smoke-core-loop.js.map +1 -1
- package/dist/model/anthropic-mapper.js +1 -3
- package/dist/model/anthropic-mapper.js.map +1 -1
- package/dist/model/model-gateway.d.ts +1 -0
- package/dist/model/model-gateway.js.map +1 -1
- package/dist/model/openai-chat-mapper.js +1 -0
- package/dist/model/openai-chat-mapper.js.map +1 -1
- package/dist/model/openai-mappers.js +1 -3
- package/dist/model/openai-mappers.js.map +1 -1
- package/dist/model/openai-responses-mapper.js +1 -0
- package/dist/model/openai-responses-mapper.js.map +1 -1
- package/dist/model/smoke-responses-mapper.js +5 -3
- package/dist/model/smoke-responses-mapper.js.map +1 -1
- package/dist/repl/index.js +5 -38
- package/dist/repl/index.js.map +1 -1
- package/dist/session/session-store.d.ts +10 -0
- package/dist/session/session-store.js +22 -1
- package/dist/session/session-store.js.map +1 -1
- package/dist/session/smoke-session.js +3 -0
- package/dist/session/smoke-session.js.map +1 -1
- package/dist/web/index.d.ts +10 -16
- package/dist/web/index.js +96 -194
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/install-ripgrep.cjs +196 -196
- package/vendor/ripgrep/darwin-arm64/COPYING +3 -3
- package/vendor/ripgrep/darwin-arm64/LICENSE-MIT +21 -21
- package/vendor/ripgrep/darwin-arm64/UNLICENSE +24 -24
- package/vendor/ripgrep/darwin-arm64/manifest.json +7 -7
- package/vendor/ripgrep/darwin-x64/COPYING +3 -3
- package/vendor/ripgrep/darwin-x64/LICENSE-MIT +21 -21
- package/vendor/ripgrep/darwin-x64/UNLICENSE +24 -24
- package/vendor/ripgrep/darwin-x64/manifest.json +7 -7
- package/vendor/ripgrep/linux-arm64/COPYING +3 -3
- package/vendor/ripgrep/linux-arm64/LICENSE-MIT +21 -21
- package/vendor/ripgrep/linux-arm64/UNLICENSE +24 -24
- package/vendor/ripgrep/linux-arm64/manifest.json +7 -7
- package/vendor/ripgrep/linux-x64/COPYING +3 -3
- package/vendor/ripgrep/linux-x64/LICENSE-MIT +21 -21
- package/vendor/ripgrep/linux-x64/UNLICENSE +24 -24
- package/vendor/ripgrep/linux-x64/manifest.json +7 -7
- package/vendor/ripgrep/win32-arm64/COPYING +3 -3
- package/vendor/ripgrep/win32-arm64/LICENSE-MIT +21 -21
- package/vendor/ripgrep/win32-arm64/UNLICENSE +24 -24
- 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 {
|
|
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 {
|
|
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"
|
|
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
|
|
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 =
|
|
913
|
+
const payload = this.snapshot(false);
|
|
1008
914
|
for (const res of this.subscribers)
|
|
1009
|
-
this.
|
|
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,
|
|
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,
|
|
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:
|
|
1522
|
+
src: imageBlockToDataUrl(block),
|
|
1651
1523
|
label: block.label,
|
|
1652
1524
|
mimeType: block.mimeType,
|
|
1653
1525
|
},
|
|
1654
1526
|
};
|
|
1655
1527
|
}
|
|
1656
|
-
function
|
|
1657
|
-
|
|
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),
|
|
1742
|
-
const
|
|
1743
|
-
return { kind: "tool", title: toolTitle(toolUse.name, "running"), bodyTitle:
|
|
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",
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|