neoctl 0.2.35 → 0.2.37
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/agents/agent-report-security.test.d.ts +1 -0
- package/dist/agents/agent-report-security.test.js +301 -0
- package/dist/agents/agent-report-security.test.js.map +1 -0
- package/dist/agents/agent-tool-persistence.test.d.ts +1 -0
- package/dist/agents/agent-tool-persistence.test.js +319 -0
- package/dist/agents/agent-tool-persistence.test.js.map +1 -0
- package/dist/agents/agent-tool.d.ts +3 -0
- package/dist/agents/agent-tool.js +246 -76
- package/dist/agents/agent-tool.js.map +1 -1
- package/dist/agents/live-preview-redaction-security.test.d.ts +1 -0
- package/dist/agents/live-preview-redaction-security.test.js +286 -0
- package/dist/agents/live-preview-redaction-security.test.js.map +1 -0
- package/dist/agents/local-agent-task.d.ts +68 -1
- package/dist/agents/local-agent-task.js +78 -5
- package/dist/agents/local-agent-task.js.map +1 -1
- package/dist/agents/no-nested-delegation.test.d.ts +1 -0
- package/dist/agents/no-nested-delegation.test.js +38 -0
- package/dist/agents/no-nested-delegation.test.js.map +1 -0
- package/dist/agents/obs07-11-run-facts.test.d.ts +1 -0
- package/dist/agents/obs07-11-run-facts.test.js +459 -0
- package/dist/agents/obs07-11-run-facts.test.js.map +1 -0
- package/dist/agents/smoke-agent-lifecycle.d.ts +1 -0
- package/dist/agents/smoke-agent-lifecycle.js +192 -0
- package/dist/agents/smoke-agent-lifecycle.js.map +1 -0
- package/dist/agents/smoke-agents.js +7 -3
- package/dist/agents/smoke-agents.js.map +1 -1
- package/dist/context/context-manager.d.ts +1 -0
- package/dist/context/context-manager.js +10 -6
- package/dist/context/context-manager.js.map +1 -1
- package/dist/context/smoke-context.js +46 -3
- package/dist/context/smoke-context.js.map +1 -1
- package/dist/core/image-registry.js +1 -1
- package/dist/core/image-registry.js.map +1 -1
- package/dist/core/obs04-visible-data.test.d.ts +1 -0
- package/dist/core/obs04-visible-data.test.js +147 -0
- package/dist/core/obs04-visible-data.test.js.map +1 -0
- package/dist/core/query-engine.d.ts +5 -0
- package/dist/core/query-engine.js +36 -3
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +5 -1
- package/dist/core/query.js +120 -18
- package/dist/core/query.js.map +1 -1
- package/dist/core/run-agent-pause.test.d.ts +1 -0
- package/dist/core/run-agent-pause.test.js +52 -0
- package/dist/core/run-agent-pause.test.js.map +1 -0
- package/dist/core/run-agent-persistence.test.d.ts +1 -0
- package/dist/core/run-agent-persistence.test.js +222 -0
- package/dist/core/run-agent-persistence.test.js.map +1 -0
- package/dist/core/run-agent.d.ts +13 -0
- package/dist/core/run-agent.js +72 -17
- package/dist/core/run-agent.js.map +1 -1
- package/dist/core/smoke-core-loop.js +8 -7
- package/dist/core/smoke-core-loop.js.map +1 -1
- package/dist/model/model-gateway.d.ts +1 -0
- package/dist/model/openai-native-image-guard.test.d.ts +1 -0
- package/dist/model/openai-native-image-guard.test.js +101 -0
- package/dist/model/openai-native-image-guard.test.js.map +1 -0
- package/dist/model/openai-responses-mapper.js +91 -16
- package/dist/model/openai-responses-mapper.js.map +1 -1
- package/dist/plugins/smoke-plugin-system.js +14 -14
- package/dist/repl/index.js +22 -28
- package/dist/repl/index.js.map +1 -1
- package/dist/secrets/secret-redaction.d.ts +6 -5
- package/dist/secrets/secret-redaction.js +7 -79
- package/dist/secrets/secret-redaction.js.map +1 -1
- package/dist/secrets/secret-types.d.ts +4 -1
- package/dist/secrets/secret-types.js.map +1 -1
- package/dist/session/session-store-safety.test.d.ts +1 -0
- package/dist/session/session-store-safety.test.js +107 -0
- package/dist/session/session-store-safety.test.js.map +1 -0
- package/dist/session/session-store.d.ts +5 -1
- package/dist/session/session-store.js +69 -7
- package/dist/session/session-store.js.map +1 -1
- package/dist/session/tool-result-memory.d.ts +3 -1
- package/dist/session/tool-result-memory.js.map +1 -1
- package/dist/tasks/subagent-tools.d.ts +4 -1
- package/dist/tasks/subagent-tools.js +85 -24
- package/dist/tasks/subagent-tools.js.map +1 -1
- package/dist/tasks/subagent-tools.test.d.ts +1 -0
- package/dist/tasks/subagent-tools.test.js +159 -0
- package/dist/tasks/subagent-tools.test.js.map +1 -0
- package/dist/tasks/task-ack-size.test.d.ts +1 -0
- package/dist/tasks/task-ack-size.test.js +101 -0
- package/dist/tasks/task-ack-size.test.js.map +1 -0
- package/dist/tasks/task-persistence.d.ts +23 -0
- package/dist/tasks/task-persistence.js +216 -0
- package/dist/tasks/task-persistence.js.map +1 -0
- package/dist/tasks/task-persistence.test.d.ts +1 -0
- package/dist/tasks/task-persistence.test.js +331 -0
- package/dist/tasks/task-persistence.test.js.map +1 -0
- package/dist/tasks/task-store.d.ts +51 -7
- package/dist/tasks/task-store.js +333 -31
- package/dist/tasks/task-store.js.map +1 -1
- package/dist/tools/builtins/exec-process-manager.d.ts +97 -2
- package/dist/tools/builtins/exec-process-manager.js +218 -19
- package/dist/tools/builtins/exec-process-manager.js.map +1 -1
- package/dist/tools/builtins/exec-tool.js +14 -1
- package/dist/tools/builtins/exec-tool.js.map +1 -1
- package/dist/tools/builtins/image-generation-tool.js +24 -24
- package/dist/tools/builtins/image-generation-tool.js.map +1 -1
- package/dist/tools/smoke-exec-process.js +2 -1
- package/dist/tools/smoke-exec-process.js.map +1 -1
- package/dist/tools/smoke-terminal-output-chain.d.ts +1 -0
- package/dist/tools/smoke-terminal-output-chain.js +245 -0
- package/dist/tools/smoke-terminal-output-chain.js.map +1 -0
- package/dist/tools/smoke-tool-system.js +6 -6
- package/dist/tools/smoke-tool-system.js.map +1 -1
- package/dist/tools/terminal-output-persistence.test.d.ts +1 -0
- package/dist/tools/terminal-output-persistence.test.js +50 -0
- package/dist/tools/terminal-output-persistence.test.js.map +1 -0
- package/dist/tools/terminal-output-store.d.ts +150 -0
- package/dist/tools/terminal-output-store.js +562 -0
- package/dist/tools/terminal-output-store.js.map +1 -0
- package/dist/tools/terminal-output-store.test.d.ts +1 -0
- package/dist/tools/terminal-output-store.test.js +364 -0
- package/dist/tools/terminal-output-store.test.js.map +1 -0
- package/dist/tools/tool.d.ts +5 -0
- package/dist/tools/tool.js.map +1 -1
- package/dist/types/events.d.ts +1 -0
- package/dist/types/messages.d.ts +1 -0
- package/dist/web/agent-content-detail.d.ts +78 -0
- package/dist/web/agent-content-detail.js +495 -0
- package/dist/web/agent-content-detail.js.map +1 -0
- package/dist/web/agent-content-detail.test.d.ts +1 -0
- package/dist/web/agent-content-detail.test.js +307 -0
- package/dist/web/agent-content-detail.test.js.map +1 -0
- package/dist/web/agent-parent-messages.test.d.ts +1 -0
- package/dist/web/agent-parent-messages.test.js +58 -0
- package/dist/web/agent-parent-messages.test.js.map +1 -0
- package/dist/web/agent-tool-payload-security.test.d.ts +1 -0
- package/dist/web/agent-tool-payload-security.test.js +120 -0
- package/dist/web/agent-tool-payload-security.test.js.map +1 -0
- package/dist/web/index.d.ts +213 -9
- package/dist/web/index.js +260 -108
- package/dist/web/index.js.map +1 -1
- package/dist/web/smoke-agent-content-http.d.ts +1 -0
- package/dist/web/smoke-agent-content-http.js +446 -0
- package/dist/web/smoke-agent-content-http.js.map +1 -0
- package/dist/web/smoke-status-semantics.d.ts +1 -0
- package/dist/web/smoke-status-semantics.js +90 -0
- package/dist/web/smoke-status-semantics.js.map +1 -0
- package/dist/web/smoke-task-session-entrypoints.d.ts +1 -0
- package/dist/web/smoke-task-session-entrypoints.js +96 -0
- package/dist/web/smoke-task-session-entrypoints.js.map +1 -0
- package/dist/web/smoke-terminal-output-http.d.ts +1 -0
- package/dist/web/smoke-terminal-output-http.js +372 -0
- package/dist/web/smoke-terminal-output-http.js.map +1 -0
- package/dist/web/smoke-tool-call-detail.d.ts +1 -0
- package/dist/web/smoke-tool-call-detail.js +106 -0
- package/dist/web/smoke-tool-call-detail.js.map +1 -0
- package/dist/web/smoke-tool-call-http.d.ts +1 -0
- package/dist/web/smoke-tool-call-http.js +100 -0
- package/dist/web/smoke-tool-call-http.js.map +1 -0
- package/dist/web/smoke-tool-detail-fields-http.d.ts +1 -0
- package/dist/web/smoke-tool-detail-fields-http.js +121 -0
- package/dist/web/smoke-tool-detail-fields-http.js.map +1 -0
- package/dist/web/smoke-web-agent-tasks.d.ts +1 -0
- package/dist/web/smoke-web-agent-tasks.js +50 -0
- package/dist/web/smoke-web-agent-tasks.js.map +1 -0
- package/dist/web/smoke-web-terminal-tasks.js +126 -76
- package/dist/web/smoke-web-terminal-tasks.js.map +1 -1
- package/dist/web/status-semantics.d.ts +11 -0
- package/dist/web/status-semantics.js +59 -0
- package/dist/web/status-semantics.js.map +1 -0
- package/dist/web/terminal-presentation.d.ts +15 -0
- package/dist/web/terminal-presentation.js +61 -0
- package/dist/web/terminal-presentation.js.map +1 -0
- package/dist/web/terminal-presentation.test.d.ts +1 -0
- package/dist/web/terminal-presentation.test.js +33 -0
- package/dist/web/terminal-presentation.test.js.map +1 -0
- package/dist/web/tool-call-detail.d.ts +40 -0
- package/dist/web/tool-call-detail.js +167 -0
- package/dist/web/tool-call-detail.js.map +1 -0
- package/dist/web/tool-detail-fields.d.ts +59 -0
- package/dist/web/tool-detail-fields.js +119 -0
- package/dist/web/tool-detail-fields.js.map +1 -0
- package/dist/web/tool-detail-fields.test.d.ts +1 -0
- package/dist/web/tool-detail-fields.test.js +121 -0
- package/dist/web/tool-detail-fields.test.js.map +1 -0
- package/package.json +90 -90
package/dist/web/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { subagentHeader, subagentStatusFacts } from "./status-semantics.js";
|
|
2
3
|
import http from "node:http";
|
|
3
4
|
import fs from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
@@ -31,6 +32,10 @@ import { parseReplCommand, helpText, replCommandDefinitions } from "../repl/comm
|
|
|
31
32
|
import { writeSessionMarkdownExport } from "../session/session-export.js";
|
|
32
33
|
import { DefaultContextManager } from "../context/context-manager.js";
|
|
33
34
|
import { createImageId } from "../types/messages.js";
|
|
35
|
+
import { readToolCallDetail, toolErrorText, allowToolDetailRequest, sanitizeAgentToolPayload, redactToolDetail } from "./tool-call-detail.js";
|
|
36
|
+
import { terminalFacts, terminalPreviews, terminalResultText } from "./terminal-presentation.js";
|
|
37
|
+
import { createAgentContentDetailResolver } from "./agent-content-detail.js";
|
|
38
|
+
import { toolDetailFieldsFromDetail } from "./tool-detail-fields.js";
|
|
34
39
|
import { WEB_HTML } from "./html.js";
|
|
35
40
|
import { openDirectory } from "../open-directory.js";
|
|
36
41
|
import { resolveImageBlockDataResultSync, resolveImageBlockDataSync } from "../core/image-storage.js";
|
|
@@ -167,14 +172,10 @@ export async function createWebRuntime(options = {}) {
|
|
|
167
172
|
tools.register(tool);
|
|
168
173
|
const agentRuntime = { modelGateway, tools, taskStore };
|
|
169
174
|
registerTool(createAgentTool(agentRuntime), { source: "builtin" });
|
|
175
|
+
let runtime;
|
|
170
176
|
const resumeHandler = async (taskId, directive) => {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
tools,
|
|
174
|
-
appState: new InMemoryAppState("main", options.cwd),
|
|
175
|
-
emit: () => undefined,
|
|
176
|
-
};
|
|
177
|
-
return resumeAgentTask(taskId, directive, agentRuntime, taskStore, dummyContext);
|
|
177
|
+
const parentContext = createResumeParentContext(runtime.engine, tools, options.cwd);
|
|
178
|
+
return resumeAgentTask(taskId, directive, agentRuntime, taskStore, parentContext);
|
|
178
179
|
};
|
|
179
180
|
for (const tool of createSubagentTools(taskStore, resumeHandler))
|
|
180
181
|
registerTool(tool, { source: "builtin" });
|
|
@@ -210,8 +211,9 @@ export async function createWebRuntime(options = {}) {
|
|
|
210
211
|
},
|
|
211
212
|
});
|
|
212
213
|
await engine.initialize();
|
|
214
|
+
activateSession({ engine, taskStore });
|
|
213
215
|
const initialMetrics = await engine.contextMetrics();
|
|
214
|
-
|
|
216
|
+
runtime = {
|
|
215
217
|
engine,
|
|
216
218
|
appPromptStore,
|
|
217
219
|
communicationLogger,
|
|
@@ -242,6 +244,7 @@ export async function createWebRuntime(options = {}) {
|
|
|
242
244
|
resolveSession: options.resolveSessionToolOverrides,
|
|
243
245
|
},
|
|
244
246
|
};
|
|
247
|
+
return runtime;
|
|
245
248
|
}
|
|
246
249
|
function normalizeWebRuntimePlugins(value) {
|
|
247
250
|
if (!Array.isArray(value))
|
|
@@ -274,10 +277,46 @@ function applyToolOverrides(registry, catalog, globalOverrides, sessionOverrides
|
|
|
274
277
|
for (const tool of catalog)
|
|
275
278
|
registry.setEnabled(tool.name, sessionOverrides[tool.name] ?? globalOverrides[tool.name] ?? true);
|
|
276
279
|
}
|
|
277
|
-
|
|
280
|
+
/** Bind only the foreground view; never stop work owned by another session. */
|
|
281
|
+
export function activateSession(runtime) {
|
|
282
|
+
const sessionDir = runtime.engine.snapshot().session?.sessionDir || undefined;
|
|
283
|
+
const summary = runtime.taskStore.bindSession?.(sessionDir);
|
|
284
|
+
const interruptedTasks = summary?.loaded ? runtime.taskStore.recoverableInterruptedTasks?.(sessionDir) ?? [] : [];
|
|
285
|
+
if (interruptedTasks.length) {
|
|
286
|
+
runtime.engine.noteRecoverableSubagents(() => {
|
|
287
|
+
for (const task of interruptedTasks)
|
|
288
|
+
runtime.taskStore.markNotified(task.taskId);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
export function sessionAgentTasks(runtime) {
|
|
293
|
+
const dir = runtime.engine.snapshot().session?.sessionDir || undefined;
|
|
294
|
+
// Older embedders provide minimal TaskStore mocks with no persistence API.
|
|
295
|
+
return runtime.taskStore.list().filter((task) => !runtime.taskStore.bindSession
|
|
296
|
+
|| task.ownerSessionDir === (dir ? path.resolve(dir) : undefined));
|
|
297
|
+
}
|
|
298
|
+
export function createResumeParentContext(engine, tools, cwd) {
|
|
299
|
+
const snapshot = engine.snapshot();
|
|
300
|
+
const session = snapshot.session;
|
|
278
301
|
return {
|
|
279
|
-
|
|
280
|
-
|
|
302
|
+
agentId: snapshot.agentId ?? "main",
|
|
303
|
+
tools,
|
|
304
|
+
appState: new InMemoryAppState(snapshot.agentId ?? "main", cwd),
|
|
305
|
+
session: session?.sessionDir ? {
|
|
306
|
+
sessionId: session.sessionId,
|
|
307
|
+
sessionDir: session.sessionDir,
|
|
308
|
+
rootDir: path.dirname(session.sessionDir),
|
|
309
|
+
} : undefined,
|
|
310
|
+
options: { mainLoopModel: snapshot.model, reasoning: snapshot.reasoning,
|
|
311
|
+
contextWindowTokensOverride: snapshot.contextWindowTokensOverride },
|
|
312
|
+
emit: () => undefined,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
export function createTaskNotificationSource(taskStore) {
|
|
316
|
+
return {
|
|
317
|
+
collectUnnotifiedCompletions(sessionDir) {
|
|
318
|
+
return taskStore.collectUnnotifiedCompletions(sessionDir).filter((task) => !taskStore.bindSession
|
|
319
|
+
|| task.ownerSessionDir === (sessionDir ? path.resolve(sessionDir) : undefined)).map((task) => ({
|
|
281
320
|
taskId: task.taskId,
|
|
282
321
|
agentId: task.agentId,
|
|
283
322
|
status: task.status,
|
|
@@ -364,6 +403,7 @@ function webRuntimeScopeKey(scope) {
|
|
|
364
403
|
}
|
|
365
404
|
export class WebRepl {
|
|
366
405
|
runtime;
|
|
406
|
+
agentContentReader = createAgentContentDetailResolver();
|
|
367
407
|
subscribers = new Set();
|
|
368
408
|
eventSequence = 0;
|
|
369
409
|
pendingDeltaOperations = [];
|
|
@@ -389,7 +429,6 @@ export class WebRepl {
|
|
|
389
429
|
pendingUserImageEchoMessageId;
|
|
390
430
|
runtimeContextRevision = 0;
|
|
391
431
|
runtimeContextPublishQueue = Promise.resolve();
|
|
392
|
-
pendingRuntimeConfiguration = false;
|
|
393
432
|
backgroundSessionRuns = new Map();
|
|
394
433
|
suppressReattachedStreaming = new Set();
|
|
395
434
|
constructor(runtime) {
|
|
@@ -433,18 +472,53 @@ export class WebRepl {
|
|
|
433
472
|
this.send(subscriber, "client.reload", payload);
|
|
434
473
|
return subscribers.length;
|
|
435
474
|
}
|
|
475
|
+
async toolCallDetail(sessionId, toolUseId, messageId) {
|
|
476
|
+
const session = this.runtime.engine.snapshot().session;
|
|
477
|
+
const detail = await readToolCallDetail({ sessionId: session?.sessionId ?? "", expectedSessionId: sessionId, sessionDir: session?.sessionDir, toolUseId, messageId, redact: value => this.runtime.engine.redactDisplayValue(value), entries: this.runtime.engine.getDisplayEntries() });
|
|
478
|
+
return detail ? this.runtime.engine.redactDisplayValue({ ...detail, fields: toolDetailFieldsFromDetail(detail) }) : undefined;
|
|
479
|
+
}
|
|
480
|
+
async agentContent(sessionId, view, request) {
|
|
481
|
+
const session = this.runtime.engine.snapshot().session;
|
|
482
|
+
if (!session?.sessionDir || sessionId !== session.sessionId || !["timeline", "delegation", "report", "messages"].includes(view))
|
|
483
|
+
return undefined;
|
|
484
|
+
// Loading/recovery belongs to activateSession, never this read-only route.
|
|
485
|
+
if (!this.runtime.taskStore.getInSession(request.taskId, session.sessionDir))
|
|
486
|
+
return undefined;
|
|
487
|
+
return this.agentContentReader[view]({
|
|
488
|
+
ownerSessionId: sessionId, ownerSessionDir: session.sessionDir, taskStore: this.runtime.taskStore,
|
|
489
|
+
redact: value => this.runtime.engine.redactDisplayValue(value),
|
|
490
|
+
}, request);
|
|
491
|
+
}
|
|
492
|
+
terminalOutput(sessionId, runId, stream, offset, limitBytes) {
|
|
493
|
+
const session = this.runtime.engine.snapshot().session;
|
|
494
|
+
if (!session?.sessionDir || !sessionId || sessionId !== session.sessionId || !runId || !["stdout", "stderr"].includes(stream))
|
|
495
|
+
return undefined;
|
|
496
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limitBytes) || limitBytes < 4 || limitBytes > 262144)
|
|
497
|
+
return undefined;
|
|
498
|
+
const manager = this.runtime.execProcessManager;
|
|
499
|
+
const registered = manager.registerOwnerSession(sessionId, session.sessionDir);
|
|
500
|
+
if (!registered.ok)
|
|
501
|
+
return undefined;
|
|
502
|
+
const page = manager.readOutput(sessionId, runId, { stream: stream, offset, limitBytes });
|
|
503
|
+
// Cursor addresses stored bytes, not the possibly additionally redacted response length.
|
|
504
|
+
return page.ok ? this.runtime.engine.redactDisplayValue({ sessionId, runId, ...page.value }) : undefined;
|
|
505
|
+
}
|
|
436
506
|
snapshot(includeCatalog = false) {
|
|
437
507
|
const backgroundTasks = this.backgroundTasks();
|
|
508
|
+
const engineSnapshot = this.runtime.engine.snapshot();
|
|
438
509
|
return {
|
|
439
|
-
lines: this.lines,
|
|
510
|
+
lines: this.lines.map(line => line.toolError ? { ...line, toolError: this.runtime.engine.redactDisplayValue?.(line.toolError) ?? line.toolError } : line),
|
|
440
511
|
status: this.status,
|
|
441
512
|
busy: this.busy,
|
|
442
513
|
queuedInput: this.queuedInput,
|
|
443
514
|
backgroundTaskCount: backgroundTasks.length,
|
|
444
515
|
backgroundTasks,
|
|
516
|
+
terminalTaskHistory: this.terminalTaskHistory(),
|
|
517
|
+
agentTaskHistory: sessionAgentTasks(this.runtime).filter((task) => this.runtime.taskStore.isTerminal(task)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).map((task) => this.agentTaskSnapshot(task)),
|
|
445
518
|
backgroundSessionRunCount: this.backgroundSessionRuns.size,
|
|
446
519
|
runningSessionIds: [...this.backgroundSessionRuns.keys()],
|
|
447
|
-
session:
|
|
520
|
+
session: engineSnapshot.session,
|
|
521
|
+
modelSettings: { model: engineSnapshot.model, reasoning: engineSnapshot.reasoning },
|
|
448
522
|
fastMode: this.runtime.engine.isFastMode(),
|
|
449
523
|
appPrompt: this.runtime.engine.getAppPrompt(),
|
|
450
524
|
catalog: includeCatalog ? webCatalog(this.runtime) : undefined,
|
|
@@ -514,6 +588,8 @@ export class WebRepl {
|
|
|
514
588
|
};
|
|
515
589
|
}
|
|
516
590
|
async setGlobalTools(value) {
|
|
591
|
+
if (this.busy)
|
|
592
|
+
return actionFailure("TOOL_UPDATE_BLOCKED", "cannot change global tools while a response is running");
|
|
517
593
|
if (!isRecord(value))
|
|
518
594
|
return actionFailure("INVALID_REQUEST", "tool overrides must be an object");
|
|
519
595
|
const support = this.runtime.toolSupport;
|
|
@@ -524,12 +600,10 @@ export class WebRepl {
|
|
|
524
600
|
return normalized;
|
|
525
601
|
try {
|
|
526
602
|
support.globalOverrides = normalized.value;
|
|
603
|
+
this.syncPluginToolRegistration();
|
|
604
|
+
applyToolOverrides(this.runtime.tools, support.catalog, support.globalOverrides, support.sessionOverrides);
|
|
527
605
|
if (support.persistGlobal)
|
|
528
606
|
await support.persistGlobal(support.globalOverrides);
|
|
529
|
-
if (this.busy)
|
|
530
|
-
this.pendingRuntimeConfiguration = true;
|
|
531
|
-
else
|
|
532
|
-
await this.applyRuntimeConfiguration();
|
|
533
607
|
await this.refreshToolConfiguration();
|
|
534
608
|
return { ok: true, state: this.globalTools() };
|
|
535
609
|
}
|
|
@@ -538,6 +612,8 @@ export class WebRepl {
|
|
|
538
612
|
}
|
|
539
613
|
}
|
|
540
614
|
async setSessionTools(value) {
|
|
615
|
+
if (this.busy)
|
|
616
|
+
return actionFailure("TOOL_UPDATE_BLOCKED", "cannot change session tools while a response is running");
|
|
541
617
|
if (!isRecord(value))
|
|
542
618
|
return actionFailure("INVALID_REQUEST", "tool overrides must be an object");
|
|
543
619
|
const support = this.runtime.toolSupport;
|
|
@@ -561,6 +637,8 @@ export class WebRepl {
|
|
|
561
637
|
await this.applySessionToolOverrides(await support.resolveSession(sessionId), false);
|
|
562
638
|
}
|
|
563
639
|
async setSessionPlugins(value) {
|
|
640
|
+
if (this.busy)
|
|
641
|
+
return actionFailure("PLUGIN_UPDATE_BLOCKED", "cannot change session plugins while a response is running");
|
|
564
642
|
if (!isRecord(value))
|
|
565
643
|
return actionFailure("INVALID_REQUEST", "plugin overrides must be an object");
|
|
566
644
|
const support = this.runtime.pluginSupport;
|
|
@@ -624,13 +702,11 @@ export class WebRepl {
|
|
|
624
702
|
if (!support)
|
|
625
703
|
return;
|
|
626
704
|
support.sessionOverrides = normalizeToolOverrides(overrides, support.catalog);
|
|
705
|
+
this.syncPluginToolRegistration();
|
|
706
|
+
applyToolOverrides(this.runtime.tools, support.catalog, support.globalOverrides, support.sessionOverrides);
|
|
627
707
|
const sessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
628
708
|
if (persist && sessionId && support.persistSession)
|
|
629
709
|
await support.persistSession(sessionId, support.sessionOverrides);
|
|
630
|
-
if (this.busy)
|
|
631
|
-
this.pendingRuntimeConfiguration = true;
|
|
632
|
-
else
|
|
633
|
-
await this.applyRuntimeConfiguration();
|
|
634
710
|
await this.refreshToolConfiguration();
|
|
635
711
|
}
|
|
636
712
|
syncPluginToolRegistration() {
|
|
@@ -645,18 +721,6 @@ export class WebRepl {
|
|
|
645
721
|
this.runtime.tools.register(tool);
|
|
646
722
|
}
|
|
647
723
|
}
|
|
648
|
-
async applyRuntimeConfiguration() {
|
|
649
|
-
this.syncPluginToolRegistration();
|
|
650
|
-
const pluginSupport = this.runtime.pluginSupport;
|
|
651
|
-
if (pluginSupport) {
|
|
652
|
-
const activePlugins = activeWebRuntimePlugins(pluginSupport.catalog, pluginSupport.overrides);
|
|
653
|
-
this.runtime.engine.setRuntimePlugins([...pluginSupport.basePluginIds, ...activePlugins.map((plugin) => plugin.id)], [...pluginSupport.basePromptSections, ...activePlugins.flatMap((plugin) => plugin.promptSections ?? [])]);
|
|
654
|
-
}
|
|
655
|
-
const toolSupport = this.runtime.toolSupport;
|
|
656
|
-
if (toolSupport)
|
|
657
|
-
applyToolOverrides(this.runtime.tools, toolSupport.catalog, toolSupport.globalOverrides, toolSupport.sessionOverrides);
|
|
658
|
-
this.pendingRuntimeConfiguration = false;
|
|
659
|
-
}
|
|
660
724
|
async refreshToolConfiguration() {
|
|
661
725
|
const metrics = await this.runtime.engine.contextMetrics();
|
|
662
726
|
this.runtime.initialMetrics = metrics;
|
|
@@ -669,14 +733,21 @@ export class WebRepl {
|
|
|
669
733
|
if (!support)
|
|
670
734
|
return;
|
|
671
735
|
const normalized = normalizePluginOverrides(overrides, support.catalog);
|
|
736
|
+
for (const plugin of support.catalog)
|
|
737
|
+
for (const tool of plugin.tools)
|
|
738
|
+
this.runtime.tools.unregister(tool.name);
|
|
739
|
+
const activePlugins = activeWebRuntimePlugins(support.catalog, normalized);
|
|
740
|
+
for (const plugin of activePlugins)
|
|
741
|
+
for (const tool of plugin.tools)
|
|
742
|
+
this.runtime.tools.register(tool);
|
|
672
743
|
support.overrides = normalized;
|
|
744
|
+
const toolSupport = this.runtime.toolSupport;
|
|
745
|
+
if (toolSupport)
|
|
746
|
+
applyToolOverrides(this.runtime.tools, toolSupport.catalog, toolSupport.globalOverrides, toolSupport.sessionOverrides);
|
|
747
|
+
this.runtime.engine.setRuntimePlugins([...support.basePluginIds, ...activePlugins.map((plugin) => plugin.id)], [...support.basePromptSections, ...activePlugins.flatMap((plugin) => plugin.promptSections ?? [])]);
|
|
673
748
|
const sessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
674
749
|
if (persist && sessionId && support.persist)
|
|
675
750
|
await support.persist(sessionId, normalized);
|
|
676
|
-
if (this.busy)
|
|
677
|
-
this.pendingRuntimeConfiguration = true;
|
|
678
|
-
else
|
|
679
|
-
await this.applyRuntimeConfiguration();
|
|
680
751
|
const metrics = await this.runtime.engine.contextMetrics();
|
|
681
752
|
this.runtime.initialMetrics = metrics;
|
|
682
753
|
this.setStatus({ ...this.status, metrics, activityTick: this.status.activityTick + 1 });
|
|
@@ -727,11 +798,7 @@ export class WebRepl {
|
|
|
727
798
|
this.queuedAttachments = [...(this.queuedAttachments ?? []), ...attachments].slice(0, 20);
|
|
728
799
|
}
|
|
729
800
|
startRun(text, attachments = []) {
|
|
730
|
-
const run = (
|
|
731
|
-
if (this.pendingRuntimeConfiguration)
|
|
732
|
-
await this.applyRuntimeConfiguration();
|
|
733
|
-
await this.handleCommandOrPrompt(text, attachments);
|
|
734
|
-
})().catch((error) => {
|
|
801
|
+
const run = this.handleCommandOrPrompt(text, attachments).catch((error) => {
|
|
735
802
|
this.append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
736
803
|
this.setBusy(false);
|
|
737
804
|
this.setStatus({ ...this.status, phase: "ready", detail: undefined });
|
|
@@ -759,6 +826,7 @@ export class WebRepl {
|
|
|
759
826
|
await this.detachRunningForeground("session switch");
|
|
760
827
|
this.runtime.engine = this.runtime.engine.forkForSession(sessionId, true);
|
|
761
828
|
await this.runtime.engine.initialize();
|
|
829
|
+
activateSession(this.runtime);
|
|
762
830
|
const snapshot = this.runtime.engine.snapshot().session;
|
|
763
831
|
if (!snapshot)
|
|
764
832
|
throw new Error("session transcripts are disabled");
|
|
@@ -776,6 +844,7 @@ export class WebRepl {
|
|
|
776
844
|
await this.detachRunningForeground("new session");
|
|
777
845
|
this.runtime.engine = this.runtime.engine.forkForSession(undefined, false);
|
|
778
846
|
await this.runtime.engine.initialize();
|
|
847
|
+
activateSession(this.runtime);
|
|
779
848
|
const snapshot = this.runtime.engine.snapshot().session;
|
|
780
849
|
if (!snapshot)
|
|
781
850
|
throw new Error("session transcripts are disabled");
|
|
@@ -892,6 +961,8 @@ export class WebRepl {
|
|
|
892
961
|
}
|
|
893
962
|
}
|
|
894
963
|
async setContextWindowK(value) {
|
|
964
|
+
if (this.busy)
|
|
965
|
+
return actionFailure("CONTEXT_WINDOW_UPDATE_BLOCKED", "cannot change context window while a response is running");
|
|
895
966
|
if (typeof value !== "string" || !/^\d+$/u.test(value) || Number(value) <= 0) {
|
|
896
967
|
return actionFailure("CONTEXT_WINDOW_INVALID", "context window must be a positive integer in k");
|
|
897
968
|
}
|
|
@@ -1017,24 +1088,77 @@ export class WebRepl {
|
|
|
1017
1088
|
this.broadcastSync();
|
|
1018
1089
|
return runWasActive;
|
|
1019
1090
|
}
|
|
1091
|
+
agentTaskSnapshot(task) {
|
|
1092
|
+
const terminal = this.runtime.taskStore.isTerminal(task);
|
|
1093
|
+
// Redact complete values before any public preview boundary: clipping first can expose
|
|
1094
|
+
// a credential prefix which no longer matches the runtime registry's full value.
|
|
1095
|
+
const safeText = (value) => value === undefined ? undefined : this.runtime.engine.redactDisplayValue(value);
|
|
1096
|
+
const result = (value, limit = 1500) => {
|
|
1097
|
+
if (!value)
|
|
1098
|
+
return undefined;
|
|
1099
|
+
if (value.displaySource !== "agent_report" && value.displaySource !== "visible_text") {
|
|
1100
|
+
return { content: "", truncated: false, status: value.status, availability: "unavailable", reason: "旧报告缺少获准展示来源;不会公开未验证的文本" };
|
|
1101
|
+
}
|
|
1102
|
+
const content = safeText(value.content);
|
|
1103
|
+
return { content: content.slice(0, limit), truncated: content.length > limit, status: value.status };
|
|
1104
|
+
};
|
|
1105
|
+
const error = safeText(task.error);
|
|
1106
|
+
return this.runtime.engine.redactDisplayValue({
|
|
1107
|
+
kind: "agent", taskId: task.taskId, agentId: task.agentId,
|
|
1108
|
+
agentType: task.agentType, type: task.type, status: task.status,
|
|
1109
|
+
description: task.description, runGeneration: task.runGeneration ?? 1,
|
|
1110
|
+
progress: { visibleText: task.progress.visibleText?.channel === "visible" && task.progress.visibleText.redactionVersion === 1 && task.progress.visibleText.runGeneration === task.runGeneration ? this.runtime.engine.redactDisplayValue(task.progress.visibleText) : undefined, lastActivity: task.progress.lastActivity, currentAction: task.progress.currentAction, totalToolUseCount: task.progress.totalToolUseCount, steps: task.progress.steps?.slice(-8).map((step) => ({ id: step.id, title: step.title, status: step.status })) },
|
|
1111
|
+
pendingMessageCount: task.pendingMessages.length,
|
|
1112
|
+
deliveredRetainedThisRun: task.messageReceipts?.filter((receipt) => receipt.status === "delivered" && receipt.runGeneration === task.runGeneration).length,
|
|
1113
|
+
messageReceipts: (task.messageReceipts ?? []).slice(-20).map(({ id, status, queuedAt, deliveredAt, runGeneration }) => ({ id, status, queuedAt, deliveredAt, runGeneration })),
|
|
1114
|
+
result: terminal ? result(task.result) : undefined,
|
|
1115
|
+
error: terminal ? error?.slice(0, 1500) : undefined,
|
|
1116
|
+
errorTruncated: terminal && (error?.length ?? 0) > 1500,
|
|
1117
|
+
runHistory: (task.runHistory ?? []).map((run) => {
|
|
1118
|
+
const archivedError = safeText(run.error);
|
|
1119
|
+
return { runGeneration: run.runGeneration, status: run.status, startedAt: run.startedAt, durationMs: run.durationMs, completedAt: run.completedAt, result: result(run.result, 600), error: archivedError?.slice(0, 600), errorTruncated: (archivedError?.length ?? 0) > 600 };
|
|
1120
|
+
}),
|
|
1121
|
+
createdAt: task.createdAt, updatedAt: task.updatedAt, startedAt: task.startedAt, completedAt: task.completedAt, durationMs: task.durationMs,
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
terminalTaskHistory() {
|
|
1125
|
+
const session = this.runtime.engine.snapshot().session;
|
|
1126
|
+
if (!session?.sessionId || !session.sessionDir)
|
|
1127
|
+
return [];
|
|
1128
|
+
const manager = this.runtime.execProcessManager;
|
|
1129
|
+
if (!manager.registerOwnerSession?.(session.sessionId, session.sessionDir).ok)
|
|
1130
|
+
return [];
|
|
1131
|
+
const records = [];
|
|
1132
|
+
let offset = 0;
|
|
1133
|
+
do {
|
|
1134
|
+
const history = manager.listHistory(session.sessionId, { offset, limit: 200 });
|
|
1135
|
+
if (!history.ok)
|
|
1136
|
+
break;
|
|
1137
|
+
records.push(...history.value.records);
|
|
1138
|
+
if (history.value.nextOffset === null)
|
|
1139
|
+
break;
|
|
1140
|
+
offset = history.value.nextOffset;
|
|
1141
|
+
} while (true);
|
|
1142
|
+
return records.reverse().map(({ record }) => ({
|
|
1143
|
+
kind: "terminal", taskId: `terminal:${record.runId}`, type: "终端",
|
|
1144
|
+
ownerSessionId: session.sessionId, sessionId: record.runId,
|
|
1145
|
+
status: record.lifecycle === "lost" ? "lost" : record.exit?.status ?? "unknown",
|
|
1146
|
+
description: record.metadata.description || record.metadata.command || "终端运行",
|
|
1147
|
+
command: record.metadata.command, cwd: record.metadata.cwd, shell: record.metadata.shell,
|
|
1148
|
+
tty: record.metadata.tty, processId: record.metadata.processId,
|
|
1149
|
+
createdAt: record.metadata.startedAt, completedAt: record.exit?.finishedAt,
|
|
1150
|
+
durationMs: record.exit?.durationMs, exitCode: record.exit?.exitCode,
|
|
1151
|
+
signal: record.exit?.signal, terminationReason: record.exit?.terminationReason,
|
|
1152
|
+
outputAvailability: record.availability, outputTruncated: record.truncated,
|
|
1153
|
+
metadataTruncated: record.metadata.truncatedFields,
|
|
1154
|
+
})).map(task => this.runtime.engine.redactDisplayValue(task));
|
|
1155
|
+
}
|
|
1020
1156
|
backgroundTasks() {
|
|
1021
|
-
const agentTasks = this.runtime
|
|
1157
|
+
const agentTasks = sessionAgentTasks(this.runtime)
|
|
1022
1158
|
.filter((task) => !this.runtime.taskStore.isTerminal(task))
|
|
1023
|
-
.map((task) => (
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
agentId: task.agentId,
|
|
1027
|
-
agentType: task.agentType,
|
|
1028
|
-
type: task.type,
|
|
1029
|
-
status: task.status,
|
|
1030
|
-
description: task.description,
|
|
1031
|
-
prompt: task.prompt,
|
|
1032
|
-
progress: task.progress,
|
|
1033
|
-
outputFile: task.outputFile,
|
|
1034
|
-
createdAt: task.createdAt,
|
|
1035
|
-
updatedAt: task.updatedAt,
|
|
1036
|
-
}));
|
|
1037
|
-
const terminalTasks = this.runtime.execProcessManager.list()
|
|
1159
|
+
.map((task) => this.agentTaskSnapshot(task));
|
|
1160
|
+
const ownerSessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
1161
|
+
const terminalTasks = (ownerSessionId ? this.runtime.execProcessManager.list(ownerSessionId) : [])
|
|
1038
1162
|
.filter((terminal) => terminal.status === "running" && terminal.backgrounded)
|
|
1039
1163
|
.map((terminal) => ({
|
|
1040
1164
|
kind: "terminal",
|
|
@@ -1042,7 +1166,8 @@ export class WebRepl {
|
|
|
1042
1166
|
type: "终端",
|
|
1043
1167
|
status: terminal.status,
|
|
1044
1168
|
description: terminal.description || terminal.command,
|
|
1045
|
-
createdAt:
|
|
1169
|
+
createdAt: terminal.started_at,
|
|
1170
|
+
ownerSessionId,
|
|
1046
1171
|
sessionId: terminal.session_id,
|
|
1047
1172
|
processId: terminal.process_id,
|
|
1048
1173
|
command: terminal.command,
|
|
@@ -1050,8 +1175,11 @@ export class WebRepl {
|
|
|
1050
1175
|
shell: terminal.shell,
|
|
1051
1176
|
tty: terminal.tty,
|
|
1052
1177
|
durationMs: terminal.duration_ms,
|
|
1053
|
-
|
|
1054
|
-
|
|
1178
|
+
// Output is loaded from the owner-bound reader, never copied into state snapshots.
|
|
1179
|
+
exitCode: terminal.exit_code,
|
|
1180
|
+
signal: terminal.signal,
|
|
1181
|
+
terminationReason: terminal.termination_reason,
|
|
1182
|
+
outputAvailability: terminal.output_ref.availability,
|
|
1055
1183
|
}));
|
|
1056
1184
|
const sessionTasks = [...this.backgroundSessionRuns.values()].map((run) => ({
|
|
1057
1185
|
kind: "session",
|
|
@@ -1108,6 +1236,7 @@ export class WebRepl {
|
|
|
1108
1236
|
await this.detachRunningForeground("session switch");
|
|
1109
1237
|
this.backgroundSessionRuns.delete(run.sessionId);
|
|
1110
1238
|
this.runtime.engine = run.engine;
|
|
1239
|
+
activateSession(this.runtime);
|
|
1111
1240
|
this.activeAbortController = run.abortController;
|
|
1112
1241
|
this.interruptArmed = false;
|
|
1113
1242
|
this.foregroundRun = run.promise;
|
|
@@ -1260,7 +1389,7 @@ export class WebRepl {
|
|
|
1260
1389
|
return liveLineId;
|
|
1261
1390
|
}, runtimeToolResultPresenter(this.runtime));
|
|
1262
1391
|
}
|
|
1263
|
-
const steps = (liveLine.toolStream?.steps ?? []).map((step) => step.status === "running" ? { ...step, status:
|
|
1392
|
+
const steps = (liveLine.toolStream?.steps ?? []).map((step) => step.status === "running" ? { ...step, status: "unknown" } : step);
|
|
1264
1393
|
if (replacement) {
|
|
1265
1394
|
this.replaceLine(liveLineId, {
|
|
1266
1395
|
...replacement,
|
|
@@ -1430,6 +1559,7 @@ export class WebRepl {
|
|
|
1430
1559
|
return void this.append({ kind: "system", text: formatUsageTotals(this.runtime.usage.snapshot()), previewStyle: "summary" });
|
|
1431
1560
|
if (command.type === "reset") {
|
|
1432
1561
|
this.runtime.engine.reset();
|
|
1562
|
+
activateSession(this.runtime);
|
|
1433
1563
|
this.runtime.usage.reset();
|
|
1434
1564
|
this.status = await resetStatus(this.runtime);
|
|
1435
1565
|
this.append(systemLine("transcript reset"));
|
|
@@ -1684,6 +1814,10 @@ export class WebRepl {
|
|
|
1684
1814
|
this.queueDeltaOperation(operation);
|
|
1685
1815
|
}
|
|
1686
1816
|
queueDeltaOperation(operation) {
|
|
1817
|
+
if (operation.type === "line.append" && operation.line.toolError)
|
|
1818
|
+
operation = { ...operation, line: { ...operation.line, toolError: this.runtime.engine.redactDisplayValue?.(operation.line.toolError) ?? operation.line.toolError } };
|
|
1819
|
+
if (operation.type === "line.patch" && operation.patch.toolError)
|
|
1820
|
+
operation = { ...operation, patch: { ...operation.patch, toolError: this.runtime.engine.redactDisplayValue?.(operation.patch.toolError) ?? operation.patch.toolError } };
|
|
1687
1821
|
this.pendingDeltaOperations.push(operation);
|
|
1688
1822
|
this.scheduleDeltaFlush();
|
|
1689
1823
|
}
|
|
@@ -1727,18 +1861,11 @@ export class WebRepl {
|
|
|
1727
1861
|
}
|
|
1728
1862
|
}
|
|
1729
1863
|
queueTerminalOutput(delta) {
|
|
1730
|
-
|
|
1864
|
+
const ownerSessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
1865
|
+
if (!this.subscribers.size || !ownerSessionId || delta.ownerSessionId !== ownerSessionId)
|
|
1731
1866
|
return;
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
const pending = this.pendingTerminalOutput.get(delta.sessionId);
|
|
1735
|
-
if (pending && pending.outputEnd === outputStart) {
|
|
1736
|
-
pending.text = appendBoundedText(pending.text, delta.text, TERMINAL_OUTPUT_EVENT_MAX_CHARS);
|
|
1737
|
-
pending.outputEnd = outputEnd;
|
|
1738
|
-
}
|
|
1739
|
-
else {
|
|
1740
|
-
this.pendingTerminalOutput.set(delta.sessionId, { text: delta.text.slice(-TERMINAL_OUTPUT_EVENT_MAX_CHARS), outputStart, outputEnd });
|
|
1741
|
-
}
|
|
1867
|
+
// Invalidation only. The reader owns independent stream cursors; SSE is not a full-output source.
|
|
1868
|
+
this.pendingTerminalOutput.set(delta.sessionId, { ownerSessionId });
|
|
1742
1869
|
if (this.terminalOutputTimer)
|
|
1743
1870
|
return;
|
|
1744
1871
|
this.terminalOutputTimer = setTimeout(() => this.flushTerminalOutput(), TERMINAL_OUTPUT_FLUSH_MS);
|
|
@@ -1746,12 +1873,12 @@ export class WebRepl {
|
|
|
1746
1873
|
}
|
|
1747
1874
|
flushTerminalOutput() {
|
|
1748
1875
|
this.terminalOutputTimer = undefined;
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
const updates = [...this.pendingTerminalOutput.entries()].map(([sessionId, output]) => ({ sessionId, ...output }));
|
|
1876
|
+
const ownerSessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
1877
|
+
const updates = [...this.pendingTerminalOutput.entries()].filter(([, output]) => output.ownerSessionId === ownerSessionId).map(([sessionId, output]) => ({ sessionId, ...output, invalidated: true }));
|
|
1752
1878
|
this.pendingTerminalOutput.clear();
|
|
1753
|
-
|
|
1754
|
-
|
|
1879
|
+
if (updates.length)
|
|
1880
|
+
for (const subscriber of this.subscribers)
|
|
1881
|
+
this.send(subscriber, "terminal.output", { updates });
|
|
1755
1882
|
}
|
|
1756
1883
|
broadcastSync() {
|
|
1757
1884
|
this.discardPendingDeltaOperations();
|
|
@@ -1802,10 +1929,38 @@ async function route(req, res, router) {
|
|
|
1802
1929
|
const body = await readJsonBody(req);
|
|
1803
1930
|
return sendJson(res, await router.requestClientReload(body));
|
|
1804
1931
|
}
|
|
1932
|
+
if (["/api/tool-call-detail", "/api/terminal-output", "/api/agent-content"].includes(url.pathname) && !allowToolDetailRequest(req.headers.origin, req.headers.host, req.headers["sec-fetch-site"])) {
|
|
1933
|
+
return sendJson(res, { error: "详情访问被拒绝" }, 403);
|
|
1934
|
+
}
|
|
1805
1935
|
const scope = webRuntimeScopeFromUrl(url);
|
|
1806
1936
|
const repl = await router.get(scope);
|
|
1807
1937
|
if (req.method === "GET" && url.pathname === "/events")
|
|
1808
1938
|
return repl.subscribe(res, router.clientInfo());
|
|
1939
|
+
if (req.method === "GET" && url.pathname === "/api/tool-call-detail") {
|
|
1940
|
+
res.setHeader("Cache-Control", "no-store");
|
|
1941
|
+
const detail = await repl.toolCallDetail(url.searchParams.get("sessionId") ?? "", url.searchParams.get("toolUseId") ?? "", url.searchParams.get("messageId") ?? undefined);
|
|
1942
|
+
if (!detail)
|
|
1943
|
+
return sendJson(res, { error: "调用详情不可获取或无权访问" }, 404);
|
|
1944
|
+
return sendJson(res, detail);
|
|
1945
|
+
}
|
|
1946
|
+
if (req.method === "GET" && url.pathname === "/api/agent-content") {
|
|
1947
|
+
res.setHeader("Cache-Control", "no-store");
|
|
1948
|
+
const refresh = url.searchParams.get("refresh");
|
|
1949
|
+
if (refresh !== null && refresh !== "true" && refresh !== "false")
|
|
1950
|
+
return sendJson(res, { error: "代理详情参数无效" }, 404);
|
|
1951
|
+
const page = await repl.agentContent(url.searchParams.get("sessionId") ?? "", url.searchParams.get("view") ?? "timeline", {
|
|
1952
|
+
taskId: url.searchParams.get("taskId") ?? "", runGeneration: Number(url.searchParams.get("runGeneration")),
|
|
1953
|
+
...(url.searchParams.has("cursor") ? { cursor: url.searchParams.get("cursor") } : {}),
|
|
1954
|
+
...(url.searchParams.has("pageChars") ? { pageChars: Number(url.searchParams.get("pageChars")) } : {}),
|
|
1955
|
+
...(url.searchParams.has("refresh") ? { refresh: url.searchParams.get("refresh") === "true" } : {}),
|
|
1956
|
+
});
|
|
1957
|
+
return page ? sendJson(res, page) : sendJson(res, { error: "代理内容不可获取或无权访问" }, 404);
|
|
1958
|
+
}
|
|
1959
|
+
if (req.method === "GET" && url.pathname === "/api/terminal-output") {
|
|
1960
|
+
res.setHeader("Cache-Control", "no-store");
|
|
1961
|
+
const page = repl.terminalOutput(url.searchParams.get("sessionId") ?? "", url.searchParams.get("runId") ?? "", url.searchParams.get("stream") ?? "stdout", Number(url.searchParams.get("offset") ?? 0), Number(url.searchParams.get("limitBytes") ?? 65536));
|
|
1962
|
+
return page ? sendJson(res, page) : sendJson(res, { error: "终端输出不可获取或无权访问" }, 404);
|
|
1963
|
+
}
|
|
1809
1964
|
if (req.method === "GET" && url.pathname === "/api/state")
|
|
1810
1965
|
return sendJson(res, repl.snapshot(true));
|
|
1811
1966
|
if (req.method === "GET" && url.pathname === "/api/runtime-context")
|
|
@@ -2727,13 +2882,19 @@ function formatToolUse(toolUse) {
|
|
|
2727
2882
|
return { kind: "tool", toolName: toolUse.name, toolPresentation, toolUseId: toolUse.id, title: toolPresentation.label, bodyTitle: summary.bodyTitle, text: summary.text, toolDisplay: buildToolUseDisplay(toolUse.name, toolUse.input), previewStyle: "summary", presentationLevel, collapsible: true };
|
|
2728
2883
|
}
|
|
2729
2884
|
function formatToolResultLine(toolName, output, ok, presentToolResult) {
|
|
2885
|
+
// Hidden fallback reports in historical agent payloads must not enter line.text
|
|
2886
|
+
// even when the frontend suppresses the ordinary result preview.
|
|
2887
|
+
const authorized = sanitizeAgentToolPayload(toolName, output);
|
|
2888
|
+
if (toolName.startsWith("subagent_"))
|
|
2889
|
+
output = authorized.value;
|
|
2730
2890
|
const presentation = normalizeToolResultPresentation(presentToolResult?.(toolName, output, ok));
|
|
2731
2891
|
const resources = presentation?.resources ?? toolResources(output);
|
|
2732
2892
|
const formatted = resources?.length
|
|
2733
2893
|
? { text: ok ? "资源已准备好。" : "资源准备失败。", full: true, bodyTitle: ok ? "资源已就绪" : "资源准备失败" }
|
|
2734
|
-
: formatToolResult(toolName, output, ok);
|
|
2894
|
+
: formatToolResult(toolName, toolName.startsWith("subagent_") ? redactToolDetail(output) : output, ok);
|
|
2735
2895
|
return {
|
|
2736
2896
|
kind: ok ? "tool" : "error",
|
|
2897
|
+
toolError: ok ? undefined : toolErrorText(output, ok).slice(0, 2000),
|
|
2737
2898
|
toolName,
|
|
2738
2899
|
toolPresentation: toolPresentationForSource(toolName),
|
|
2739
2900
|
title: presentation?.title ?? toolTitle(toolName, "finished"),
|
|
@@ -2920,12 +3081,16 @@ function normalizeToolDisplay(display) {
|
|
|
2920
3081
|
const visibleText = new Set([subject, purpose].filter((value) => Boolean(value)).map(normalizeDisplayText));
|
|
2921
3082
|
const facts = display.facts.filter((fact) => {
|
|
2922
3083
|
const normalized = normalizeDisplayText(fact.value);
|
|
2923
|
-
|
|
3084
|
+
const identity = `${fact.label}\0${normalized}`;
|
|
3085
|
+
if (!normalized || visibleText.has(identity))
|
|
2924
3086
|
return false;
|
|
2925
|
-
visibleText.add(
|
|
3087
|
+
visibleText.add(identity);
|
|
2926
3088
|
return true;
|
|
2927
3089
|
});
|
|
2928
3090
|
const previews = display.previews.filter((preview) => {
|
|
3091
|
+
// Keep identical nonempty streams, but never render empty output panels.
|
|
3092
|
+
if (/^(stdout|stderr)(?:$|[( ·])/.test(preview.label ?? ""))
|
|
3093
|
+
return !!preview.content.trim();
|
|
2929
3094
|
const normalized = normalizeDisplayText(preview.content);
|
|
2930
3095
|
if (!normalized || visibleText.has(normalized))
|
|
2931
3096
|
return false;
|
|
@@ -2959,8 +3124,10 @@ function buildToolUseDisplay(toolName, input) {
|
|
|
2959
3124
|
}
|
|
2960
3125
|
else if (toolName === "expose_downloads")
|
|
2961
3126
|
pushToolFact(facts, "文件", arrayLabel(data.paths), true);
|
|
2962
|
-
else if (toolName === "subagent_message")
|
|
2963
|
-
|
|
3127
|
+
else if (toolName === "subagent_message") {
|
|
3128
|
+
if (typeof data.message === "string" && data.message.trim())
|
|
3129
|
+
previews.push({ kind: "text", content: data.message });
|
|
3130
|
+
}
|
|
2964
3131
|
else if (toolName.startsWith("subagent_"))
|
|
2965
3132
|
pushToolFact(facts, "任务", data.task_id, true);
|
|
2966
3133
|
else if (toolName.startsWith("secret_")) {
|
|
@@ -2974,7 +3141,7 @@ function buildToolUseDisplay(toolName, input) {
|
|
|
2974
3141
|
return normalizeToolDisplay({ subject, purpose, facts, previews });
|
|
2975
3142
|
}
|
|
2976
3143
|
function buildToolResultDisplay(toolName, output, ok) {
|
|
2977
|
-
const data = isRecord(output) ? output : {};
|
|
3144
|
+
const data = toolName.startsWith("subagent_") ? subagentHeader(output) : isRecord(output) ? output : {};
|
|
2978
3145
|
const facts = [];
|
|
2979
3146
|
const previews = [];
|
|
2980
3147
|
let subject = toolDisplaySubject(toolName, data);
|
|
@@ -2987,13 +3154,8 @@ function buildToolResultDisplay(toolName, output, ok) {
|
|
|
2987
3154
|
subject = undefined;
|
|
2988
3155
|
purpose = stringValue(data.description);
|
|
2989
3156
|
pushToolPreview(previews, "命令", "code", data.command);
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
pushToolPreview(previews, data.stderr ? "输出 / 错误" : "输出", "code", outputText);
|
|
2993
|
-
if (data.timed_out && !outputText)
|
|
2994
|
-
pushToolFact(facts, "状态", "命令执行超时", false, "warning");
|
|
2995
|
-
else if ((!ok || (typeof data.exit_code === "number" && data.exit_code !== 0)) && !outputText)
|
|
2996
|
-
pushToolFact(facts, "状态", `退出码 ${data.exit_code ?? "未知"}`, false, "danger");
|
|
3157
|
+
previews.push(...terminalPreviews(data));
|
|
3158
|
+
facts.push(...terminalFacts(data));
|
|
2997
3159
|
}
|
|
2998
3160
|
else if (toolName === "file_search") {
|
|
2999
3161
|
subject = stringValue(data.query) || stringValue(data.grepPath) || stringValue(data.path);
|
|
@@ -3016,7 +3178,7 @@ function buildToolResultDisplay(toolName, output, ok) {
|
|
|
3016
3178
|
else if (toolName === "image_create") {
|
|
3017
3179
|
subject = stringValue(data.semanticName);
|
|
3018
3180
|
pushToolFact(facts, "图片", `${numberValue(data.returnedImages) ?? arrayLength(data.images)} 张`);
|
|
3019
|
-
pushToolFact(facts, "
|
|
3181
|
+
pushToolFact(facts, "请求尺寸", data.size === "auto" ? "auto(自动策略)" : data.size);
|
|
3020
3182
|
}
|
|
3021
3183
|
else if (toolName === "image_inspect") {
|
|
3022
3184
|
subject = arrayLabel(data.imageRefs);
|
|
@@ -3026,6 +3188,9 @@ function buildToolResultDisplay(toolName, output, ok) {
|
|
|
3026
3188
|
}
|
|
3027
3189
|
else if (toolName.startsWith("subagent_") || toolName === "subagent_run") {
|
|
3028
3190
|
subject = stringValue(data.description) || stringValue(data.task_id) || stringValue(data.agent_id);
|
|
3191
|
+
facts.push(...subagentStatusFacts(toolName, output, ok));
|
|
3192
|
+
pushToolFact(facts, "轮次", data.run_generation);
|
|
3193
|
+
pushToolFact(facts, "待交付", data.pending_messages);
|
|
3029
3194
|
}
|
|
3030
3195
|
else if (toolName === "subagent_message") {
|
|
3031
3196
|
subject = stringValue(data.target);
|
|
@@ -3209,21 +3374,8 @@ function diffLineMarker(line) {
|
|
|
3209
3374
|
function isExecOutput(value) {
|
|
3210
3375
|
return isRecord(value) && typeof value.command === "string" && typeof value.duration_ms === "number";
|
|
3211
3376
|
}
|
|
3212
|
-
function formatExecToolResult(output,
|
|
3213
|
-
|
|
3214
|
-
const description = typeof output.description === "string" ? output.description.trim() : "";
|
|
3215
|
-
const lines = [description ? `目的:${description}` : "执行命令", `状态:${status}`, `耗时:${output.duration_ms}ms`];
|
|
3216
|
-
if (output.status === "running" && typeof output.session_id === "string")
|
|
3217
|
-
lines.push(`会话:${output.session_id}`);
|
|
3218
|
-
const stdout = typeof output.stdout === "string" ? output.stdout.replace(/\s+$/u, "") : "";
|
|
3219
|
-
const stderr = typeof output.stderr === "string" ? output.stderr.replace(/\s+$/u, "") : "";
|
|
3220
|
-
if (stdout)
|
|
3221
|
-
lines.push("输出:", stdout);
|
|
3222
|
-
if (stderr)
|
|
3223
|
-
lines.push("错误:", stderr);
|
|
3224
|
-
if (!stdout && !stderr)
|
|
3225
|
-
lines.push(ok ? "无输出。" : "没有捕获到输出。");
|
|
3226
|
-
return lines.join("\n");
|
|
3377
|
+
function formatExecToolResult(output, _ok) {
|
|
3378
|
+
return terminalResultText(output);
|
|
3227
3379
|
}
|
|
3228
3380
|
function formatImageGenerationToolResult(output, ok) {
|
|
3229
3381
|
const error = typeof output.error === "string" ? output.error : undefined;
|