neoctl 0.2.27 → 0.2.28
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/README.md +35 -47
- package/dist/agents/agent-definition.js +1 -1
- package/dist/agents/agent-definition.js.map +1 -1
- package/dist/agents/agent-tool.js +1 -1
- package/dist/agents/agent-tool.js.map +1 -1
- package/dist/agents/smoke-agents.js +2 -2
- package/dist/agents/smoke-agents.js.map +1 -1
- package/dist/context/compaction.js +8 -5
- package/dist/context/compaction.js.map +1 -1
- package/dist/context/context-manager.d.ts +8 -9
- package/dist/context/context-manager.js +33 -41
- package/dist/context/context-manager.js.map +1 -1
- package/dist/context/prompts.js +1 -1
- package/dist/context/prompts.js.map +1 -1
- package/dist/context/smoke-context.js +30 -8
- package/dist/context/smoke-context.js.map +1 -1
- package/dist/core/context-metrics.d.ts +1 -1
- package/dist/core/context-metrics.js +5 -3
- package/dist/core/context-metrics.js.map +1 -1
- package/dist/core/message-pipeline.js +13 -21
- package/dist/core/message-pipeline.js.map +1 -1
- package/dist/core/prompt-cache-key.d.ts +10 -0
- package/dist/core/prompt-cache-key.js +34 -0
- package/dist/core/prompt-cache-key.js.map +1 -0
- package/dist/core/prompt-cache-telemetry.d.ts +2 -1
- package/dist/core/prompt-cache-telemetry.js +23 -1
- package/dist/core/prompt-cache-telemetry.js.map +1 -1
- package/dist/core/query-engine.d.ts +6 -5
- package/dist/core/query-engine.js +18 -18
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +0 -1
- package/dist/core/query.js +22 -14
- package/dist/core/query.js.map +1 -1
- package/dist/core/run-agent.d.ts +0 -1
- package/dist/core/run-agent.js +0 -1
- package/dist/core/run-agent.js.map +1 -1
- package/dist/core/smoke-core-loop.js +27 -3
- package/dist/core/smoke-core-loop.js.map +1 -1
- package/dist/core/state.d.ts +0 -2
- package/dist/core/state.js +0 -1
- package/dist/core/state.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/model/config.d.ts +2 -9
- package/dist/model/config.js +3 -31
- package/dist/model/config.js.map +1 -1
- package/dist/model/env.js +2 -10
- package/dist/model/env.js.map +1 -1
- package/dist/model/model-gateway.d.ts +0 -6
- package/dist/model/model-gateway.js.map +1 -1
- package/dist/model/model-metadata.json +0 -40
- package/dist/model/openai-adapter.d.ts +2 -1
- package/dist/model/openai-adapter.js +19 -0
- package/dist/model/openai-adapter.js.map +1 -1
- package/dist/model/openai-chat-mapper.d.ts +0 -1
- package/dist/model/openai-chat-mapper.js +13 -9
- package/dist/model/openai-chat-mapper.js.map +1 -1
- package/dist/model/openai-mappers.d.ts +4 -4
- package/dist/model/openai-mappers.js +23 -17
- package/dist/model/openai-mappers.js.map +1 -1
- package/dist/model/openai-responses-mapper.js +76 -35
- package/dist/model/openai-responses-mapper.js.map +1 -1
- package/dist/model/provider-factory.js +1 -22
- package/dist/model/provider-factory.js.map +1 -1
- package/dist/model/smoke-responses-mapper.js +82 -3
- package/dist/model/smoke-responses-mapper.js.map +1 -1
- package/dist/repl/commands.js +5 -2
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/index.js +85 -129
- package/dist/repl/index.js.map +1 -1
- package/dist/repl/run-command.d.ts +37 -0
- package/dist/repl/run-command.js +167 -0
- package/dist/repl/run-command.js.map +1 -0
- package/dist/repl/smoke-run-command.js +43 -0
- package/dist/repl/smoke-run-command.js.map +1 -0
- package/dist/secrets/smoke-secrets.js +5 -4
- package/dist/secrets/smoke-secrets.js.map +1 -1
- package/dist/session/session-export.d.ts +0 -1
- package/dist/session/session-export.js +0 -1
- package/dist/session/session-export.js.map +1 -1
- package/dist/session/smoke-session.js +6 -2
- package/dist/session/smoke-session.js.map +1 -1
- package/dist/tools/builtins/exec-process-manager.d.ts +78 -0
- package/dist/tools/builtins/exec-process-manager.js +394 -0
- package/dist/tools/builtins/exec-process-manager.js.map +1 -0
- package/dist/tools/builtins/exec-pty-host.d.ts +1 -0
- package/dist/tools/builtins/exec-pty-host.js +70 -0
- package/dist/tools/builtins/exec-pty-host.js.map +1 -0
- package/dist/tools/builtins/exec-tool.d.ts +16 -27
- package/dist/tools/builtins/exec-tool.js +153 -324
- package/dist/tools/builtins/exec-tool.js.map +1 -1
- package/dist/tools/builtins/image-generation-tool.d.ts +0 -4
- package/dist/tools/builtins/image-generation-tool.js +59 -185
- package/dist/tools/builtins/image-generation-tool.js.map +1 -1
- package/dist/tools/smoke-exec-process.d.ts +1 -0
- package/dist/tools/smoke-exec-process.js +42 -0
- package/dist/tools/smoke-exec-process.js.map +1 -0
- package/dist/tools/smoke-tool-system.js +28 -36
- package/dist/tools/smoke-tool-system.js.map +1 -1
- package/dist/types/events.d.ts +8 -0
- package/dist/types/messages.d.ts +5 -0
- package/dist/types/messages.js +14 -0
- package/dist/types/messages.js.map +1 -1
- package/dist/web/html.js +76 -29
- package/dist/web/html.js.map +1 -1
- package/dist/web/index.d.ts +94 -37
- package/dist/web/index.js +345 -117
- package/dist/web/index.js.map +1 -1
- package/dist/web/plan-payload.d.ts +14 -0
- package/dist/web/plan-payload.js +27 -0
- package/dist/web/plan-payload.js.map +1 -0
- package/dist/web/runtime-context-protocol.d.ts +46 -0
- package/dist/web/runtime-context-protocol.js +72 -0
- package/dist/web/runtime-context-protocol.js.map +1 -0
- package/dist/web/smoke-plan-payload.d.ts +1 -0
- package/dist/web/smoke-plan-payload.js +30 -0
- package/dist/web/smoke-plan-payload.js.map +1 -0
- package/dist/web/smoke-runtime-context-protocol.d.ts +1 -0
- package/dist/web/smoke-runtime-context-protocol.js +31 -0
- package/dist/web/smoke-runtime-context-protocol.js.map +1 -0
- package/dist/web/smoke-web-queue.d.ts +1 -0
- package/dist/web/smoke-web-queue.js +7 -0
- package/dist/web/smoke-web-queue.js.map +1 -0
- package/package.json +7 -2
- package/scripts/verify-ripgrep.cjs +8 -8
- package/dist/model/anthropic-adapter.d.ts +0 -30
- package/dist/model/anthropic-adapter.js +0 -106
- package/dist/model/anthropic-adapter.js.map +0 -1
- package/dist/model/anthropic-mapper.d.ts +0 -13
- package/dist/model/anthropic-mapper.js +0 -508
- package/dist/model/anthropic-mapper.js.map +0 -1
- package/dist/model/smoke-anthropic-mapper.js +0 -89
- package/dist/model/smoke-anthropic-mapper.js.map +0 -1
- /package/dist/{model/smoke-anthropic-mapper.d.ts → repl/smoke-run-command.d.ts} +0 -0
package/dist/web/index.js
CHANGED
|
@@ -15,7 +15,8 @@ import { CommunicationLogger, LoggingModelGateway } from "../model/communication
|
|
|
15
15
|
import { createModelGatewayFromConfig, createModelGatewayFromProcessEnv } from "../model/provider-factory.js";
|
|
16
16
|
import { ToolRegistry } from "../tools/registry.js";
|
|
17
17
|
import { editTool, writeTool } from "../tools/builtins/edit-tool.js";
|
|
18
|
-
import { createExecTool } from "../tools/builtins/exec-tool.js";
|
|
18
|
+
import { createExecTool, createWriteStdinTool } from "../tools/builtins/exec-tool.js";
|
|
19
|
+
import { ExecProcessManager } from "../tools/builtins/exec-process-manager.js";
|
|
19
20
|
import { listDirectoryTool, readFileTool } from "../tools/builtins/filesystem-tools.js";
|
|
20
21
|
import { grepTool } from "../tools/builtins/grep-tool.js";
|
|
21
22
|
import { searchTool } from "../tools/builtins/search-tool.js";
|
|
@@ -27,9 +28,12 @@ import { createTaskTools } from "../tasks/task-tools.js";
|
|
|
27
28
|
import { TaskStore } from "../tasks/task-store.js";
|
|
28
29
|
import { parseReplCommand, helpText, replCommandDefinitions } from "../repl/commands.js";
|
|
29
30
|
import { writeSessionMarkdownExport } from "../session/session-export.js";
|
|
31
|
+
import { DefaultContextManager } from "../context/context-manager.js";
|
|
30
32
|
import { WEB_HTML } from "./html.js";
|
|
31
33
|
import { openDirectory } from "../open-directory.js";
|
|
32
34
|
import { resolveImageBlockDataSync } from "../core/image-storage.js";
|
|
35
|
+
import { isWebPlanPayload, serializeWebPlanPayload, webPlanBodyTitle } from "./plan-payload.js";
|
|
36
|
+
import { createWebRuntimeContextPayload } from "./runtime-context-protocol.js";
|
|
33
37
|
const require = createRequire(import.meta.url);
|
|
34
38
|
const markedPackageDir = path.dirname(require.resolve("marked/package.json"));
|
|
35
39
|
const highlightPackageDir = path.dirname(require.resolve("@highlightjs/cdn-assets/package.json"));
|
|
@@ -80,6 +84,11 @@ function sumUsageTokens(left, right) {
|
|
|
80
84
|
return undefined;
|
|
81
85
|
return (left ?? 0) + (right ?? 0);
|
|
82
86
|
}
|
|
87
|
+
// UI actions report structured results to the client. They must not append UiLine
|
|
88
|
+
// entries: the transcript is reserved for conversation, tool, and user-command events.
|
|
89
|
+
function actionFailure(errorCode, error) {
|
|
90
|
+
return { ok: false, errorCode, error };
|
|
91
|
+
}
|
|
83
92
|
const DEFAULT_WEB_RUNTIME_KEY = "__default__";
|
|
84
93
|
export async function runWebServer(argv = process.argv.slice(2), runtimeOptions = {}) {
|
|
85
94
|
const options = parseWebArgs(argv);
|
|
@@ -114,10 +123,15 @@ export async function createWebRuntime(options = {}) {
|
|
|
114
123
|
const communicationLogger = new CommunicationLogger();
|
|
115
124
|
const modelGateway = new LoggingModelGateway(createModelGatewayFromProcessEnv(process.env), communicationLogger);
|
|
116
125
|
const taskStore = new TaskStore();
|
|
126
|
+
const pluginCatalog = normalizeWebRuntimePlugins(options.externalPlugins);
|
|
127
|
+
const pluginOverrides = normalizePluginOverrides(options.sessionPluginOverrides, pluginCatalog);
|
|
128
|
+
const activePlugins = activeWebRuntimePlugins(pluginCatalog, pluginOverrides);
|
|
117
129
|
const tools = new ToolRegistry();
|
|
130
|
+
const execProcessManager = new ExecProcessManager();
|
|
118
131
|
tools.register(editTool);
|
|
119
132
|
tools.register(writeTool);
|
|
120
|
-
tools.register(createExecTool({
|
|
133
|
+
tools.register(createExecTool({ processManager: execProcessManager }));
|
|
134
|
+
tools.register(createWriteStdinTool(execProcessManager));
|
|
121
135
|
tools.register(listDirectoryTool);
|
|
122
136
|
tools.register(readFileTool);
|
|
123
137
|
tools.register(grepTool);
|
|
@@ -128,13 +142,16 @@ export async function createWebRuntime(options = {}) {
|
|
|
128
142
|
tools.register(planTool);
|
|
129
143
|
for (const tool of options.externalTools ?? [])
|
|
130
144
|
tools.register(tool);
|
|
145
|
+
for (const plugin of activePlugins)
|
|
146
|
+
for (const tool of plugin.tools)
|
|
147
|
+
tools.register(tool);
|
|
131
148
|
const agentRuntime = { modelGateway, tools, taskStore };
|
|
132
149
|
tools.register(createAgentTool(agentRuntime));
|
|
133
150
|
const resumeHandler = async (taskId, directive) => {
|
|
134
151
|
const dummyContext = {
|
|
135
152
|
agentId: "main",
|
|
136
153
|
tools,
|
|
137
|
-
appState: new InMemoryAppState("main"),
|
|
154
|
+
appState: new InMemoryAppState("main", options.cwd),
|
|
138
155
|
emit: () => undefined,
|
|
139
156
|
};
|
|
140
157
|
return resumeAgentTask(taskId, directive, agentRuntime, taskStore, dummyContext);
|
|
@@ -144,15 +161,21 @@ export async function createWebRuntime(options = {}) {
|
|
|
144
161
|
const appPromptStore = new InMemoryAppPromptStore();
|
|
145
162
|
const engine = new QueryEngine({
|
|
146
163
|
agentId: options.agentId ?? "main",
|
|
164
|
+
cwd: options.cwd,
|
|
147
165
|
model: modelConfig?.model,
|
|
148
|
-
fallbackModel: modelConfig?.fallbackModel,
|
|
149
166
|
reasoning: modelConfig?.defaultReasoning,
|
|
150
167
|
queryOrigin: "web",
|
|
151
168
|
modelGateway,
|
|
152
169
|
tools,
|
|
170
|
+
contextManagerFactory: (cwd) => new DefaultContextManager({ cwd }),
|
|
171
|
+
additionalPromptSections: [
|
|
172
|
+
...(options.externalPromptSections ?? []),
|
|
173
|
+
...activePlugins.flatMap((plugin) => plugin.promptSections ?? []),
|
|
174
|
+
],
|
|
153
175
|
appPromptStore,
|
|
154
176
|
taskNotificationSource: createTaskNotificationSource(taskStore),
|
|
155
177
|
commands: replCommandDefinitions.map((command) => command.usage),
|
|
178
|
+
plugins: [...(options.plugins ?? []), ...activePlugins.map((plugin) => plugin.id)],
|
|
156
179
|
session: {
|
|
157
180
|
enabled: process.env.AGENT_SESSION_TRANSCRIPT !== "0",
|
|
158
181
|
sessionId: options.sessionId ?? process.env.AGENT_SESSION_ID,
|
|
@@ -171,13 +194,45 @@ export async function createWebRuntime(options = {}) {
|
|
|
171
194
|
agentRuntime,
|
|
172
195
|
usage: new SessionUsageTracker(),
|
|
173
196
|
taskStore,
|
|
197
|
+
execProcessManager,
|
|
174
198
|
tools,
|
|
175
199
|
initialMetrics,
|
|
176
200
|
defaultReasoning: modelConfig?.defaultReasoning,
|
|
177
201
|
envPath: process.env.NEO_ENV_FILE?.trim() ? path.resolve(process.env.NEO_ENV_FILE.trim()) : envLoad.userDotEnvPath,
|
|
178
202
|
envNotice: envLoad.createdUserDotEnv ? formatCreatedEnvNotice(envLoad.userDotEnvPath) : undefined,
|
|
203
|
+
pluginSupport: pluginCatalog.length ? {
|
|
204
|
+
catalog: pluginCatalog,
|
|
205
|
+
overrides: pluginOverrides,
|
|
206
|
+
basePromptSections: [...(options.externalPromptSections ?? [])],
|
|
207
|
+
basePluginIds: [...(options.plugins ?? [])],
|
|
208
|
+
persist: options.persistSessionPluginOverrides,
|
|
209
|
+
resolve: options.resolveSessionPluginOverrides,
|
|
210
|
+
} : undefined,
|
|
179
211
|
};
|
|
180
212
|
}
|
|
213
|
+
function normalizeWebRuntimePlugins(value) {
|
|
214
|
+
if (!Array.isArray(value))
|
|
215
|
+
return [];
|
|
216
|
+
const plugins = value.map((plugin) => ({
|
|
217
|
+
...plugin,
|
|
218
|
+
id: plugin.id.trim(),
|
|
219
|
+
name: plugin.name.trim() || plugin.id.trim(),
|
|
220
|
+
version: plugin.version.trim() || "0.0.0",
|
|
221
|
+
tools: [...plugin.tools],
|
|
222
|
+
promptSections: [...(plugin.promptSections ?? [])],
|
|
223
|
+
})).filter((plugin) => plugin.id);
|
|
224
|
+
plugins.sort((left, right) => left.id.localeCompare(right.id));
|
|
225
|
+
if (new Set(plugins.map((plugin) => plugin.id)).size !== plugins.length)
|
|
226
|
+
throw new Error("duplicate web runtime plugin id");
|
|
227
|
+
return plugins;
|
|
228
|
+
}
|
|
229
|
+
function normalizePluginOverrides(value, catalog) {
|
|
230
|
+
const available = new Set(catalog.map((plugin) => plugin.id));
|
|
231
|
+
return Object.fromEntries(Object.entries(value ?? {}).filter(([id, enabled]) => available.has(id) && typeof enabled === "boolean"));
|
|
232
|
+
}
|
|
233
|
+
function activeWebRuntimePlugins(catalog, overrides) {
|
|
234
|
+
return catalog.filter((plugin) => plugin.globallyEnabled && overrides[plugin.id] !== false);
|
|
235
|
+
}
|
|
181
236
|
function createTaskNotificationSource(taskStore) {
|
|
182
237
|
return {
|
|
183
238
|
collectUnnotifiedCompletions() {
|
|
@@ -200,7 +255,7 @@ function syncImageGenerationTool(runtime, provider) {
|
|
|
200
255
|
runtime.tools.register(createOpenAIImageGenerationTool());
|
|
201
256
|
}
|
|
202
257
|
function formatCreatedEnvNotice(dotEnvPath) {
|
|
203
|
-
return `Created default config file: ${dotEnvPath}\nSet
|
|
258
|
+
return `Created default config file: ${dotEnvPath}\nSet OPENAI_API_KEY, then restart neo.`;
|
|
204
259
|
}
|
|
205
260
|
function parseResumeFlag(value) {
|
|
206
261
|
if (!value)
|
|
@@ -268,6 +323,9 @@ export class WebRepl {
|
|
|
268
323
|
foregroundRun;
|
|
269
324
|
foregroundRunToken = 0;
|
|
270
325
|
pendingUserImageEchoLabels;
|
|
326
|
+
pendingUserImageEchoMessageId;
|
|
327
|
+
runtimeContextRevision = 0;
|
|
328
|
+
runtimeContextPublishQueue = Promise.resolve();
|
|
271
329
|
backgroundSessionRuns = new Map();
|
|
272
330
|
suppressReattachedStreaming = new Set();
|
|
273
331
|
backgroundTaskCount;
|
|
@@ -275,15 +333,19 @@ export class WebRepl {
|
|
|
275
333
|
this.runtime = runtime;
|
|
276
334
|
this.lines = initialLines(runtime, { current: 0 });
|
|
277
335
|
this.status = initialStatus(runtime);
|
|
278
|
-
this.backgroundTaskCount = runtime.taskStore.activeCount();
|
|
336
|
+
this.backgroundTaskCount = runtime.taskStore.activeCount() + runtime.execProcessManager.activeCount();
|
|
279
337
|
runtime.taskStore.subscribe(() => {
|
|
280
|
-
this.backgroundTaskCount = runtime.taskStore.activeCount();
|
|
338
|
+
this.backgroundTaskCount = runtime.taskStore.activeCount() + runtime.execProcessManager.activeCount();
|
|
339
|
+
this.broadcastSync();
|
|
340
|
+
});
|
|
341
|
+
runtime.execProcessManager.subscribe(() => {
|
|
342
|
+
this.backgroundTaskCount = runtime.taskStore.activeCount() + runtime.execProcessManager.activeCount();
|
|
281
343
|
this.broadcastSync();
|
|
282
344
|
});
|
|
283
345
|
runtime.engine.onSessionTitleChange(() => this.broadcastSync());
|
|
284
346
|
}
|
|
285
347
|
subscribe(res) {
|
|
286
|
-
const subscriber = { response: res, blocked: false, needsSync: false };
|
|
348
|
+
const subscriber = { response: res, blocked: false, needsSync: false, needsRuntimeContext: false };
|
|
287
349
|
this.subscribers.add(subscriber);
|
|
288
350
|
res.writeHead(200, {
|
|
289
351
|
"Content-Type": "text/event-stream; charset=utf-8",
|
|
@@ -292,6 +354,7 @@ export class WebRepl {
|
|
|
292
354
|
"X-Accel-Buffering": "no",
|
|
293
355
|
});
|
|
294
356
|
this.send(subscriber, "sync", this.snapshot(true));
|
|
357
|
+
this.publishRuntimeContext([subscriber]);
|
|
295
358
|
reqKeepAlive(res);
|
|
296
359
|
res.on("close", () => this.subscribers.delete(subscriber));
|
|
297
360
|
}
|
|
@@ -312,6 +375,89 @@ export class WebRepl {
|
|
|
312
375
|
interactive: includeCatalog ? webInteractiveCatalog(this.runtime) : undefined,
|
|
313
376
|
};
|
|
314
377
|
}
|
|
378
|
+
async runtimeContext() {
|
|
379
|
+
return this.buildRuntimeContext(this.runtimeContextRevision);
|
|
380
|
+
}
|
|
381
|
+
sessionPlugins() {
|
|
382
|
+
const support = this.runtime.pluginSupport;
|
|
383
|
+
const sessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
384
|
+
if (!support)
|
|
385
|
+
return { sessionId, busy: this.busy, items: [] };
|
|
386
|
+
return {
|
|
387
|
+
sessionId,
|
|
388
|
+
busy: this.busy,
|
|
389
|
+
items: support.catalog.map((plugin) => {
|
|
390
|
+
const override = support.overrides[plugin.id];
|
|
391
|
+
return {
|
|
392
|
+
id: plugin.id,
|
|
393
|
+
name: plugin.name,
|
|
394
|
+
version: plugin.version,
|
|
395
|
+
globallyEnabled: plugin.globallyEnabled,
|
|
396
|
+
mode: override === undefined ? "inherit" : override ? "enabled" : "disabled",
|
|
397
|
+
effectiveEnabled: plugin.globallyEnabled && override !== false,
|
|
398
|
+
tools: plugin.tools.map((tool) => tool.name),
|
|
399
|
+
promptSections: plugin.promptSections?.length ?? 0,
|
|
400
|
+
};
|
|
401
|
+
}),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
async setSessionPlugins(value) {
|
|
405
|
+
if (this.busy)
|
|
406
|
+
return actionFailure("PLUGIN_UPDATE_BLOCKED", "cannot change session plugins while a response is running");
|
|
407
|
+
if (!isRecord(value))
|
|
408
|
+
return actionFailure("INVALID_REQUEST", "plugin overrides must be an object");
|
|
409
|
+
const support = this.runtime.pluginSupport;
|
|
410
|
+
if (!support)
|
|
411
|
+
return actionFailure("PLUGIN_NOT_CONFIGURED", "web plugins are not configured");
|
|
412
|
+
const available = new Set(support.catalog.map((plugin) => plugin.id));
|
|
413
|
+
const overrides = {};
|
|
414
|
+
for (const [id, mode] of Object.entries(value)) {
|
|
415
|
+
if (!available.has(id))
|
|
416
|
+
return actionFailure("PLUGIN_INVALID", `unknown web plugin: ${id}`);
|
|
417
|
+
if (mode === "enabled" || mode === true)
|
|
418
|
+
overrides[id] = true;
|
|
419
|
+
else if (mode === "disabled" || mode === false)
|
|
420
|
+
overrides[id] = false;
|
|
421
|
+
else if (mode !== "inherit" && mode !== null && mode !== undefined)
|
|
422
|
+
return actionFailure("PLUGIN_INVALID", `invalid plugin mode: ${id}`);
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
await this.applySessionPluginOverrides(overrides, true);
|
|
426
|
+
return { ok: true, state: this.sessionPlugins() };
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
return actionFailure("PLUGIN_UPDATE_FAILED", error instanceof Error ? error.message : String(error));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async loadSessionPlugins(sessionId) {
|
|
433
|
+
const support = this.runtime.pluginSupport;
|
|
434
|
+
if (!support?.resolve || !sessionId)
|
|
435
|
+
return;
|
|
436
|
+
await this.applySessionPluginOverrides(await support.resolve(sessionId), false);
|
|
437
|
+
}
|
|
438
|
+
async applySessionPluginOverrides(overrides, persist) {
|
|
439
|
+
const support = this.runtime.pluginSupport;
|
|
440
|
+
if (!support)
|
|
441
|
+
return;
|
|
442
|
+
const normalized = normalizePluginOverrides(overrides, support.catalog);
|
|
443
|
+
for (const plugin of support.catalog)
|
|
444
|
+
for (const tool of plugin.tools)
|
|
445
|
+
this.runtime.tools.unregister(tool.name);
|
|
446
|
+
const activePlugins = activeWebRuntimePlugins(support.catalog, normalized);
|
|
447
|
+
for (const plugin of activePlugins)
|
|
448
|
+
for (const tool of plugin.tools)
|
|
449
|
+
this.runtime.tools.register(tool);
|
|
450
|
+
support.overrides = normalized;
|
|
451
|
+
this.runtime.engine.setRuntimePlugins([...support.basePluginIds, ...activePlugins.map((plugin) => plugin.id)], [...support.basePromptSections, ...activePlugins.flatMap((plugin) => plugin.promptSections ?? [])]);
|
|
452
|
+
const sessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
453
|
+
if (persist && sessionId && support.persist)
|
|
454
|
+
await support.persist(sessionId, normalized);
|
|
455
|
+
const metrics = await this.runtime.engine.contextMetrics();
|
|
456
|
+
this.runtime.initialMetrics = metrics;
|
|
457
|
+
this.setStatus({ ...this.status, metrics, activityTick: this.status.activityTick + 1 });
|
|
458
|
+
this.broadcastSync();
|
|
459
|
+
this.publishRuntimeContext();
|
|
460
|
+
}
|
|
315
461
|
async submit(text, attachments = []) {
|
|
316
462
|
const trimmed = text.trim();
|
|
317
463
|
if (!trimmed && attachments.length === 0)
|
|
@@ -324,14 +470,36 @@ export class WebRepl {
|
|
|
324
470
|
await this.detachRunningForeground("session browser");
|
|
325
471
|
}
|
|
326
472
|
else if (this.busy) {
|
|
327
|
-
this.
|
|
328
|
-
this.queuedAttachments = attachments;
|
|
473
|
+
this.enqueueInput(text, attachments);
|
|
329
474
|
this.broadcastSync();
|
|
330
475
|
return { ok: true };
|
|
331
476
|
}
|
|
332
477
|
this.startRun(text, attachments);
|
|
333
478
|
return { ok: true };
|
|
334
479
|
}
|
|
480
|
+
submitImmediately(text, attachments = []) {
|
|
481
|
+
if (!this.busy) {
|
|
482
|
+
this.startRun(text, attachments);
|
|
483
|
+
return { ok: true, interrupted: false };
|
|
484
|
+
}
|
|
485
|
+
this.enqueueInput(text, attachments);
|
|
486
|
+
return this.sendQueuedNow();
|
|
487
|
+
}
|
|
488
|
+
sendQueuedNow() {
|
|
489
|
+
const queuedText = this.queuedInput;
|
|
490
|
+
const queuedAttachments = this.queuedAttachments ?? [];
|
|
491
|
+
if (queuedText === undefined)
|
|
492
|
+
return { ok: true, interrupted: false };
|
|
493
|
+
this.queuedInput = undefined;
|
|
494
|
+
this.queuedAttachments = undefined;
|
|
495
|
+
const interrupted = this.stopForegroundRun("Interrupted to send queued web input");
|
|
496
|
+
this.startRun(queuedText, queuedAttachments);
|
|
497
|
+
return { ok: true, interrupted };
|
|
498
|
+
}
|
|
499
|
+
enqueueInput(text, attachments) {
|
|
500
|
+
this.queuedInput = mergeWebQueuedInput(this.queuedInput, text);
|
|
501
|
+
this.queuedAttachments = [...(this.queuedAttachments ?? []), ...attachments].slice(0, 20);
|
|
502
|
+
}
|
|
335
503
|
startRun(text, attachments = []) {
|
|
336
504
|
const run = this.handleCommandOrPrompt(text, attachments).catch((error) => {
|
|
337
505
|
this.append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
@@ -351,7 +519,7 @@ export class WebRepl {
|
|
|
351
519
|
}
|
|
352
520
|
async resumeSession(sessionId) {
|
|
353
521
|
if (!sessionId)
|
|
354
|
-
return
|
|
522
|
+
return actionFailure("INVALID_REQUEST", "sessionId is required");
|
|
355
523
|
try {
|
|
356
524
|
const running = this.backgroundSessionRuns.get(sessionId);
|
|
357
525
|
if (running) {
|
|
@@ -364,13 +532,13 @@ export class WebRepl {
|
|
|
364
532
|
const snapshot = this.runtime.engine.snapshot().session;
|
|
365
533
|
if (!snapshot)
|
|
366
534
|
throw new Error("session transcripts are disabled");
|
|
367
|
-
await this.
|
|
535
|
+
await this.loadSessionPlugins(snapshot.sessionId);
|
|
536
|
+
await this.refreshSessionView();
|
|
368
537
|
return { ok: true };
|
|
369
538
|
}
|
|
370
539
|
catch (error) {
|
|
371
540
|
const message = error instanceof Error ? error.message : String(error);
|
|
372
|
-
|
|
373
|
-
return { ok: false, error: message };
|
|
541
|
+
return actionFailure("SESSION_RESUME_FAILED", message);
|
|
374
542
|
}
|
|
375
543
|
}
|
|
376
544
|
async newSession() {
|
|
@@ -381,16 +549,16 @@ export class WebRepl {
|
|
|
381
549
|
const snapshot = this.runtime.engine.snapshot().session;
|
|
382
550
|
if (!snapshot)
|
|
383
551
|
throw new Error("session transcripts are disabled");
|
|
384
|
-
await this.
|
|
552
|
+
await this.loadSessionPlugins(snapshot.sessionId);
|
|
553
|
+
await this.refreshSessionView();
|
|
385
554
|
return { ok: true };
|
|
386
555
|
}
|
|
387
556
|
catch (error) {
|
|
388
557
|
const message = error instanceof Error ? error.message : String(error);
|
|
389
|
-
|
|
390
|
-
return { ok: false, error: message };
|
|
558
|
+
return actionFailure("SESSION_CREATE_FAILED", message);
|
|
391
559
|
}
|
|
392
560
|
}
|
|
393
|
-
async refreshSessionView(
|
|
561
|
+
async refreshSessionView() {
|
|
394
562
|
const metrics = await this.runtime.engine.contextMetrics();
|
|
395
563
|
this.runtime.usage.reset();
|
|
396
564
|
this.status = initialStatus(this.runtime, metrics);
|
|
@@ -401,24 +569,27 @@ export class WebRepl {
|
|
|
401
569
|
this.thinkingLineId = undefined;
|
|
402
570
|
this.finalizedThinkingLineId = undefined;
|
|
403
571
|
this.liveToolLineIds.clear();
|
|
404
|
-
if (line)
|
|
405
|
-
this.append(line);
|
|
406
572
|
this.broadcastSync();
|
|
573
|
+
this.publishRuntimeContext();
|
|
407
574
|
}
|
|
408
575
|
async deleteSession(sessionId) {
|
|
409
576
|
if (!sessionId)
|
|
410
|
-
return
|
|
577
|
+
return actionFailure("INVALID_REQUEST", "sessionId is required");
|
|
578
|
+
if (this.runtime.engine.snapshot().session?.sessionId === sessionId) {
|
|
579
|
+
return actionFailure("SESSION_ACTIVE", "cannot delete the active session");
|
|
580
|
+
}
|
|
581
|
+
if (this.backgroundSessionRuns.has(sessionId)) {
|
|
582
|
+
return actionFailure("SESSION_RUNNING", "cannot delete a running session");
|
|
583
|
+
}
|
|
411
584
|
try {
|
|
412
585
|
const deleted = await this.runtime.engine.deleteSession(sessionId);
|
|
413
586
|
if (!deleted)
|
|
414
|
-
return
|
|
415
|
-
this.append(systemLine(`deleted session ${sessionId}`));
|
|
587
|
+
return actionFailure("SESSION_NOT_FOUND", `session not found: ${sessionId}`);
|
|
416
588
|
return { ok: true };
|
|
417
589
|
}
|
|
418
590
|
catch (error) {
|
|
419
591
|
const message = error instanceof Error ? error.message : String(error);
|
|
420
|
-
|
|
421
|
-
return { ok: false, error: message };
|
|
592
|
+
return actionFailure("SESSION_DELETE_FAILED", message);
|
|
422
593
|
}
|
|
423
594
|
}
|
|
424
595
|
appPrompt() {
|
|
@@ -446,11 +617,12 @@ export class WebRepl {
|
|
|
446
617
|
});
|
|
447
618
|
}
|
|
448
619
|
this.broadcastSync();
|
|
620
|
+
this.publishRuntimeContext();
|
|
449
621
|
return { ok: true, appPrompt };
|
|
450
622
|
}
|
|
451
623
|
catch (error) {
|
|
452
624
|
const message = error instanceof Error ? error.message : String(error);
|
|
453
|
-
return
|
|
625
|
+
return actionFailure("PROMPT_UPDATE_FAILED", message);
|
|
454
626
|
}
|
|
455
627
|
}
|
|
456
628
|
imagePayload(messageId, blockIndex) {
|
|
@@ -480,7 +652,7 @@ export class WebRepl {
|
|
|
480
652
|
return { ok: true, fastMode };
|
|
481
653
|
}
|
|
482
654
|
catch (error) {
|
|
483
|
-
return
|
|
655
|
+
return actionFailure("FAST_MODE_UPDATE_FAILED", error instanceof Error ? error.message : String(error));
|
|
484
656
|
}
|
|
485
657
|
}
|
|
486
658
|
loginForm(providerValue) {
|
|
@@ -490,11 +662,11 @@ export class WebRepl {
|
|
|
490
662
|
async saveLogin(providerValue, values) {
|
|
491
663
|
const provider = parseLoginProvider(providerValue);
|
|
492
664
|
if (!provider)
|
|
493
|
-
return
|
|
665
|
+
return actionFailure("LOGIN_INVALID", "provider must be openai");
|
|
494
666
|
const payload = { ...createLoginFormPayload(this.runtime.envPath, provider), provider, values };
|
|
495
667
|
const validationError = validateLoginFormPayload(payload);
|
|
496
668
|
if (validationError)
|
|
497
|
-
return
|
|
669
|
+
return actionFailure("LOGIN_INVALID", validationError);
|
|
498
670
|
try {
|
|
499
671
|
await saveLoginPayloadToEnv(payload);
|
|
500
672
|
applyLoginPayloadToProcessEnv(payload);
|
|
@@ -504,18 +676,17 @@ export class WebRepl {
|
|
|
504
676
|
const innerGateway = createModelGatewayFromConfig(config);
|
|
505
677
|
this.runtime.modelGateway.setInner(innerGateway);
|
|
506
678
|
this.runtime.agentRuntime.modelGateway = this.runtime.modelGateway;
|
|
507
|
-
this.runtime.engine.setModelProvider({ modelGateway: this.runtime.modelGateway, model: config.model,
|
|
679
|
+
this.runtime.engine.setModelProvider({ modelGateway: this.runtime.modelGateway, model: config.model, reasoning: config.defaultReasoning });
|
|
508
680
|
syncImageGenerationTool(this.runtime, config.provider);
|
|
509
681
|
this.runtime.defaultReasoning = config.defaultReasoning;
|
|
510
682
|
const metrics = await this.runtime.engine.contextMetrics();
|
|
511
683
|
this.setStatus({ ...this.status, metrics, activityTick: this.status.activityTick + 1 });
|
|
512
|
-
this.
|
|
684
|
+
this.publishRuntimeContext();
|
|
513
685
|
return { ok: true };
|
|
514
686
|
}
|
|
515
687
|
catch (error) {
|
|
516
688
|
const message = `Login save failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
517
|
-
|
|
518
|
-
return { ok: false, error: message };
|
|
689
|
+
return actionFailure("LOGIN_SAVE_FAILED", message);
|
|
519
690
|
}
|
|
520
691
|
}
|
|
521
692
|
interrupt() {
|
|
@@ -578,6 +749,8 @@ export class WebRepl {
|
|
|
578
749
|
this.interruptArmed = false;
|
|
579
750
|
this.queuedInput = undefined;
|
|
580
751
|
this.queuedAttachments = undefined;
|
|
752
|
+
this.pendingUserImageEchoLabels = undefined;
|
|
753
|
+
this.pendingUserImageEchoMessageId = undefined;
|
|
581
754
|
this.finalizeForegroundView();
|
|
582
755
|
this.finalizeLiveToolLines();
|
|
583
756
|
this.busy = false;
|
|
@@ -624,9 +797,10 @@ export class WebRepl {
|
|
|
624
797
|
this.interruptArmed = false;
|
|
625
798
|
this.queuedInput = undefined;
|
|
626
799
|
this.queuedAttachments = undefined;
|
|
800
|
+
this.pendingUserImageEchoLabels = undefined;
|
|
801
|
+
this.pendingUserImageEchoMessageId = undefined;
|
|
627
802
|
this.busy = false;
|
|
628
803
|
this.status = { ...this.status, phase: "ready", detail: undefined };
|
|
629
|
-
this.append(systemLine(`Detached running ${sessionId} to background for ${reason}.`));
|
|
630
804
|
return true;
|
|
631
805
|
}
|
|
632
806
|
async reattachRunningSession(run) {
|
|
@@ -637,7 +811,7 @@ export class WebRepl {
|
|
|
637
811
|
this.interruptArmed = false;
|
|
638
812
|
this.foregroundRun = run.promise;
|
|
639
813
|
this.suppressReattachedStreaming.add(run.engine);
|
|
640
|
-
await this.refreshSessionView(
|
|
814
|
+
await this.refreshSessionView();
|
|
641
815
|
this.setBusy(true);
|
|
642
816
|
this.setStatus({ ...this.status, phase: "running", detail: "working" });
|
|
643
817
|
}
|
|
@@ -668,8 +842,10 @@ export class WebRepl {
|
|
|
668
842
|
handleEvent(event) {
|
|
669
843
|
this.reduce(event);
|
|
670
844
|
if (event.type === "message" && this.matchesPendingUserImageEcho(event.message)) {
|
|
845
|
+
const messageId = this.pendingUserImageEchoMessageId;
|
|
671
846
|
this.pendingUserImageEchoLabels = undefined;
|
|
672
|
-
|
|
847
|
+
this.pendingUserImageEchoMessageId = undefined;
|
|
848
|
+
renderMessageImages(event.message, (line) => this.append(line), messageId);
|
|
673
849
|
this.broadcastSync();
|
|
674
850
|
return;
|
|
675
851
|
}
|
|
@@ -798,6 +974,7 @@ export class WebRepl {
|
|
|
798
974
|
this.runtime.usage.reset();
|
|
799
975
|
this.status = await resetStatus(this.runtime);
|
|
800
976
|
this.append(systemLine("transcript reset"));
|
|
977
|
+
this.publishRuntimeContext();
|
|
801
978
|
return;
|
|
802
979
|
}
|
|
803
980
|
if (command.type === "state") {
|
|
@@ -852,6 +1029,7 @@ export class WebRepl {
|
|
|
852
1029
|
this.append(await handleModelCommand(command, this.runtime));
|
|
853
1030
|
const metrics = await this.runtime.engine.contextMetrics();
|
|
854
1031
|
this.setStatus({ ...this.status, phase: "ready", detail: undefined, metrics, activityTick: this.status.activityTick + 1 });
|
|
1032
|
+
this.publishRuntimeContext();
|
|
855
1033
|
}
|
|
856
1034
|
finally {
|
|
857
1035
|
this.setBusy(false);
|
|
@@ -879,11 +1057,13 @@ export class WebRepl {
|
|
|
879
1057
|
return;
|
|
880
1058
|
}
|
|
881
1059
|
const promptPayload = buildWebPromptPayload(command.text, attachments);
|
|
882
|
-
|
|
1060
|
+
const optimisticMessageId = `web-user-${this.lineId + 1}`;
|
|
1061
|
+
this.append({ kind: "user", text: promptPayload.displayText, messageId: optimisticMessageId });
|
|
883
1062
|
const optimisticImageLabels = (promptPayload.blocks ?? [])
|
|
884
1063
|
.filter((block) => block.type === "image" && typeof block.label === "string")
|
|
885
1064
|
.map((block) => block.label);
|
|
886
1065
|
this.pendingUserImageEchoLabels = optimisticImageLabels.length ? optimisticImageLabels : undefined;
|
|
1066
|
+
this.pendingUserImageEchoMessageId = optimisticImageLabels.length ? optimisticMessageId : undefined;
|
|
887
1067
|
const runToken = ++this.foregroundRunToken;
|
|
888
1068
|
const abortController = new AbortController();
|
|
889
1069
|
this.activeAbortController = abortController;
|
|
@@ -924,6 +1104,7 @@ export class WebRepl {
|
|
|
924
1104
|
this.interruptArmed = false;
|
|
925
1105
|
this.finalizeForegroundView();
|
|
926
1106
|
this.pendingUserImageEchoLabels = undefined;
|
|
1107
|
+
this.pendingUserImageEchoMessageId = undefined;
|
|
927
1108
|
const queuedText = this.queuedInput;
|
|
928
1109
|
const queuedAttach = this.queuedAttachments;
|
|
929
1110
|
this.queuedInput = undefined;
|
|
@@ -974,7 +1155,10 @@ export class WebRepl {
|
|
|
974
1155
|
}
|
|
975
1156
|
send(subscriber, event, data) {
|
|
976
1157
|
if (subscriber.blocked) {
|
|
977
|
-
|
|
1158
|
+
if (event === "runtime.context")
|
|
1159
|
+
subscriber.needsRuntimeContext = true;
|
|
1160
|
+
else
|
|
1161
|
+
subscriber.needsSync = true;
|
|
978
1162
|
return;
|
|
979
1163
|
}
|
|
980
1164
|
const eventId = ++this.eventSequence;
|
|
@@ -984,10 +1168,41 @@ export class WebRepl {
|
|
|
984
1168
|
subscriber.blocked = true;
|
|
985
1169
|
subscriber.response.once("drain", () => {
|
|
986
1170
|
subscriber.blocked = false;
|
|
987
|
-
if (
|
|
1171
|
+
if (subscriber.response.destroyed)
|
|
988
1172
|
return;
|
|
989
|
-
subscriber.needsSync
|
|
990
|
-
|
|
1173
|
+
if (subscriber.needsSync) {
|
|
1174
|
+
subscriber.needsSync = false;
|
|
1175
|
+
this.send(subscriber, "sync", this.snapshot(false));
|
|
1176
|
+
}
|
|
1177
|
+
if (subscriber.needsRuntimeContext) {
|
|
1178
|
+
subscriber.needsRuntimeContext = false;
|
|
1179
|
+
this.publishRuntimeContext([subscriber]);
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
async buildRuntimeContext(revision) {
|
|
1184
|
+
const snapshot = await this.runtime.engine.promptExportSnapshot();
|
|
1185
|
+
return createWebRuntimeContextPayload(snapshot, {
|
|
1186
|
+
revision,
|
|
1187
|
+
sessionId: this.runtime.engine.snapshot().session?.sessionId,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
publishRuntimeContext(targets) {
|
|
1191
|
+
this.runtimeContextPublishQueue = this.runtimeContextPublishQueue.then(async () => {
|
|
1192
|
+
const revision = ++this.runtimeContextRevision;
|
|
1193
|
+
const payload = await this.buildRuntimeContext(revision);
|
|
1194
|
+
const subscribers = targets ?? [...this.subscribers];
|
|
1195
|
+
for (const subscriber of subscribers) {
|
|
1196
|
+
if (!subscriber.response.destroyed && this.subscribers.has(subscriber))
|
|
1197
|
+
this.send(subscriber, "runtime.context", payload);
|
|
1198
|
+
}
|
|
1199
|
+
}).catch((error) => {
|
|
1200
|
+
const payload = { protocolVersion: 1, message: error instanceof Error ? error.message : String(error) };
|
|
1201
|
+
const subscribers = targets ?? [...this.subscribers];
|
|
1202
|
+
for (const subscriber of subscribers) {
|
|
1203
|
+
if (!subscriber.response.destroyed && this.subscribers.has(subscriber))
|
|
1204
|
+
this.send(subscriber, "runtime.context.error", payload);
|
|
1205
|
+
}
|
|
991
1206
|
});
|
|
992
1207
|
}
|
|
993
1208
|
broadcastDelta(operation) {
|
|
@@ -1075,6 +1290,14 @@ async function route(req, res, router) {
|
|
|
1075
1290
|
return repl.subscribe(res);
|
|
1076
1291
|
if (req.method === "GET" && url.pathname === "/api/state")
|
|
1077
1292
|
return sendJson(res, repl.snapshot(true));
|
|
1293
|
+
if (req.method === "GET" && url.pathname === "/api/runtime-context")
|
|
1294
|
+
return sendJson(res, await repl.runtimeContext());
|
|
1295
|
+
if (req.method === "GET" && url.pathname === "/api/session-plugins")
|
|
1296
|
+
return sendJson(res, repl.sessionPlugins());
|
|
1297
|
+
if (req.method === "POST" && url.pathname === "/api/session-plugins") {
|
|
1298
|
+
const body = await readJsonBody(req);
|
|
1299
|
+
return sendJson(res, await repl.setSessionPlugins(body.overrides));
|
|
1300
|
+
}
|
|
1078
1301
|
const imageMatch = /^\/api\/images\/([^/]+)\/(\d+)$/u.exec(url.pathname);
|
|
1079
1302
|
if (req.method === "GET" && imageMatch) {
|
|
1080
1303
|
const payload = repl.imagePayload(decodeURIComponent(imageMatch[1]), Number(imageMatch[2]));
|
|
@@ -1094,10 +1317,16 @@ async function route(req, res, router) {
|
|
|
1094
1317
|
const body = await readJsonBody(req);
|
|
1095
1318
|
return sendJson(res, await repl.submit(String(body.text ?? ""), sanitizeWebAttachments(body.attachments)));
|
|
1096
1319
|
}
|
|
1320
|
+
if (req.method === "POST" && url.pathname === "/api/submit-now") {
|
|
1321
|
+
const body = await readJsonBody(req);
|
|
1322
|
+
return sendJson(res, repl.submitImmediately(String(body.text ?? ""), sanitizeWebAttachments(body.attachments)));
|
|
1323
|
+
}
|
|
1097
1324
|
if (req.method === "POST" && url.pathname === "/api/interrupt")
|
|
1098
1325
|
return sendJson(res, repl.interrupt());
|
|
1099
1326
|
if (req.method === "POST" && url.pathname === "/api/queue/cancel")
|
|
1100
1327
|
return sendJson(res, repl.cancelQueue());
|
|
1328
|
+
if (req.method === "POST" && url.pathname === "/api/queue/send-now")
|
|
1329
|
+
return sendJson(res, repl.sendQueuedNow());
|
|
1101
1330
|
if (req.method === "GET" && url.pathname === "/api/sessions")
|
|
1102
1331
|
return sendJson(res, await repl.listSessions());
|
|
1103
1332
|
if (req.method === "POST" && url.pathname === "/api/sessions/resume") {
|
|
@@ -1119,7 +1348,7 @@ async function route(req, res, router) {
|
|
|
1119
1348
|
sendJson(res, { error: "not found" }, 404);
|
|
1120
1349
|
}
|
|
1121
1350
|
catch (error) {
|
|
1122
|
-
sendJson(res, { error: error instanceof Error ? error.message : String(error) }, 500);
|
|
1351
|
+
sendJson(res, { errorCode: "WEB_REQUEST_FAILED", error: error instanceof Error ? error.message : String(error) }, 500);
|
|
1123
1352
|
}
|
|
1124
1353
|
}
|
|
1125
1354
|
function webRuntimeScopeFromUrl(url) {
|
|
@@ -1164,10 +1393,22 @@ async function readJsonBody(req) {
|
|
|
1164
1393
|
function sanitizeWebAttachments(value) {
|
|
1165
1394
|
if (!Array.isArray(value))
|
|
1166
1395
|
return [];
|
|
1167
|
-
return value.
|
|
1396
|
+
return value.slice(0, 20).flatMap((item) => {
|
|
1168
1397
|
if (!isRecord(item))
|
|
1169
|
-
return
|
|
1170
|
-
|
|
1398
|
+
return [];
|
|
1399
|
+
if (item.kind === "image" && typeof item.label === "string" && /^\[img#\d+\]$/.test(item.label) && typeof item.mimeType === "string" && item.mimeType.startsWith("image/") && typeof item.data === "string") {
|
|
1400
|
+
return [{ kind: "image", label: item.label, mimeType: item.mimeType, data: item.data }];
|
|
1401
|
+
}
|
|
1402
|
+
if (item.kind === "file" && typeof item.name === "string" && typeof item.mimeType === "string" && typeof item.size === "number" && Number.isFinite(item.size) && typeof item.absolutePath === "string" && path.isAbsolute(item.absolutePath)) {
|
|
1403
|
+
return [{
|
|
1404
|
+
kind: "file",
|
|
1405
|
+
name: path.basename(item.name).slice(0, 180),
|
|
1406
|
+
mimeType: item.mimeType.slice(0, 160),
|
|
1407
|
+
size: Math.max(0, Math.round(item.size)),
|
|
1408
|
+
absolutePath: path.resolve(item.absolutePath),
|
|
1409
|
+
}];
|
|
1410
|
+
}
|
|
1411
|
+
return [];
|
|
1171
1412
|
});
|
|
1172
1413
|
}
|
|
1173
1414
|
function webCatalog(runtime) {
|
|
@@ -1201,7 +1442,7 @@ function restoredHistoryLines(runtime) {
|
|
|
1201
1442
|
return lines.length;
|
|
1202
1443
|
};
|
|
1203
1444
|
for (const message of runtime.engine.getHistoryMessages())
|
|
1204
|
-
renderMessage(message, append, undefined, { includeToolUseBlocks: true, includeThinkingBlocks:
|
|
1445
|
+
renderMessage(message, append, undefined, { includeToolUseBlocks: true, includeThinkingBlocks: true });
|
|
1205
1446
|
return lines;
|
|
1206
1447
|
}
|
|
1207
1448
|
function initialStatus(runtime, metrics = runtime.initialMetrics) {
|
|
@@ -1211,9 +1452,12 @@ async function resetStatus(runtime) {
|
|
|
1211
1452
|
return initialStatus(runtime, await runtime.engine.contextMetrics());
|
|
1212
1453
|
}
|
|
1213
1454
|
function buildWebPromptPayload(displayText, attachments) {
|
|
1214
|
-
const activeAttachments = attachments.filter((attachment) => displayText.includes(attachment.label));
|
|
1215
|
-
|
|
1216
|
-
|
|
1455
|
+
const activeAttachments = attachments.filter((attachment) => attachment.kind === "image" && displayText.includes(attachment.label));
|
|
1456
|
+
const fileManifest = buildWebFileAttachmentManifest(attachments.filter((attachment) => attachment.kind === "file"));
|
|
1457
|
+
if (activeAttachments.length === 0) {
|
|
1458
|
+
const text = [displayText.trim(), fileManifest].filter(Boolean).join("\n\n");
|
|
1459
|
+
return { text, displayText };
|
|
1460
|
+
}
|
|
1217
1461
|
const blocks = [];
|
|
1218
1462
|
let cursor = 0;
|
|
1219
1463
|
while (cursor < displayText.length) {
|
|
@@ -1226,8 +1470,27 @@ function buildWebPromptPayload(displayText, attachments) {
|
|
|
1226
1470
|
blocks.push({ type: "image", mimeType: next.attachment.mimeType, data: next.attachment.data, label: next.attachment.label });
|
|
1227
1471
|
cursor = next.index + next.attachment.label.length;
|
|
1228
1472
|
}
|
|
1473
|
+
pushTextBlock(blocks, fileManifest ? `\n\n${fileManifest}` : "");
|
|
1229
1474
|
const text = blocks.map((block) => block.type === "text" ? block.text : block.type === "image" ? block.label ?? "[image]" : "").join("");
|
|
1230
|
-
return { text, displayText
|
|
1475
|
+
return { text, displayText, blocks };
|
|
1476
|
+
}
|
|
1477
|
+
function buildWebFileAttachmentManifest(attachments) {
|
|
1478
|
+
if (attachments.length === 0)
|
|
1479
|
+
return "";
|
|
1480
|
+
return [
|
|
1481
|
+
"<<ATTACHMENT_MANIFEST>>",
|
|
1482
|
+
"Files attached by the user for this turn:",
|
|
1483
|
+
...attachments.map((attachment, index) => [
|
|
1484
|
+
`- file${index + 1}: ${attachment.name || `attachment-${index + 1}`}`,
|
|
1485
|
+
` path: ${attachment.absolutePath}`,
|
|
1486
|
+
` mimeType: ${attachment.mimeType || "application/octet-stream"}`,
|
|
1487
|
+
` size: ${attachment.size} bytes`,
|
|
1488
|
+
].join("\n")),
|
|
1489
|
+
"<</ATTACHMENT_MANIFEST>>",
|
|
1490
|
+
].join("\n");
|
|
1491
|
+
}
|
|
1492
|
+
export function mergeWebQueuedInput(current, next) {
|
|
1493
|
+
return [current?.trimEnd(), next.trim()].filter(Boolean).join("\n");
|
|
1231
1494
|
}
|
|
1232
1495
|
function nextWebAttachmentOccurrence(text, attachments, start) {
|
|
1233
1496
|
let best;
|
|
@@ -1320,7 +1583,7 @@ async function handleModelCommand(command, runtime) {
|
|
|
1320
1583
|
const innerGateway = createModelGatewayFromConfig(config);
|
|
1321
1584
|
runtime.modelGateway.setInner(innerGateway);
|
|
1322
1585
|
runtime.agentRuntime.modelGateway = runtime.modelGateway;
|
|
1323
|
-
runtime.engine.setModelProvider({ modelGateway: runtime.modelGateway, model: config.model,
|
|
1586
|
+
runtime.engine.setModelProvider({ modelGateway: runtime.modelGateway, model: config.model, reasoning: config.defaultReasoning });
|
|
1324
1587
|
syncImageGenerationTool(runtime, config.provider);
|
|
1325
1588
|
runtime.defaultReasoning = config.defaultReasoning;
|
|
1326
1589
|
}
|
|
@@ -1372,18 +1635,18 @@ async function persistModelCommandSettings(runtime, command, reasoningUpdate) {
|
|
|
1372
1635
|
return { providerChanged: targetProvider !== currentProvider };
|
|
1373
1636
|
}
|
|
1374
1637
|
function currentModelProvider() {
|
|
1375
|
-
return
|
|
1638
|
+
return "openai";
|
|
1376
1639
|
}
|
|
1377
1640
|
function parseLoginProvider(value) {
|
|
1378
|
-
if (value === "openai"
|
|
1641
|
+
if (value === "openai")
|
|
1379
1642
|
return value;
|
|
1380
1643
|
return undefined;
|
|
1381
1644
|
}
|
|
1382
|
-
const LOGIN_PROVIDERS = ["openai"
|
|
1645
|
+
const LOGIN_PROVIDERS = ["openai"];
|
|
1383
1646
|
const SHARED_LOGIN_FIELDS = [
|
|
1384
1647
|
{ key: "reasoningEffort", label: "Reasoning effort", envKey: "MODEL_REASONING_EFFORT", scope: "shared", options: ["", "off", "none", "minimal", "low", "medium", "high", "xhigh", "max"] },
|
|
1385
1648
|
{ key: "reasoningSummary", label: "Reasoning summary", envKey: "MODEL_REASONING_SUMMARY", scope: "shared", options: ["", "auto", "concise", "detailed"] },
|
|
1386
|
-
{ key: "maxOutputTokens", label: "Max output tokens", envKey: "MODEL_MAX_OUTPUT_TOKENS", scope: "shared"
|
|
1649
|
+
{ key: "maxOutputTokens", label: "Max output tokens", envKey: "MODEL_MAX_OUTPUT_TOKENS", scope: "shared" },
|
|
1387
1650
|
{ key: "timeoutMs", label: "Timeout ms", envKey: "MODEL_TIMEOUT_MS", scope: "shared", placeholder: "120000" },
|
|
1388
1651
|
{ key: "streamIdleTimeoutMs", label: "Stream idle timeout ms", envKey: "MODEL_STREAM_IDLE_TIMEOUT_MS", scope: "shared", placeholder: "120000" },
|
|
1389
1652
|
{ key: "maxRetries", label: "Max retries", envKey: "MODEL_MAX_RETRIES", scope: "shared", placeholder: "2" },
|
|
@@ -1393,23 +1656,13 @@ const LOGIN_FIELD_DEFINITIONS = {
|
|
|
1393
1656
|
{ key: "apiKey", label: "API key", envKey: "OPENAI_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
|
|
1394
1657
|
{ key: "baseUrl", label: "Base URL", envKey: "OPENAI_BASE_URL", scope: "provider", placeholder: "https://api.openai.com" },
|
|
1395
1658
|
{ key: "model", label: "Model", envKey: "OPENAI_MODEL", scope: "provider", required: true, placeholder: "gpt-5.6" },
|
|
1396
|
-
{ key: "fallbackModel", label: "Fallback model", envKey: "OPENAI_FALLBACK_MODEL", scope: "provider" },
|
|
1397
1659
|
{ key: "endpoint", label: "Endpoint", envKey: "OPENAI_ENDPOINT", scope: "provider", placeholder: "auto", options: ["auto", "responses", "chat"] },
|
|
1398
1660
|
...SHARED_LOGIN_FIELDS,
|
|
1399
1661
|
],
|
|
1400
|
-
anthropic: [
|
|
1401
|
-
{ key: "apiKey", label: "API key", envKey: "ANTHROPIC_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-ant-..." },
|
|
1402
|
-
{ key: "baseUrl", label: "Base URL", envKey: "ANTHROPIC_BASE_URL", scope: "provider", placeholder: "https://api.anthropic.com" },
|
|
1403
|
-
{ key: "model", label: "Model", envKey: "ANTHROPIC_MODEL", scope: "provider", required: true, placeholder: "claude-sonnet-4-6" },
|
|
1404
|
-
{ key: "fallbackModel", label: "Fallback model", envKey: "ANTHROPIC_FALLBACK_MODEL", scope: "provider" },
|
|
1405
|
-
{ key: "version", label: "Anthropic version", envKey: "ANTHROPIC_VERSION", scope: "provider", placeholder: "2023-06-01" },
|
|
1406
|
-
...SHARED_LOGIN_FIELDS,
|
|
1407
|
-
],
|
|
1408
1662
|
};
|
|
1409
1663
|
const DEPRECATED_MODEL_ENV_KEYS = [
|
|
1410
1664
|
"MODEL_API_KEY", "MODEL_BASE_URL", "MODEL_ID", "MODEL_FALLBACK_ID", "MODEL_ENDPOINT", "OPENAI_PROVIDER",
|
|
1411
1665
|
"OPENAI_REASONING_EFFORT", "OPENAI_REASONING_SUMMARY", "OPENAI_MAX_OUTPUT_TOKENS", "OPENAI_TIMEOUT_MS", "OPENAI_STREAM_IDLE_TIMEOUT_MS", "OPENAI_MAX_RETRIES",
|
|
1412
|
-
"ANTHROPIC_REASONING_EFFORT", "ANTHROPIC_REASONING_SUMMARY", "ANTHROPIC_MAX_OUTPUT_TOKENS", "ANTHROPIC_TIMEOUT_MS", "ANTHROPIC_STREAM_IDLE_TIMEOUT_MS", "ANTHROPIC_MAX_RETRIES",
|
|
1413
1666
|
];
|
|
1414
1667
|
function createLoginFormPayload(envPath, provider) {
|
|
1415
1668
|
const env = parseEnvFileSafe(envPath);
|
|
@@ -1434,19 +1687,13 @@ function loginValuesForProvider(provider, env) {
|
|
|
1434
1687
|
values.endpoint = "auto";
|
|
1435
1688
|
return values;
|
|
1436
1689
|
}
|
|
1437
|
-
function guessLoginProvider(
|
|
1438
|
-
if (env.ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_API_KEY)
|
|
1439
|
-
return "anthropic";
|
|
1690
|
+
function guessLoginProvider(_env) {
|
|
1440
1691
|
return currentModelProvider();
|
|
1441
1692
|
}
|
|
1442
|
-
function defaultBaseUrlForLoginProvider(
|
|
1443
|
-
if (provider === "anthropic")
|
|
1444
|
-
return "https://api.anthropic.com";
|
|
1693
|
+
function defaultBaseUrlForLoginProvider(_provider) {
|
|
1445
1694
|
return "https://api.openai.com";
|
|
1446
1695
|
}
|
|
1447
|
-
function defaultModelForLoginProvider(
|
|
1448
|
-
if (provider === "anthropic")
|
|
1449
|
-
return "claude-sonnet-4-6";
|
|
1696
|
+
function defaultModelForLoginProvider(_provider) {
|
|
1450
1697
|
return "gpt-5.6";
|
|
1451
1698
|
}
|
|
1452
1699
|
function validateLoginFormPayload(payload) {
|
|
@@ -1462,6 +1709,9 @@ function validateLoginFormPayload(payload) {
|
|
|
1462
1709
|
if (value && !Number.isFinite(Number(value)))
|
|
1463
1710
|
return `${fieldKey} must be a number.`;
|
|
1464
1711
|
}
|
|
1712
|
+
if (payload.provider === "openai" && payload.values.endpoint === "chat" && payload.values.reasoningSummary?.trim()) {
|
|
1713
|
+
return "Reasoning summary requires the Responses API endpoint.";
|
|
1714
|
+
}
|
|
1465
1715
|
return undefined;
|
|
1466
1716
|
}
|
|
1467
1717
|
async function saveLoginPayloadToEnv(payload) {
|
|
@@ -1496,9 +1746,7 @@ function stripEnvQuotes(value) {
|
|
|
1496
1746
|
return value.slice(1, -1);
|
|
1497
1747
|
return value;
|
|
1498
1748
|
}
|
|
1499
|
-
function modelEnvKeyForProvider(
|
|
1500
|
-
if (provider === "anthropic")
|
|
1501
|
-
return "ANTHROPIC_MODEL";
|
|
1749
|
+
function modelEnvKeyForProvider(_provider) {
|
|
1502
1750
|
return "OPENAI_MODEL";
|
|
1503
1751
|
}
|
|
1504
1752
|
function envValueForReasoning(reasoning) {
|
|
@@ -1547,9 +1795,6 @@ function formatEnvValue(value) {
|
|
|
1547
1795
|
return value;
|
|
1548
1796
|
return JSON.stringify(value);
|
|
1549
1797
|
}
|
|
1550
|
-
function formatResume(snapshot) {
|
|
1551
|
-
return `resumed session ${snapshot.sessionId}: ${snapshot.resumedMessages} messages from ${snapshot.transcriptPath}`;
|
|
1552
|
-
}
|
|
1553
1798
|
function applyEnvUpdatesToProcess(updates) {
|
|
1554
1799
|
for (const [key, value] of Object.entries(updates)) {
|
|
1555
1800
|
if (value === undefined)
|
|
@@ -1573,8 +1818,6 @@ function validateModelReasoningArgument(modelId, reasoning) {
|
|
|
1573
1818
|
function formatModelSettings(settings, defaultReasoning) {
|
|
1574
1819
|
const window = resolveContextWindowTokens(settings.model);
|
|
1575
1820
|
const lines = ["Model settings:", ` Model: ${settings.model ?? "<provider default>"}`];
|
|
1576
|
-
if (settings.fallbackModel)
|
|
1577
|
-
lines.push(` Fallback: ${settings.fallbackModel}`);
|
|
1578
1821
|
lines.push(` Reasoning effort: ${formatReasoningSetting(settings.reasoning)}`);
|
|
1579
1822
|
if (defaultReasoning?.effort)
|
|
1580
1823
|
lines.push(` Env default reasoning: ${defaultReasoning.effort}`);
|
|
@@ -1607,30 +1850,30 @@ function renderMessage(message, append, activeAssistantId, options = {}) {
|
|
|
1607
1850
|
if (kind === "meta")
|
|
1608
1851
|
continue;
|
|
1609
1852
|
if (kind === "system")
|
|
1610
|
-
append({ kind, title: titleForRole(message.role), text: block.text, previewStyle: "summary" });
|
|
1853
|
+
append({ kind, title: titleForRole(message.role), text: block.text, messageId: message.id, previewStyle: "summary" });
|
|
1611
1854
|
else
|
|
1612
|
-
append({ kind, text: block.text });
|
|
1855
|
+
append({ kind, text: block.text, messageId: message.id });
|
|
1613
1856
|
rendered = true;
|
|
1614
1857
|
}
|
|
1615
1858
|
else if (block.type === "image") {
|
|
1616
1859
|
const line = imageLineForBlock(message.role, block, message.id, blockIndex);
|
|
1617
1860
|
if (!line)
|
|
1618
1861
|
continue;
|
|
1619
|
-
append({ ...line, parentToolName });
|
|
1862
|
+
append({ ...line, messageId: message.id, parentToolName });
|
|
1620
1863
|
rendered = true;
|
|
1621
1864
|
}
|
|
1622
1865
|
else if (block.type === "thinking") {
|
|
1623
1866
|
if (options.includeThinkingBlocks === false)
|
|
1624
1867
|
continue;
|
|
1625
|
-
append(thinkingLine(block.text));
|
|
1868
|
+
append({ ...thinkingLine(block.text), messageId: message.id });
|
|
1626
1869
|
rendered = true;
|
|
1627
1870
|
}
|
|
1628
1871
|
else if (block.type === "tool_use" && options.includeToolUseBlocks) {
|
|
1629
|
-
append({ ...formatToolUse(block), live: false });
|
|
1872
|
+
append({ ...formatToolUse(block), messageId: message.id, live: false });
|
|
1630
1873
|
rendered = true;
|
|
1631
1874
|
}
|
|
1632
1875
|
else if (block.type === "tool_result") {
|
|
1633
|
-
append(formatToolResultLine(block.name, block.output, block.ok));
|
|
1876
|
+
append({ ...formatToolResultLine(block.name, block.output, block.ok), messageId: message.id });
|
|
1634
1877
|
rendered = true;
|
|
1635
1878
|
}
|
|
1636
1879
|
}
|
|
@@ -1641,12 +1884,12 @@ function renderToolResultMessage(message, append) {
|
|
|
1641
1884
|
for (const block of message.blocks) {
|
|
1642
1885
|
if (block.type !== "tool_result")
|
|
1643
1886
|
continue;
|
|
1644
|
-
append({ ...formatToolResultLine(block.name, block.output, block.ok), toolUseId: block.toolUseId }, block.toolUseId);
|
|
1887
|
+
append({ ...formatToolResultLine(block.name, block.output, block.ok), messageId: message.id, toolUseId: block.toolUseId }, block.toolUseId);
|
|
1645
1888
|
rendered = true;
|
|
1646
1889
|
}
|
|
1647
1890
|
return rendered;
|
|
1648
1891
|
}
|
|
1649
|
-
function renderMessageImages(message, append) {
|
|
1892
|
+
function renderMessageImages(message, append, messageId = message.id) {
|
|
1650
1893
|
let rendered = false;
|
|
1651
1894
|
const parentToolName = typeof message.metadata?.tool === "string" ? message.metadata.tool : undefined;
|
|
1652
1895
|
const parentToolUseId = message.blocks.find((block) => block.type === "tool_result")?.toolUseId;
|
|
@@ -1656,7 +1899,7 @@ function renderMessageImages(message, append) {
|
|
|
1656
1899
|
const line = imageLineForBlock(message.role, block, message.id, blockIndex);
|
|
1657
1900
|
if (!line)
|
|
1658
1901
|
continue;
|
|
1659
|
-
append({ ...line, parentToolName, parentToolUseId });
|
|
1902
|
+
append({ ...line, messageId, parentToolName, parentToolUseId });
|
|
1660
1903
|
rendered = true;
|
|
1661
1904
|
}
|
|
1662
1905
|
return rendered;
|
|
@@ -1727,8 +1970,8 @@ function thinkingLine(text, live = false) {
|
|
|
1727
1970
|
return { kind: "thinking", title: titleForKind("thinking"), text, previewStyle: "summary", summaryMaxLines: THINKING_SUMMARY_MAX_LINES, live };
|
|
1728
1971
|
}
|
|
1729
1972
|
function formatToolUse(toolUse) {
|
|
1730
|
-
if (toolUse.name === "plan" &&
|
|
1731
|
-
return { kind: "tool", toolName: toolUse.name, title: toolTitle(toolUse.name, "running"), bodyTitle:
|
|
1973
|
+
if (toolUse.name === "plan" && isWebPlanPayload(toolUse.input))
|
|
1974
|
+
return { kind: "tool", toolName: toolUse.name, title: toolTitle(toolUse.name, "running"), bodyTitle: webPlanBodyTitle(toolUse.input), text: serializeWebPlanPayload(toolUse.input), collapsible: true };
|
|
1732
1975
|
if (toolUse.name === "image2")
|
|
1733
1976
|
return { kind: "tool", toolName: toolUse.name, toolUseId: toolUse.id, title: toolTitle(toolUse.name, "running"), bodyTitle: "图片模型处理中", text: JSON.stringify(toolUse.input ?? {}, null, 2), format: "plain", previewStyle: "summary", collapsible: true };
|
|
1734
1977
|
const summary = summarizeToolUse(toolUse.name, toolUse.input);
|
|
@@ -1742,7 +1985,8 @@ function toolTitle(toolName, _phase) {
|
|
|
1742
1985
|
const labels = {
|
|
1743
1986
|
agent: "子任务",
|
|
1744
1987
|
edit: "编辑文件",
|
|
1745
|
-
|
|
1988
|
+
exec_command: "执行命令",
|
|
1989
|
+
write_stdin: "终端交互",
|
|
1746
1990
|
expose_downloads: "文件下载",
|
|
1747
1991
|
grep: "搜索文本",
|
|
1748
1992
|
image2: "图片生成",
|
|
@@ -1783,24 +2027,6 @@ function toolUsePurpose(toolName, input) {
|
|
|
1783
2027
|
return `修改 ${path.basename(input.path)}`;
|
|
1784
2028
|
return undefined;
|
|
1785
2029
|
}
|
|
1786
|
-
function isPlanToolPayload(value) {
|
|
1787
|
-
if (!isRecord(value) || !Array.isArray(value.items))
|
|
1788
|
-
return false;
|
|
1789
|
-
return value.items.every((item) => isRecord(item) && typeof item.description === "string" && (item.status === "pending" || item.status === "in_progress" || item.status === "completed"));
|
|
1790
|
-
}
|
|
1791
|
-
function planToolBodyTitle(payload) {
|
|
1792
|
-
const title = payload.title?.trim();
|
|
1793
|
-
return title ? title : undefined;
|
|
1794
|
-
}
|
|
1795
|
-
function formatPlanToolPayload(payload) {
|
|
1796
|
-
const sections = [];
|
|
1797
|
-
if (payload.summary?.trim())
|
|
1798
|
-
sections.push(payload.summary.trim());
|
|
1799
|
-
if (payload.note?.trim())
|
|
1800
|
-
sections.push(payload.note.trim());
|
|
1801
|
-
sections.push(payload.items.map((item) => item.status === "completed" ? `- ~~${item.description.trim()}~~` : item.status === "in_progress" ? `- ▶ ${item.description.trim()}` : `- ${item.description.trim()}`).join("\n"));
|
|
1802
|
-
return sections.filter(Boolean).join("\n");
|
|
1803
|
-
}
|
|
1804
2030
|
function formatToolResult(toolName, output, ok) {
|
|
1805
2031
|
if ((toolName === "edit" || toolName === "write") && isRecord(output) && isEditToolOutput(output))
|
|
1806
2032
|
return { text: formatEditToolDiff(output, ok), format: "diff", summaryMaxLines: EDIT_TOOL_SUMMARY_MAX_LINES };
|
|
@@ -1818,8 +2044,8 @@ function formatToolResult(toolName, output, ok) {
|
|
|
1818
2044
|
return { text: formatImageGenerationToolResult(output, ok), format: "plain", summaryMaxLines: 4 };
|
|
1819
2045
|
if (toolName === "expose_downloads")
|
|
1820
2046
|
return { text: formatExposeDownloadsToolResult(output, ok), full: true, bodyTitle: ok ? "请点击下载" : "下载准备失败" };
|
|
1821
|
-
if (toolName === "plan" &&
|
|
1822
|
-
return { text:
|
|
2047
|
+
if (toolName === "plan" && isWebPlanPayload(output))
|
|
2048
|
+
return { text: serializeWebPlanPayload(output), full: true, bodyTitle: webPlanBodyTitle(output) };
|
|
1823
2049
|
if (typeof output === "string")
|
|
1824
2050
|
return { text: output, format: hasAnsi(output) ? "ansi" : undefined, summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
1825
2051
|
return { text: `${ok ? "ok" : "failed"}\n${formatReplData(output, 6000)}`, summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
@@ -1895,12 +2121,14 @@ function diffLineMarker(line) {
|
|
|
1895
2121
|
return marker === "+" || marker === "-" || marker === " " ? marker : undefined;
|
|
1896
2122
|
}
|
|
1897
2123
|
function isExecOutput(value) {
|
|
1898
|
-
return isRecord(value) && typeof value.command === "string" && typeof value.
|
|
2124
|
+
return isRecord(value) && typeof value.command === "string" && typeof value.duration_ms === "number";
|
|
1899
2125
|
}
|
|
1900
2126
|
function formatExecToolResult(output, ok) {
|
|
1901
|
-
const status = output.
|
|
2127
|
+
const status = output.status === "running" ? "运行中" : output.timed_out ? "已超时" : ok ? "已完成" : "执行失败";
|
|
1902
2128
|
const description = typeof output.description === "string" ? output.description.trim() : "";
|
|
1903
|
-
const lines = [description ? `目的:${description}` : "执行命令", `状态:${status}`, `耗时:${output.
|
|
2129
|
+
const lines = [description ? `目的:${description}` : "执行命令", `状态:${status}`, `耗时:${output.duration_ms}ms`];
|
|
2130
|
+
if (output.status === "running" && typeof output.session_id === "string")
|
|
2131
|
+
lines.push(`会话:${output.session_id}`);
|
|
1904
2132
|
const stdout = typeof output.stdout === "string" ? output.stdout.replace(/\s+$/u, "") : "";
|
|
1905
2133
|
const stderr = typeof output.stderr === "string" ? output.stderr.replace(/\s+$/u, "") : "";
|
|
1906
2134
|
if (stdout)
|