neoctl 0.2.36 → 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.js +9 -3
- package/dist/agents/agent-tool-persistence.test.js.map +1 -1
- package/dist/agents/agent-tool.d.ts +3 -0
- package/dist/agents/agent-tool.js +127 -34
- 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 +27 -1
- package/dist/agents/local-agent-task.js +75 -5
- package/dist/agents/local-agent-task.js.map +1 -1
- 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.js +8 -4
- package/dist/agents/smoke-agent-lifecycle.js.map +1 -1
- 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 +35 -2
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +2 -0
- package/dist/core/query.js +104 -15
- package/dist/core/query.js.map +1 -1
- package/dist/core/run-agent.d.ts +2 -0
- package/dist/core/run-agent.js +20 -10
- package/dist/core/run-agent.js.map +1 -1
- package/dist/model/model-gateway.d.ts +1 -0
- package/dist/model/model-metadata.json +99 -99
- 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 +2 -2
- 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.d.ts +4 -1
- package/dist/session/session-store.js +5 -2
- package/dist/session/session-store.js.map +1 -1
- package/dist/tasks/subagent-tools.test.js +1 -0
- package/dist/tasks/subagent-tools.test.js.map +1 -1
- package/dist/tasks/task-ack-size.test.js +6 -5
- package/dist/tasks/task-ack-size.test.js.map +1 -1
- package/dist/tasks/task-persistence.d.ts +1 -0
- package/dist/tasks/task-persistence.js +31 -9
- package/dist/tasks/task-persistence.js.map +1 -1
- package/dist/tasks/task-store.d.ts +6 -4
- package/dist/tasks/task-store.js +46 -20
- 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/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 +114 -3
- package/dist/web/index.js +182 -67
- 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.js +22 -1
- package/dist/web/smoke-task-session-entrypoints.js.map +1 -1
- 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-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 +93 -93
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";
|
|
@@ -274,7 +279,15 @@ function applyToolOverrides(registry, catalog, globalOverrides, sessionOverrides
|
|
|
274
279
|
}
|
|
275
280
|
/** Bind only the foreground view; never stop work owned by another session. */
|
|
276
281
|
export function activateSession(runtime) {
|
|
277
|
-
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
|
+
}
|
|
278
291
|
}
|
|
279
292
|
export function sessionAgentTasks(runtime) {
|
|
280
293
|
const dir = runtime.engine.snapshot().session?.sessionDir || undefined;
|
|
@@ -390,6 +403,7 @@ function webRuntimeScopeKey(scope) {
|
|
|
390
403
|
}
|
|
391
404
|
export class WebRepl {
|
|
392
405
|
runtime;
|
|
406
|
+
agentContentReader = createAgentContentDetailResolver();
|
|
393
407
|
subscribers = new Set();
|
|
394
408
|
eventSequence = 0;
|
|
395
409
|
pendingDeltaOperations = [];
|
|
@@ -458,19 +472,53 @@ export class WebRepl {
|
|
|
458
472
|
this.send(subscriber, "client.reload", payload);
|
|
459
473
|
return subscribers.length;
|
|
460
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
|
+
}
|
|
461
506
|
snapshot(includeCatalog = false) {
|
|
462
507
|
const backgroundTasks = this.backgroundTasks();
|
|
508
|
+
const engineSnapshot = this.runtime.engine.snapshot();
|
|
463
509
|
return {
|
|
464
|
-
lines: this.lines,
|
|
510
|
+
lines: this.lines.map(line => line.toolError ? { ...line, toolError: this.runtime.engine.redactDisplayValue?.(line.toolError) ?? line.toolError } : line),
|
|
465
511
|
status: this.status,
|
|
466
512
|
busy: this.busy,
|
|
467
513
|
queuedInput: this.queuedInput,
|
|
468
514
|
backgroundTaskCount: backgroundTasks.length,
|
|
469
515
|
backgroundTasks,
|
|
470
|
-
|
|
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)),
|
|
471
518
|
backgroundSessionRunCount: this.backgroundSessionRuns.size,
|
|
472
519
|
runningSessionIds: [...this.backgroundSessionRuns.keys()],
|
|
473
|
-
session:
|
|
520
|
+
session: engineSnapshot.session,
|
|
521
|
+
modelSettings: { model: engineSnapshot.model, reasoning: engineSnapshot.reasoning },
|
|
474
522
|
fastMode: this.runtime.engine.isFastMode(),
|
|
475
523
|
appPrompt: this.runtime.engine.getAppPrompt(),
|
|
476
524
|
catalog: includeCatalog ? webCatalog(this.runtime) : undefined,
|
|
@@ -1042,27 +1090,75 @@ export class WebRepl {
|
|
|
1042
1090
|
}
|
|
1043
1091
|
agentTaskSnapshot(task) {
|
|
1044
1092
|
const terminal = this.runtime.taskStore.isTerminal(task);
|
|
1045
|
-
|
|
1046
|
-
|
|
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({
|
|
1047
1107
|
kind: "agent", taskId: task.taskId, agentId: task.agentId,
|
|
1048
1108
|
agentType: task.agentType, type: task.type, status: task.status,
|
|
1049
1109
|
description: task.description, runGeneration: task.runGeneration ?? 1,
|
|
1050
|
-
progress: { 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 })) },
|
|
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 })) },
|
|
1051
1111
|
pendingMessageCount: task.pendingMessages.length,
|
|
1052
1112
|
deliveredRetainedThisRun: task.messageReceipts?.filter((receipt) => receipt.status === "delivered" && receipt.runGeneration === task.runGeneration).length,
|
|
1053
1113
|
messageReceipts: (task.messageReceipts ?? []).slice(-20).map(({ id, status, queuedAt, deliveredAt, runGeneration }) => ({ id, status, queuedAt, deliveredAt, runGeneration })),
|
|
1054
1114
|
result: terminal ? result(task.result) : undefined,
|
|
1055
|
-
error: terminal ?
|
|
1056
|
-
errorTruncated: terminal && (
|
|
1057
|
-
runHistory: (task.runHistory ?? []).
|
|
1058
|
-
|
|
1059
|
-
|
|
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));
|
|
1060
1155
|
}
|
|
1061
1156
|
backgroundTasks() {
|
|
1062
1157
|
const agentTasks = sessionAgentTasks(this.runtime)
|
|
1063
1158
|
.filter((task) => !this.runtime.taskStore.isTerminal(task))
|
|
1064
1159
|
.map((task) => this.agentTaskSnapshot(task));
|
|
1065
|
-
const
|
|
1160
|
+
const ownerSessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
1161
|
+
const terminalTasks = (ownerSessionId ? this.runtime.execProcessManager.list(ownerSessionId) : [])
|
|
1066
1162
|
.filter((terminal) => terminal.status === "running" && terminal.backgrounded)
|
|
1067
1163
|
.map((terminal) => ({
|
|
1068
1164
|
kind: "terminal",
|
|
@@ -1070,7 +1166,8 @@ export class WebRepl {
|
|
|
1070
1166
|
type: "终端",
|
|
1071
1167
|
status: terminal.status,
|
|
1072
1168
|
description: terminal.description || terminal.command,
|
|
1073
|
-
createdAt:
|
|
1169
|
+
createdAt: terminal.started_at,
|
|
1170
|
+
ownerSessionId,
|
|
1074
1171
|
sessionId: terminal.session_id,
|
|
1075
1172
|
processId: terminal.process_id,
|
|
1076
1173
|
command: terminal.command,
|
|
@@ -1078,8 +1175,11 @@ export class WebRepl {
|
|
|
1078
1175
|
shell: terminal.shell,
|
|
1079
1176
|
tty: terminal.tty,
|
|
1080
1177
|
durationMs: terminal.duration_ms,
|
|
1081
|
-
|
|
1082
|
-
|
|
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,
|
|
1083
1183
|
}));
|
|
1084
1184
|
const sessionTasks = [...this.backgroundSessionRuns.values()].map((run) => ({
|
|
1085
1185
|
kind: "session",
|
|
@@ -1289,7 +1389,7 @@ export class WebRepl {
|
|
|
1289
1389
|
return liveLineId;
|
|
1290
1390
|
}, runtimeToolResultPresenter(this.runtime));
|
|
1291
1391
|
}
|
|
1292
|
-
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);
|
|
1293
1393
|
if (replacement) {
|
|
1294
1394
|
this.replaceLine(liveLineId, {
|
|
1295
1395
|
...replacement,
|
|
@@ -1714,6 +1814,10 @@ export class WebRepl {
|
|
|
1714
1814
|
this.queueDeltaOperation(operation);
|
|
1715
1815
|
}
|
|
1716
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 } };
|
|
1717
1821
|
this.pendingDeltaOperations.push(operation);
|
|
1718
1822
|
this.scheduleDeltaFlush();
|
|
1719
1823
|
}
|
|
@@ -1757,18 +1861,11 @@ export class WebRepl {
|
|
|
1757
1861
|
}
|
|
1758
1862
|
}
|
|
1759
1863
|
queueTerminalOutput(delta) {
|
|
1760
|
-
|
|
1864
|
+
const ownerSessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
1865
|
+
if (!this.subscribers.size || !ownerSessionId || delta.ownerSessionId !== ownerSessionId)
|
|
1761
1866
|
return;
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
const pending = this.pendingTerminalOutput.get(delta.sessionId);
|
|
1765
|
-
if (pending && pending.outputEnd === outputStart) {
|
|
1766
|
-
pending.text = appendBoundedText(pending.text, delta.text, TERMINAL_OUTPUT_EVENT_MAX_CHARS);
|
|
1767
|
-
pending.outputEnd = outputEnd;
|
|
1768
|
-
}
|
|
1769
|
-
else {
|
|
1770
|
-
this.pendingTerminalOutput.set(delta.sessionId, { text: delta.text.slice(-TERMINAL_OUTPUT_EVENT_MAX_CHARS), outputStart, outputEnd });
|
|
1771
|
-
}
|
|
1867
|
+
// Invalidation only. The reader owns independent stream cursors; SSE is not a full-output source.
|
|
1868
|
+
this.pendingTerminalOutput.set(delta.sessionId, { ownerSessionId });
|
|
1772
1869
|
if (this.terminalOutputTimer)
|
|
1773
1870
|
return;
|
|
1774
1871
|
this.terminalOutputTimer = setTimeout(() => this.flushTerminalOutput(), TERMINAL_OUTPUT_FLUSH_MS);
|
|
@@ -1776,12 +1873,12 @@ export class WebRepl {
|
|
|
1776
1873
|
}
|
|
1777
1874
|
flushTerminalOutput() {
|
|
1778
1875
|
this.terminalOutputTimer = undefined;
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
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 }));
|
|
1782
1878
|
this.pendingTerminalOutput.clear();
|
|
1783
|
-
|
|
1784
|
-
|
|
1879
|
+
if (updates.length)
|
|
1880
|
+
for (const subscriber of this.subscribers)
|
|
1881
|
+
this.send(subscriber, "terminal.output", { updates });
|
|
1785
1882
|
}
|
|
1786
1883
|
broadcastSync() {
|
|
1787
1884
|
this.discardPendingDeltaOperations();
|
|
@@ -1832,10 +1929,38 @@ async function route(req, res, router) {
|
|
|
1832
1929
|
const body = await readJsonBody(req);
|
|
1833
1930
|
return sendJson(res, await router.requestClientReload(body));
|
|
1834
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
|
+
}
|
|
1835
1935
|
const scope = webRuntimeScopeFromUrl(url);
|
|
1836
1936
|
const repl = await router.get(scope);
|
|
1837
1937
|
if (req.method === "GET" && url.pathname === "/events")
|
|
1838
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
|
+
}
|
|
1839
1964
|
if (req.method === "GET" && url.pathname === "/api/state")
|
|
1840
1965
|
return sendJson(res, repl.snapshot(true));
|
|
1841
1966
|
if (req.method === "GET" && url.pathname === "/api/runtime-context")
|
|
@@ -2757,13 +2882,19 @@ function formatToolUse(toolUse) {
|
|
|
2757
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 };
|
|
2758
2883
|
}
|
|
2759
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;
|
|
2760
2890
|
const presentation = normalizeToolResultPresentation(presentToolResult?.(toolName, output, ok));
|
|
2761
2891
|
const resources = presentation?.resources ?? toolResources(output);
|
|
2762
2892
|
const formatted = resources?.length
|
|
2763
2893
|
? { text: ok ? "资源已准备好。" : "资源准备失败。", full: true, bodyTitle: ok ? "资源已就绪" : "资源准备失败" }
|
|
2764
|
-
: formatToolResult(toolName, output, ok);
|
|
2894
|
+
: formatToolResult(toolName, toolName.startsWith("subagent_") ? redactToolDetail(output) : output, ok);
|
|
2765
2895
|
return {
|
|
2766
2896
|
kind: ok ? "tool" : "error",
|
|
2897
|
+
toolError: ok ? undefined : toolErrorText(output, ok).slice(0, 2000),
|
|
2767
2898
|
toolName,
|
|
2768
2899
|
toolPresentation: toolPresentationForSource(toolName),
|
|
2769
2900
|
title: presentation?.title ?? toolTitle(toolName, "finished"),
|
|
@@ -2950,12 +3081,16 @@ function normalizeToolDisplay(display) {
|
|
|
2950
3081
|
const visibleText = new Set([subject, purpose].filter((value) => Boolean(value)).map(normalizeDisplayText));
|
|
2951
3082
|
const facts = display.facts.filter((fact) => {
|
|
2952
3083
|
const normalized = normalizeDisplayText(fact.value);
|
|
2953
|
-
|
|
3084
|
+
const identity = `${fact.label}\0${normalized}`;
|
|
3085
|
+
if (!normalized || visibleText.has(identity))
|
|
2954
3086
|
return false;
|
|
2955
|
-
visibleText.add(
|
|
3087
|
+
visibleText.add(identity);
|
|
2956
3088
|
return true;
|
|
2957
3089
|
});
|
|
2958
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();
|
|
2959
3094
|
const normalized = normalizeDisplayText(preview.content);
|
|
2960
3095
|
if (!normalized || visibleText.has(normalized))
|
|
2961
3096
|
return false;
|
|
@@ -2989,8 +3124,10 @@ function buildToolUseDisplay(toolName, input) {
|
|
|
2989
3124
|
}
|
|
2990
3125
|
else if (toolName === "expose_downloads")
|
|
2991
3126
|
pushToolFact(facts, "文件", arrayLabel(data.paths), true);
|
|
2992
|
-
else if (toolName === "subagent_message")
|
|
2993
|
-
|
|
3127
|
+
else if (toolName === "subagent_message") {
|
|
3128
|
+
if (typeof data.message === "string" && data.message.trim())
|
|
3129
|
+
previews.push({ kind: "text", content: data.message });
|
|
3130
|
+
}
|
|
2994
3131
|
else if (toolName.startsWith("subagent_"))
|
|
2995
3132
|
pushToolFact(facts, "任务", data.task_id, true);
|
|
2996
3133
|
else if (toolName.startsWith("secret_")) {
|
|
@@ -3004,7 +3141,7 @@ function buildToolUseDisplay(toolName, input) {
|
|
|
3004
3141
|
return normalizeToolDisplay({ subject, purpose, facts, previews });
|
|
3005
3142
|
}
|
|
3006
3143
|
function buildToolResultDisplay(toolName, output, ok) {
|
|
3007
|
-
const data = isRecord(output) ? output : {};
|
|
3144
|
+
const data = toolName.startsWith("subagent_") ? subagentHeader(output) : isRecord(output) ? output : {};
|
|
3008
3145
|
const facts = [];
|
|
3009
3146
|
const previews = [];
|
|
3010
3147
|
let subject = toolDisplaySubject(toolName, data);
|
|
@@ -3017,13 +3154,8 @@ function buildToolResultDisplay(toolName, output, ok) {
|
|
|
3017
3154
|
subject = undefined;
|
|
3018
3155
|
purpose = stringValue(data.description);
|
|
3019
3156
|
pushToolPreview(previews, "命令", "code", data.command);
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
pushToolPreview(previews, data.stderr ? "输出 / 错误" : "输出", "code", outputText);
|
|
3023
|
-
if (data.timed_out && !outputText)
|
|
3024
|
-
pushToolFact(facts, "状态", "命令执行超时", false, "warning");
|
|
3025
|
-
else if ((!ok || (typeof data.exit_code === "number" && data.exit_code !== 0)) && !outputText)
|
|
3026
|
-
pushToolFact(facts, "状态", `退出码 ${data.exit_code ?? "未知"}`, false, "danger");
|
|
3157
|
+
previews.push(...terminalPreviews(data));
|
|
3158
|
+
facts.push(...terminalFacts(data));
|
|
3027
3159
|
}
|
|
3028
3160
|
else if (toolName === "file_search") {
|
|
3029
3161
|
subject = stringValue(data.query) || stringValue(data.grepPath) || stringValue(data.path);
|
|
@@ -3046,7 +3178,7 @@ function buildToolResultDisplay(toolName, output, ok) {
|
|
|
3046
3178
|
else if (toolName === "image_create") {
|
|
3047
3179
|
subject = stringValue(data.semanticName);
|
|
3048
3180
|
pushToolFact(facts, "图片", `${numberValue(data.returnedImages) ?? arrayLength(data.images)} 张`);
|
|
3049
|
-
pushToolFact(facts, "
|
|
3181
|
+
pushToolFact(facts, "请求尺寸", data.size === "auto" ? "auto(自动策略)" : data.size);
|
|
3050
3182
|
}
|
|
3051
3183
|
else if (toolName === "image_inspect") {
|
|
3052
3184
|
subject = arrayLabel(data.imageRefs);
|
|
@@ -3056,13 +3188,9 @@ function buildToolResultDisplay(toolName, output, ok) {
|
|
|
3056
3188
|
}
|
|
3057
3189
|
else if (toolName.startsWith("subagent_") || toolName === "subagent_run") {
|
|
3058
3190
|
subject = stringValue(data.description) || stringValue(data.task_id) || stringValue(data.agent_id);
|
|
3059
|
-
|
|
3191
|
+
facts.push(...subagentStatusFacts(toolName, output, ok));
|
|
3060
3192
|
pushToolFact(facts, "轮次", data.run_generation);
|
|
3061
3193
|
pushToolFact(facts, "待交付", data.pending_messages);
|
|
3062
|
-
if (toolName === "subagent_message")
|
|
3063
|
-
pushToolFact(facts, "交付说明", "仅入队,不代表已交付或完成");
|
|
3064
|
-
if (data.requires_resume === true)
|
|
3065
|
-
pushToolFact(facts, "后续", "需要显式续跑");
|
|
3066
3194
|
}
|
|
3067
3195
|
else if (toolName === "subagent_message") {
|
|
3068
3196
|
subject = stringValue(data.target);
|
|
@@ -3246,21 +3374,8 @@ function diffLineMarker(line) {
|
|
|
3246
3374
|
function isExecOutput(value) {
|
|
3247
3375
|
return isRecord(value) && typeof value.command === "string" && typeof value.duration_ms === "number";
|
|
3248
3376
|
}
|
|
3249
|
-
function formatExecToolResult(output,
|
|
3250
|
-
|
|
3251
|
-
const description = typeof output.description === "string" ? output.description.trim() : "";
|
|
3252
|
-
const lines = [description ? `目的:${description}` : "执行命令", `状态:${status}`, `耗时:${output.duration_ms}ms`];
|
|
3253
|
-
if (output.status === "running" && typeof output.session_id === "string")
|
|
3254
|
-
lines.push(`会话:${output.session_id}`);
|
|
3255
|
-
const stdout = typeof output.stdout === "string" ? output.stdout.replace(/\s+$/u, "") : "";
|
|
3256
|
-
const stderr = typeof output.stderr === "string" ? output.stderr.replace(/\s+$/u, "") : "";
|
|
3257
|
-
if (stdout)
|
|
3258
|
-
lines.push("输出:", stdout);
|
|
3259
|
-
if (stderr)
|
|
3260
|
-
lines.push("错误:", stderr);
|
|
3261
|
-
if (!stdout && !stderr)
|
|
3262
|
-
lines.push(ok ? "无输出。" : "没有捕获到输出。");
|
|
3263
|
-
return lines.join("\n");
|
|
3377
|
+
function formatExecToolResult(output, _ok) {
|
|
3378
|
+
return terminalResultText(output);
|
|
3264
3379
|
}
|
|
3265
3380
|
function formatImageGenerationToolResult(output, ok) {
|
|
3266
3381
|
const error = typeof output.error === "string" ? output.error : undefined;
|