neoctl 0.2.36 → 0.2.38
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-definition.d.ts +2 -0
- package/dist/agents/agent-definition.js +10 -0
- package/dist/agents/agent-definition.js.map +1 -1
- 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 +131 -34
- package/dist/agents/agent-tool.js.map +1 -1
- package/dist/agents/coordination-boundary.test.d.ts +1 -0
- package/dist/agents/coordination-boundary.test.js +58 -0
- package/dist/agents/coordination-boundary.test.js.map +1 -0
- 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 +5 -0
- package/dist/context/context-manager.js +14 -7
- package/dist/context/context-manager.js.map +1 -1
- package/dist/context/prompt-config.d.ts +35 -0
- package/dist/context/prompt-config.js +151 -0
- package/dist/context/prompt-config.js.map +1 -0
- package/dist/context/prompt-config.test.d.ts +1 -0
- package/dist/context/prompt-config.test.js +124 -0
- package/dist/context/prompt-config.test.js.map +1 -0
- package/dist/context/prompts.d.ts +1 -1
- package/dist/context/prompts.js +4 -27
- package/dist/context/prompts.js.map +1 -1
- package/dist/context/smoke-context.js +49 -4
- package/dist/context/smoke-context.js.map +1 -1
- package/dist/context/system.md +16 -0
- 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 +32 -1
- package/dist/core/query-engine.js +320 -95
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query-session-settings.test.d.ts +1 -0
- package/dist/core/query-session-settings.test.js +252 -0
- package/dist/core/query-session-settings.test.js.map +1 -0
- package/dist/core/query.d.ts +18 -0
- package/dist/core/query.js +134 -15
- package/dist/core/query.js.map +1 -1
- package/dist/core/run-agent.d.ts +2 -0
- package/dist/core/run-agent.js +23 -15
- package/dist/core/run-agent.js.map +1 -1
- package/dist/core/session-settings-prompt.d.ts +35 -0
- package/dist/core/session-settings-prompt.js +61 -0
- package/dist/core/session-settings-prompt.js.map +1 -0
- package/dist/core/session-settings-prompt.test.d.ts +1 -0
- package/dist/core/session-settings-prompt.test.js +257 -0
- package/dist/core/session-settings-prompt.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/model-gateway.d.ts +1 -0
- package/dist/model/model-metadata.json +97 -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-instructions.test.d.ts +1 -0
- package/dist/model/openai-responses-instructions.test.js +64 -0
- package/dist/model/openai-responses-instructions.test.js.map +1 -0
- package/dist/model/openai-responses-mapper.js +96 -32
- package/dist/model/openai-responses-mapper.js.map +1 -1
- package/dist/model/smoke-responses-mapper.js +8 -5
- package/dist/model/smoke-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-export.d.ts +6 -0
- package/dist/session/session-export.js.map +1 -1
- package/dist/session/session-store.d.ts +15 -1
- package/dist/session/session-store.js +30 -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 +221 -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-background-history.test.d.ts +1 -0
- package/dist/tools/terminal-background-history.test.js +77 -0
- package/dist/tools/terminal-background-history.test.js.map +1 -0
- 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 +154 -0
- package/dist/tools/terminal-output-store.js +580 -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 +136 -4
- package/dist/web/index.js +303 -78
- package/dist/web/index.js.map +1 -1
- package/dist/web/prompt-config-protocol.d.ts +4 -0
- package/dist/web/prompt-config-protocol.js +57 -0
- package/dist/web/prompt-config-protocol.js.map +1 -0
- package/dist/web/prompt-config-protocol.test.d.ts +1 -0
- package/dist/web/prompt-config-protocol.test.js +78 -0
- package/dist/web/prompt-config-protocol.test.js.map +1 -0
- package/dist/web/runtime-context-protocol.d.ts +1 -0
- package/dist/web/runtime-context-protocol.js +1 -0
- package/dist/web/runtime-context-protocol.js.map +1 -1
- package/dist/web/session-prompt-integration.test.d.ts +1 -0
- package/dist/web/session-prompt-integration.test.js +57 -0
- package/dist/web/session-prompt-integration.test.js.map +1 -0
- package/dist/web/session-prompt-protocol.d.ts +8 -0
- package/dist/web/session-prompt-protocol.js +11 -0
- package/dist/web/session-prompt-protocol.js.map +1 -0
- package/dist/web/session-settings.test.d.ts +1 -0
- package/dist/web/session-settings.test.js +254 -0
- package/dist/web/session-settings.test.js.map +1 -0
- 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 +377 -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 +223 -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/docs/prompt-config.md +71 -0
- package/package.json +94 -93
- package/scripts/build-standalone.mjs +3 -0
- package/scripts/copy-prompt-resources.mjs +5 -0
package/dist/web/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { subagentHeader, subagentStatusFacts } from "./status-semantics.js";
|
|
3
|
+
import { handlePromptConfigRequest } from "./prompt-config-protocol.js";
|
|
2
4
|
import http from "node:http";
|
|
3
5
|
import fs from "node:fs/promises";
|
|
4
6
|
import path from "node:path";
|
|
5
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
8
|
import { fileURLToPath } from "node:url";
|
|
7
9
|
import { createRequire } from "node:module";
|
|
10
|
+
import { readSessionPrompt, updateSessionPrompt, SessionPromptError } from "./session-prompt-protocol.js";
|
|
8
11
|
import { QueryEngine } from "../core/query-engine.js";
|
|
9
12
|
import { InMemoryAppPromptStore } from "../app/app-prompt.js";
|
|
10
13
|
import { InMemoryAppState } from "../app/app-state.js";
|
|
@@ -31,6 +34,10 @@ import { parseReplCommand, helpText, replCommandDefinitions } from "../repl/comm
|
|
|
31
34
|
import { writeSessionMarkdownExport } from "../session/session-export.js";
|
|
32
35
|
import { DefaultContextManager } from "../context/context-manager.js";
|
|
33
36
|
import { createImageId } from "../types/messages.js";
|
|
37
|
+
import { readToolCallDetail, toolErrorText, allowToolDetailRequest, sanitizeAgentToolPayload, redactToolDetail } from "./tool-call-detail.js";
|
|
38
|
+
import { terminalFacts, terminalPreviews, terminalResultText } from "./terminal-presentation.js";
|
|
39
|
+
import { createAgentContentDetailResolver } from "./agent-content-detail.js";
|
|
40
|
+
import { toolDetailFieldsFromDetail } from "./tool-detail-fields.js";
|
|
34
41
|
import { WEB_HTML } from "./html.js";
|
|
35
42
|
import { openDirectory } from "../open-directory.js";
|
|
36
43
|
import { resolveImageBlockDataResultSync, resolveImageBlockDataSync } from "../core/image-storage.js";
|
|
@@ -274,7 +281,15 @@ function applyToolOverrides(registry, catalog, globalOverrides, sessionOverrides
|
|
|
274
281
|
}
|
|
275
282
|
/** Bind only the foreground view; never stop work owned by another session. */
|
|
276
283
|
export function activateSession(runtime) {
|
|
277
|
-
runtime.
|
|
284
|
+
const sessionDir = runtime.engine.snapshot().session?.sessionDir || undefined;
|
|
285
|
+
const summary = runtime.taskStore.bindSession?.(sessionDir);
|
|
286
|
+
const interruptedTasks = summary?.loaded ? runtime.taskStore.recoverableInterruptedTasks?.(sessionDir) ?? [] : [];
|
|
287
|
+
if (interruptedTasks.length) {
|
|
288
|
+
runtime.engine.noteRecoverableSubagents(() => {
|
|
289
|
+
for (const task of interruptedTasks)
|
|
290
|
+
runtime.taskStore.markNotified(task.taskId);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
278
293
|
}
|
|
279
294
|
export function sessionAgentTasks(runtime) {
|
|
280
295
|
const dir = runtime.engine.snapshot().session?.sessionDir || undefined;
|
|
@@ -390,6 +405,7 @@ function webRuntimeScopeKey(scope) {
|
|
|
390
405
|
}
|
|
391
406
|
export class WebRepl {
|
|
392
407
|
runtime;
|
|
408
|
+
agentContentReader = createAgentContentDetailResolver();
|
|
393
409
|
subscribers = new Set();
|
|
394
410
|
eventSequence = 0;
|
|
395
411
|
pendingDeltaOperations = [];
|
|
@@ -458,19 +474,60 @@ export class WebRepl {
|
|
|
458
474
|
this.send(subscriber, "client.reload", payload);
|
|
459
475
|
return subscribers.length;
|
|
460
476
|
}
|
|
477
|
+
async toolCallDetail(sessionId, toolUseId, messageId) {
|
|
478
|
+
const session = this.runtime.engine.snapshot().session;
|
|
479
|
+
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() });
|
|
480
|
+
return detail ? this.runtime.engine.redactDisplayValue({ ...detail, fields: toolDetailFieldsFromDetail(detail) }) : undefined;
|
|
481
|
+
}
|
|
482
|
+
async agentContent(sessionId, view, request) {
|
|
483
|
+
const session = this.runtime.engine.snapshot().session;
|
|
484
|
+
if (!session?.sessionDir || sessionId !== session.sessionId || !["timeline", "delegation", "report", "messages"].includes(view))
|
|
485
|
+
return undefined;
|
|
486
|
+
// Loading/recovery belongs to activateSession, never this read-only route.
|
|
487
|
+
if (!this.runtime.taskStore.getInSession(request.taskId, session.sessionDir))
|
|
488
|
+
return undefined;
|
|
489
|
+
return this.agentContentReader[view]({
|
|
490
|
+
ownerSessionId: sessionId, ownerSessionDir: session.sessionDir, taskStore: this.runtime.taskStore,
|
|
491
|
+
redact: value => this.runtime.engine.redactDisplayValue(value),
|
|
492
|
+
}, request);
|
|
493
|
+
}
|
|
494
|
+
terminalOutput(sessionId, runId, stream, offset, limitBytes) {
|
|
495
|
+
const session = this.runtime.engine.snapshot().session;
|
|
496
|
+
if (!session?.sessionDir || !sessionId || sessionId !== session.sessionId || !runId || !["stdout", "stderr"].includes(stream))
|
|
497
|
+
return undefined;
|
|
498
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limitBytes) || limitBytes < 4 || limitBytes > 262144)
|
|
499
|
+
return undefined;
|
|
500
|
+
const manager = this.runtime.execProcessManager;
|
|
501
|
+
const registered = manager.registerOwnerSession(sessionId, session.sessionDir);
|
|
502
|
+
if (!registered.ok)
|
|
503
|
+
return undefined;
|
|
504
|
+
const page = manager.readOutput(sessionId, runId, { stream: stream, offset, limitBytes });
|
|
505
|
+
// Cursor addresses stored bytes, not the possibly additionally redacted response length.
|
|
506
|
+
return page.ok ? this.runtime.engine.redactDisplayValue({ sessionId, runId, ...page.value }) : undefined;
|
|
507
|
+
}
|
|
461
508
|
snapshot(includeCatalog = false) {
|
|
462
509
|
const backgroundTasks = this.backgroundTasks();
|
|
510
|
+
const engineSnapshot = this.runtime.engine.snapshot();
|
|
511
|
+
const pendingSessionSettings = this.runtime.engine.getPendingSessionSettings?.() ?? {};
|
|
512
|
+
// Include retained reasoning alongside a queued model. Explicit undefined is
|
|
513
|
+
// the default reset and is intentionally omitted by JSON serialization.
|
|
514
|
+
if (Object.hasOwn(pendingSessionSettings, "model") && !Object.hasOwn(pendingSessionSettings, "reasoning")) {
|
|
515
|
+
pendingSessionSettings.reasoning = engineSnapshot.reasoning;
|
|
516
|
+
}
|
|
463
517
|
return {
|
|
464
|
-
lines: this.lines,
|
|
518
|
+
lines: this.lines.map(line => line.toolError ? { ...line, toolError: this.runtime.engine.redactDisplayValue?.(line.toolError) ?? line.toolError } : line),
|
|
465
519
|
status: this.status,
|
|
466
520
|
busy: this.busy,
|
|
467
521
|
queuedInput: this.queuedInput,
|
|
468
522
|
backgroundTaskCount: backgroundTasks.length,
|
|
469
523
|
backgroundTasks,
|
|
470
|
-
|
|
524
|
+
terminalTaskHistory: this.terminalTaskHistory(),
|
|
525
|
+
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
526
|
backgroundSessionRunCount: this.backgroundSessionRuns.size,
|
|
472
527
|
runningSessionIds: [...this.backgroundSessionRuns.keys()],
|
|
473
|
-
session:
|
|
528
|
+
session: engineSnapshot.session,
|
|
529
|
+
modelSettings: { model: engineSnapshot.model, reasoning: engineSnapshot.reasoning },
|
|
530
|
+
pendingSessionSettings,
|
|
474
531
|
fastMode: this.runtime.engine.isFastMode(),
|
|
475
532
|
appPrompt: this.runtime.engine.getAppPrompt(),
|
|
476
533
|
catalog: includeCatalog ? webCatalog(this.runtime) : undefined,
|
|
@@ -902,35 +959,105 @@ export class WebRepl {
|
|
|
902
959
|
}
|
|
903
960
|
return undefined;
|
|
904
961
|
}
|
|
962
|
+
async refreshSessionSettingMetrics(engine) {
|
|
963
|
+
const metrics = await engine.contextMetrics();
|
|
964
|
+
// A request can finish after its owner session was detached/switched.
|
|
965
|
+
if (this.runtime.engine !== engine)
|
|
966
|
+
return;
|
|
967
|
+
this.runtime.initialMetrics = metrics;
|
|
968
|
+
this.setStatus({ ...this.status, metrics, activityTick: this.status.activityTick + 1 });
|
|
969
|
+
}
|
|
905
970
|
async setFastMode(enabled) {
|
|
971
|
+
const engine = this.runtime.engine;
|
|
906
972
|
try {
|
|
907
|
-
const
|
|
908
|
-
this.
|
|
909
|
-
|
|
973
|
+
const { deferred } = await engine.updateSessionSettings({ fastMode: enabled });
|
|
974
|
+
if (this.runtime.engine === engine)
|
|
975
|
+
this.broadcastSync();
|
|
976
|
+
return { ok: true, fastMode: enabled, deferred };
|
|
910
977
|
}
|
|
911
978
|
catch (error) {
|
|
912
979
|
return actionFailure("FAST_MODE_UPDATE_FAILED", error instanceof Error ? error.message : String(error));
|
|
913
980
|
}
|
|
914
981
|
}
|
|
915
982
|
async setContextWindowK(value) {
|
|
916
|
-
if (
|
|
917
|
-
return actionFailure("CONTEXT_WINDOW_UPDATE_BLOCKED", "cannot change context window while a response is running");
|
|
918
|
-
if (typeof value !== "string" || !/^\d+$/u.test(value) || Number(value) <= 0) {
|
|
983
|
+
if (typeof value !== "string" || !/^\d+$/u.test(value) || Number(value) <= 0 || !Number.isSafeInteger(Number(value) * 1000)) {
|
|
919
984
|
return actionFailure("CONTEXT_WINDOW_INVALID", "context window must be a positive integer in k");
|
|
920
985
|
}
|
|
921
986
|
const contextWindowK = Number(value);
|
|
922
987
|
const contextWindowTokens = contextWindowK * 1000;
|
|
988
|
+
const engine = this.runtime.engine;
|
|
923
989
|
try {
|
|
924
|
-
await
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
990
|
+
const { deferred } = await engine.updateSessionSettings({ contextWindowTokens });
|
|
991
|
+
if (!deferred)
|
|
992
|
+
await this.refreshSessionSettingMetrics(engine);
|
|
993
|
+
if (this.runtime.engine === engine)
|
|
994
|
+
this.broadcastSync();
|
|
995
|
+
return { ok: true, contextWindowK, contextWindowTokens, deferred };
|
|
929
996
|
}
|
|
930
997
|
catch (error) {
|
|
931
998
|
return actionFailure("CONTEXT_WINDOW_UPDATE_FAILED", error instanceof Error ? error.message : String(error));
|
|
932
999
|
}
|
|
933
1000
|
}
|
|
1001
|
+
async setSessionModel(model, reasoning) {
|
|
1002
|
+
if (typeof model !== "string" || !model.trim() || model.length > 256 || /[\s\x00-\x1f]/u.test(model.trim())) {
|
|
1003
|
+
return actionFailure("MODEL_INVALID", "invalid model");
|
|
1004
|
+
}
|
|
1005
|
+
if (reasoning !== undefined && (typeof reasoning !== "string" || !["none", "minimal", "low", "medium", "high", "xhigh", "max", "default", "off"].includes(reasoning))) {
|
|
1006
|
+
return actionFailure("MODEL_INVALID", "invalid reasoning effort");
|
|
1007
|
+
}
|
|
1008
|
+
const engine = this.runtime.engine;
|
|
1009
|
+
const nextModel = model.trim();
|
|
1010
|
+
const current = { ...engine.getModelSettings(), ...engine.getPendingSessionSettings() };
|
|
1011
|
+
const argument = reasoning;
|
|
1012
|
+
const validationError = validateModelReasoningArgument(nextModel, argument);
|
|
1013
|
+
if (validationError)
|
|
1014
|
+
return actionFailure("MODEL_INVALID", validationError);
|
|
1015
|
+
const update = resolveModelReasoningUpdate(argument, current.reasoning, nextModel, nextModel !== current.model);
|
|
1016
|
+
try {
|
|
1017
|
+
// Current engine only: no env write, provider replacement, transcript message,
|
|
1018
|
+
// runtime-context injection or prompt-cache prefix change for UI settings.
|
|
1019
|
+
const { deferred } = await engine.updateSessionSettings({ model: nextModel, ...(update.update ? { reasoning: update.reasoning } : {}) });
|
|
1020
|
+
if (!deferred)
|
|
1021
|
+
await this.refreshSessionSettingMetrics(engine);
|
|
1022
|
+
if (this.runtime.engine === engine)
|
|
1023
|
+
this.broadcastSync();
|
|
1024
|
+
return { ok: true, deferred, modelSettings: { model: nextModel, reasoning: update.reasoning } };
|
|
1025
|
+
}
|
|
1026
|
+
catch (error) {
|
|
1027
|
+
return actionFailure("MODEL_UPDATE_FAILED", error instanceof Error ? error.message : String(error));
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
sessionPrompt() {
|
|
1031
|
+
return readSessionPrompt(this.runtime.engine);
|
|
1032
|
+
}
|
|
1033
|
+
async saveSessionPrompt(body) {
|
|
1034
|
+
const engine = this.runtime.engine;
|
|
1035
|
+
const result = await updateSessionPrompt(engine, body);
|
|
1036
|
+
if (this.runtime.engine === engine) {
|
|
1037
|
+
if (!result.deferred)
|
|
1038
|
+
await this.refreshSessionSettingMetrics(engine);
|
|
1039
|
+
this.publishRuntimeContext();
|
|
1040
|
+
this.broadcastSync();
|
|
1041
|
+
}
|
|
1042
|
+
return result;
|
|
1043
|
+
}
|
|
1044
|
+
async compactSession() {
|
|
1045
|
+
const engine = this.runtime.engine;
|
|
1046
|
+
try {
|
|
1047
|
+
const result = await engine.updateSessionSettings({ compact: true });
|
|
1048
|
+
if (this.runtime.engine === engine) {
|
|
1049
|
+
if (result.compaction?.report)
|
|
1050
|
+
this.appendCompaction(result.compaction.report);
|
|
1051
|
+
if (!result.deferred)
|
|
1052
|
+
await this.refreshSessionSettingMetrics(engine);
|
|
1053
|
+
this.broadcastSync();
|
|
1054
|
+
}
|
|
1055
|
+
return { ok: true, deferred: result.deferred };
|
|
1056
|
+
}
|
|
1057
|
+
catch (error) {
|
|
1058
|
+
return actionFailure("COMPACTION_FAILED", error instanceof Error ? error.message : String(error));
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
934
1061
|
loginForm(providerValue) {
|
|
935
1062
|
const provider = parseLoginProvider(providerValue);
|
|
936
1063
|
return createLoginFormPayload(this.runtime.envPath, provider);
|
|
@@ -1042,27 +1169,77 @@ export class WebRepl {
|
|
|
1042
1169
|
}
|
|
1043
1170
|
agentTaskSnapshot(task) {
|
|
1044
1171
|
const terminal = this.runtime.taskStore.isTerminal(task);
|
|
1045
|
-
|
|
1046
|
-
|
|
1172
|
+
// Redact complete values before any public preview boundary: clipping first can expose
|
|
1173
|
+
// a credential prefix which no longer matches the runtime registry's full value.
|
|
1174
|
+
const safeText = (value) => value === undefined ? undefined : this.runtime.engine.redactDisplayValue(value);
|
|
1175
|
+
const result = (value, limit = 1500) => {
|
|
1176
|
+
if (!value)
|
|
1177
|
+
return undefined;
|
|
1178
|
+
if (value.displaySource !== "agent_report" && value.displaySource !== "visible_text") {
|
|
1179
|
+
return { content: "", truncated: false, status: value.status, availability: "unavailable", reason: "旧报告缺少获准展示来源;不会公开未验证的文本" };
|
|
1180
|
+
}
|
|
1181
|
+
const content = safeText(value.content);
|
|
1182
|
+
return { content: content.slice(0, limit), truncated: content.length > limit, status: value.status };
|
|
1183
|
+
};
|
|
1184
|
+
const error = safeText(task.error);
|
|
1185
|
+
return this.runtime.engine.redactDisplayValue({
|
|
1047
1186
|
kind: "agent", taskId: task.taskId, agentId: task.agentId,
|
|
1048
1187
|
agentType: task.agentType, type: task.type, status: task.status,
|
|
1049
1188
|
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 })) },
|
|
1189
|
+
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
1190
|
pendingMessageCount: task.pendingMessages.length,
|
|
1052
1191
|
deliveredRetainedThisRun: task.messageReceipts?.filter((receipt) => receipt.status === "delivered" && receipt.runGeneration === task.runGeneration).length,
|
|
1053
1192
|
messageReceipts: (task.messageReceipts ?? []).slice(-20).map(({ id, status, queuedAt, deliveredAt, runGeneration }) => ({ id, status, queuedAt, deliveredAt, runGeneration })),
|
|
1054
1193
|
result: terminal ? result(task.result) : undefined,
|
|
1055
|
-
error: terminal ?
|
|
1056
|
-
errorTruncated: terminal && (
|
|
1057
|
-
runHistory: (task.runHistory ?? []).
|
|
1058
|
-
|
|
1059
|
-
|
|
1194
|
+
error: terminal ? error?.slice(0, 1500) : undefined,
|
|
1195
|
+
errorTruncated: terminal && (error?.length ?? 0) > 1500,
|
|
1196
|
+
runHistory: (task.runHistory ?? []).map((run) => {
|
|
1197
|
+
const archivedError = safeText(run.error);
|
|
1198
|
+
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 };
|
|
1199
|
+
}),
|
|
1200
|
+
createdAt: task.createdAt, updatedAt: task.updatedAt, startedAt: task.startedAt, completedAt: task.completedAt, durationMs: task.durationMs,
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
terminalTaskHistory() {
|
|
1204
|
+
const session = this.runtime.engine.snapshot().session;
|
|
1205
|
+
if (!session?.sessionId || !session.sessionDir)
|
|
1206
|
+
return [];
|
|
1207
|
+
const manager = this.runtime.execProcessManager;
|
|
1208
|
+
if (!manager.registerOwnerSession?.(session.sessionId, session.sessionDir).ok)
|
|
1209
|
+
return [];
|
|
1210
|
+
const records = [];
|
|
1211
|
+
let offset = 0;
|
|
1212
|
+
do {
|
|
1213
|
+
const history = manager.listHistory(session.sessionId, { offset, limit: 200 });
|
|
1214
|
+
if (!history.ok)
|
|
1215
|
+
break;
|
|
1216
|
+
records.push(...history.value.records);
|
|
1217
|
+
if (history.value.nextOffset === null)
|
|
1218
|
+
break;
|
|
1219
|
+
offset = history.value.nextOffset;
|
|
1220
|
+
} while (true);
|
|
1221
|
+
// Output history includes foreground commands too. Only an explicit durable
|
|
1222
|
+
// background transition belongs in this UI; legacy records are not inferred.
|
|
1223
|
+
return records.filter(({ record }) => record.metadata.backgrounded === true).reverse().map(({ record }) => ({
|
|
1224
|
+
kind: "terminal", taskId: `terminal:${record.runId}`, type: "终端",
|
|
1225
|
+
ownerSessionId: session.sessionId, sessionId: record.runId,
|
|
1226
|
+
status: record.lifecycle === "lost" ? "lost" : record.exit?.status ?? "unknown",
|
|
1227
|
+
description: record.metadata.description || record.metadata.command || "终端运行",
|
|
1228
|
+
command: record.metadata.command, cwd: record.metadata.cwd, shell: record.metadata.shell,
|
|
1229
|
+
tty: record.metadata.tty, processId: record.metadata.processId,
|
|
1230
|
+
createdAt: record.metadata.startedAt, completedAt: record.exit?.finishedAt,
|
|
1231
|
+
durationMs: record.exit?.durationMs, exitCode: record.exit?.exitCode,
|
|
1232
|
+
signal: record.exit?.signal, terminationReason: record.exit?.terminationReason,
|
|
1233
|
+
outputAvailability: record.availability, outputTruncated: record.truncated,
|
|
1234
|
+
metadataTruncated: record.metadata.truncatedFields,
|
|
1235
|
+
})).map(task => this.runtime.engine.redactDisplayValue(task));
|
|
1060
1236
|
}
|
|
1061
1237
|
backgroundTasks() {
|
|
1062
1238
|
const agentTasks = sessionAgentTasks(this.runtime)
|
|
1063
1239
|
.filter((task) => !this.runtime.taskStore.isTerminal(task))
|
|
1064
1240
|
.map((task) => this.agentTaskSnapshot(task));
|
|
1065
|
-
const
|
|
1241
|
+
const ownerSessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
1242
|
+
const terminalTasks = (ownerSessionId ? this.runtime.execProcessManager.list(ownerSessionId) : [])
|
|
1066
1243
|
.filter((terminal) => terminal.status === "running" && terminal.backgrounded)
|
|
1067
1244
|
.map((terminal) => ({
|
|
1068
1245
|
kind: "terminal",
|
|
@@ -1070,7 +1247,8 @@ export class WebRepl {
|
|
|
1070
1247
|
type: "终端",
|
|
1071
1248
|
status: terminal.status,
|
|
1072
1249
|
description: terminal.description || terminal.command,
|
|
1073
|
-
createdAt:
|
|
1250
|
+
createdAt: terminal.started_at,
|
|
1251
|
+
ownerSessionId,
|
|
1074
1252
|
sessionId: terminal.session_id,
|
|
1075
1253
|
processId: terminal.process_id,
|
|
1076
1254
|
command: terminal.command,
|
|
@@ -1078,8 +1256,11 @@ export class WebRepl {
|
|
|
1078
1256
|
shell: terminal.shell,
|
|
1079
1257
|
tty: terminal.tty,
|
|
1080
1258
|
durationMs: terminal.duration_ms,
|
|
1081
|
-
|
|
1082
|
-
|
|
1259
|
+
// Output is loaded from the owner-bound reader, never copied into state snapshots.
|
|
1260
|
+
exitCode: terminal.exit_code,
|
|
1261
|
+
signal: terminal.signal,
|
|
1262
|
+
terminationReason: terminal.termination_reason,
|
|
1263
|
+
outputAvailability: terminal.output_ref.availability,
|
|
1083
1264
|
}));
|
|
1084
1265
|
const sessionTasks = [...this.backgroundSessionRuns.values()].map((run) => ({
|
|
1085
1266
|
kind: "session",
|
|
@@ -1289,7 +1470,7 @@ export class WebRepl {
|
|
|
1289
1470
|
return liveLineId;
|
|
1290
1471
|
}, runtimeToolResultPresenter(this.runtime));
|
|
1291
1472
|
}
|
|
1292
|
-
const steps = (liveLine.toolStream?.steps ?? []).map((step) => step.status === "running" ? { ...step, status:
|
|
1473
|
+
const steps = (liveLine.toolStream?.steps ?? []).map((step) => step.status === "running" ? { ...step, status: "unknown" } : step);
|
|
1293
1474
|
if (replacement) {
|
|
1294
1475
|
this.replaceLine(liveLineId, {
|
|
1295
1476
|
...replacement,
|
|
@@ -1714,6 +1895,10 @@ export class WebRepl {
|
|
|
1714
1895
|
this.queueDeltaOperation(operation);
|
|
1715
1896
|
}
|
|
1716
1897
|
queueDeltaOperation(operation) {
|
|
1898
|
+
if (operation.type === "line.append" && operation.line.toolError)
|
|
1899
|
+
operation = { ...operation, line: { ...operation.line, toolError: this.runtime.engine.redactDisplayValue?.(operation.line.toolError) ?? operation.line.toolError } };
|
|
1900
|
+
if (operation.type === "line.patch" && operation.patch.toolError)
|
|
1901
|
+
operation = { ...operation, patch: { ...operation.patch, toolError: this.runtime.engine.redactDisplayValue?.(operation.patch.toolError) ?? operation.patch.toolError } };
|
|
1717
1902
|
this.pendingDeltaOperations.push(operation);
|
|
1718
1903
|
this.scheduleDeltaFlush();
|
|
1719
1904
|
}
|
|
@@ -1757,18 +1942,11 @@ export class WebRepl {
|
|
|
1757
1942
|
}
|
|
1758
1943
|
}
|
|
1759
1944
|
queueTerminalOutput(delta) {
|
|
1760
|
-
|
|
1945
|
+
const ownerSessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
1946
|
+
if (!this.subscribers.size || !ownerSessionId || delta.ownerSessionId !== ownerSessionId)
|
|
1761
1947
|
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
|
-
}
|
|
1948
|
+
// Invalidation only. The reader owns independent stream cursors; SSE is not a full-output source.
|
|
1949
|
+
this.pendingTerminalOutput.set(delta.sessionId, { ownerSessionId });
|
|
1772
1950
|
if (this.terminalOutputTimer)
|
|
1773
1951
|
return;
|
|
1774
1952
|
this.terminalOutputTimer = setTimeout(() => this.flushTerminalOutput(), TERMINAL_OUTPUT_FLUSH_MS);
|
|
@@ -1776,12 +1954,12 @@ export class WebRepl {
|
|
|
1776
1954
|
}
|
|
1777
1955
|
flushTerminalOutput() {
|
|
1778
1956
|
this.terminalOutputTimer = undefined;
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
const updates = [...this.pendingTerminalOutput.entries()].map(([sessionId, output]) => ({ sessionId, ...output }));
|
|
1957
|
+
const ownerSessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
1958
|
+
const updates = [...this.pendingTerminalOutput.entries()].filter(([, output]) => output.ownerSessionId === ownerSessionId).map(([sessionId, output]) => ({ sessionId, ...output, invalidated: true }));
|
|
1782
1959
|
this.pendingTerminalOutput.clear();
|
|
1783
|
-
|
|
1784
|
-
|
|
1960
|
+
if (updates.length)
|
|
1961
|
+
for (const subscriber of this.subscribers)
|
|
1962
|
+
this.send(subscriber, "terminal.output", { updates });
|
|
1785
1963
|
}
|
|
1786
1964
|
broadcastSync() {
|
|
1787
1965
|
this.discardPendingDeltaOperations();
|
|
@@ -1826,16 +2004,46 @@ async function route(req, res, router) {
|
|
|
1826
2004
|
return sendFile(res, highlightAssetPath, "text/javascript; charset=utf-8");
|
|
1827
2005
|
if (req.method === "GET" && url.pathname === "/vendor/highlight-theme.css")
|
|
1828
2006
|
return sendFile(res, highlightThemeAssetPath, "text/css; charset=utf-8");
|
|
2007
|
+
if (url.pathname === "/api/prompt-config")
|
|
2008
|
+
return handlePromptConfigRequest(req, res);
|
|
1829
2009
|
if (req.method === "GET" && url.pathname === "/api/client-info")
|
|
1830
2010
|
return sendJson(res, router.clientInfo());
|
|
1831
2011
|
if (req.method === "POST" && url.pathname === "/api/client-reload") {
|
|
1832
2012
|
const body = await readJsonBody(req);
|
|
1833
2013
|
return sendJson(res, await router.requestClientReload(body));
|
|
1834
2014
|
}
|
|
2015
|
+
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"])) {
|
|
2016
|
+
return sendJson(res, { error: "详情访问被拒绝" }, 403);
|
|
2017
|
+
}
|
|
1835
2018
|
const scope = webRuntimeScopeFromUrl(url);
|
|
1836
2019
|
const repl = await router.get(scope);
|
|
1837
2020
|
if (req.method === "GET" && url.pathname === "/events")
|
|
1838
2021
|
return repl.subscribe(res, router.clientInfo());
|
|
2022
|
+
if (req.method === "GET" && url.pathname === "/api/tool-call-detail") {
|
|
2023
|
+
res.setHeader("Cache-Control", "no-store");
|
|
2024
|
+
const detail = await repl.toolCallDetail(url.searchParams.get("sessionId") ?? "", url.searchParams.get("toolUseId") ?? "", url.searchParams.get("messageId") ?? undefined);
|
|
2025
|
+
if (!detail)
|
|
2026
|
+
return sendJson(res, { error: "调用详情不可获取或无权访问" }, 404);
|
|
2027
|
+
return sendJson(res, detail);
|
|
2028
|
+
}
|
|
2029
|
+
if (req.method === "GET" && url.pathname === "/api/agent-content") {
|
|
2030
|
+
res.setHeader("Cache-Control", "no-store");
|
|
2031
|
+
const refresh = url.searchParams.get("refresh");
|
|
2032
|
+
if (refresh !== null && refresh !== "true" && refresh !== "false")
|
|
2033
|
+
return sendJson(res, { error: "代理详情参数无效" }, 404);
|
|
2034
|
+
const page = await repl.agentContent(url.searchParams.get("sessionId") ?? "", url.searchParams.get("view") ?? "timeline", {
|
|
2035
|
+
taskId: url.searchParams.get("taskId") ?? "", runGeneration: Number(url.searchParams.get("runGeneration")),
|
|
2036
|
+
...(url.searchParams.has("cursor") ? { cursor: url.searchParams.get("cursor") } : {}),
|
|
2037
|
+
...(url.searchParams.has("pageChars") ? { pageChars: Number(url.searchParams.get("pageChars")) } : {}),
|
|
2038
|
+
...(url.searchParams.has("refresh") ? { refresh: url.searchParams.get("refresh") === "true" } : {}),
|
|
2039
|
+
});
|
|
2040
|
+
return page ? sendJson(res, page) : sendJson(res, { error: "代理内容不可获取或无权访问" }, 404);
|
|
2041
|
+
}
|
|
2042
|
+
if (req.method === "GET" && url.pathname === "/api/terminal-output") {
|
|
2043
|
+
res.setHeader("Cache-Control", "no-store");
|
|
2044
|
+
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));
|
|
2045
|
+
return page ? sendJson(res, page) : sendJson(res, { error: "终端输出不可获取或无权访问" }, 404);
|
|
2046
|
+
}
|
|
1839
2047
|
if (req.method === "GET" && url.pathname === "/api/state")
|
|
1840
2048
|
return sendJson(res, repl.snapshot(true));
|
|
1841
2049
|
if (req.method === "GET" && url.pathname === "/api/runtime-context")
|
|
@@ -1898,6 +2106,32 @@ async function route(req, res, router) {
|
|
|
1898
2106
|
const body = await readJsonBody(req);
|
|
1899
2107
|
return sendJson(res, repl.setAppPrompt(body));
|
|
1900
2108
|
}
|
|
2109
|
+
if (url.pathname === "/api/session-prompt" && (req.method === "GET" || req.method === "POST")) {
|
|
2110
|
+
res.setHeader("Cache-Control", "no-store");
|
|
2111
|
+
try {
|
|
2112
|
+
if (req.method === "GET")
|
|
2113
|
+
return sendJson(res, await repl.sessionPrompt());
|
|
2114
|
+
let body;
|
|
2115
|
+
try {
|
|
2116
|
+
body = await readJsonBody(req);
|
|
2117
|
+
}
|
|
2118
|
+
catch {
|
|
2119
|
+
return sendJson(res, actionFailure("SESSION_PROMPT_INVALID", "invalid JSON body"), 400);
|
|
2120
|
+
}
|
|
2121
|
+
return sendJson(res, await repl.saveSessionPrompt(body));
|
|
2122
|
+
}
|
|
2123
|
+
catch (error) {
|
|
2124
|
+
const status = error instanceof SessionPromptError ? error.statusCode : 500;
|
|
2125
|
+
const code = status === 409 ? "SESSION_PROMPT_CONFLICT" : status === 400 ? "SESSION_PROMPT_INVALID" : "SESSION_PROMPT_FAILED";
|
|
2126
|
+
return sendJson(res, actionFailure(code, error instanceof Error ? error.message : String(error)), status);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
if (req.method === "POST" && url.pathname === "/api/session-model") {
|
|
2130
|
+
const body = await readJsonBody(req);
|
|
2131
|
+
return sendJson(res, await repl.setSessionModel(body.model, body.reasoning));
|
|
2132
|
+
}
|
|
2133
|
+
if (req.method === "POST" && url.pathname === "/api/compact")
|
|
2134
|
+
return sendJson(res, await repl.compactSession());
|
|
1901
2135
|
if (req.method === "POST" && url.pathname === "/api/fast-mode") {
|
|
1902
2136
|
const body = await readJsonBody(req);
|
|
1903
2137
|
return sendJson(res, await repl.setFastMode(body.enabled === true));
|
|
@@ -2024,6 +2258,7 @@ function webCatalog(runtime) {
|
|
|
2024
2258
|
return {
|
|
2025
2259
|
commands: replCommandDefinitions,
|
|
2026
2260
|
modelIds,
|
|
2261
|
+
modelReasoning: Object.fromEntries(modelIds.map(model => [model, reasoningEffortsForModel(model) ?? []])),
|
|
2027
2262
|
reasoning: ["none", "minimal", "low", "medium", "high", "xhigh", "max", "default", "off"],
|
|
2028
2263
|
envPath: runtime.envPath,
|
|
2029
2264
|
};
|
|
@@ -2757,13 +2992,19 @@ function formatToolUse(toolUse) {
|
|
|
2757
2992
|
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
2993
|
}
|
|
2759
2994
|
function formatToolResultLine(toolName, output, ok, presentToolResult) {
|
|
2995
|
+
// Hidden fallback reports in historical agent payloads must not enter line.text
|
|
2996
|
+
// even when the frontend suppresses the ordinary result preview.
|
|
2997
|
+
const authorized = sanitizeAgentToolPayload(toolName, output);
|
|
2998
|
+
if (toolName.startsWith("subagent_"))
|
|
2999
|
+
output = authorized.value;
|
|
2760
3000
|
const presentation = normalizeToolResultPresentation(presentToolResult?.(toolName, output, ok));
|
|
2761
3001
|
const resources = presentation?.resources ?? toolResources(output);
|
|
2762
3002
|
const formatted = resources?.length
|
|
2763
3003
|
? { text: ok ? "资源已准备好。" : "资源准备失败。", full: true, bodyTitle: ok ? "资源已就绪" : "资源准备失败" }
|
|
2764
|
-
: formatToolResult(toolName, output, ok);
|
|
3004
|
+
: formatToolResult(toolName, toolName.startsWith("subagent_") ? redactToolDetail(output) : output, ok);
|
|
2765
3005
|
return {
|
|
2766
3006
|
kind: ok ? "tool" : "error",
|
|
3007
|
+
toolError: ok ? undefined : toolErrorText(output, ok).slice(0, 2000),
|
|
2767
3008
|
toolName,
|
|
2768
3009
|
toolPresentation: toolPresentationForSource(toolName),
|
|
2769
3010
|
title: presentation?.title ?? toolTitle(toolName, "finished"),
|
|
@@ -2950,12 +3191,16 @@ function normalizeToolDisplay(display) {
|
|
|
2950
3191
|
const visibleText = new Set([subject, purpose].filter((value) => Boolean(value)).map(normalizeDisplayText));
|
|
2951
3192
|
const facts = display.facts.filter((fact) => {
|
|
2952
3193
|
const normalized = normalizeDisplayText(fact.value);
|
|
2953
|
-
|
|
3194
|
+
const identity = `${fact.label}\0${normalized}`;
|
|
3195
|
+
if (!normalized || visibleText.has(identity))
|
|
2954
3196
|
return false;
|
|
2955
|
-
visibleText.add(
|
|
3197
|
+
visibleText.add(identity);
|
|
2956
3198
|
return true;
|
|
2957
3199
|
});
|
|
2958
3200
|
const previews = display.previews.filter((preview) => {
|
|
3201
|
+
// Keep identical nonempty streams, but never render empty output panels.
|
|
3202
|
+
if (/^(stdout|stderr)(?:$|[( ·])/.test(preview.label ?? ""))
|
|
3203
|
+
return !!preview.content.trim();
|
|
2959
3204
|
const normalized = normalizeDisplayText(preview.content);
|
|
2960
3205
|
if (!normalized || visibleText.has(normalized))
|
|
2961
3206
|
return false;
|
|
@@ -2989,8 +3234,10 @@ function buildToolUseDisplay(toolName, input) {
|
|
|
2989
3234
|
}
|
|
2990
3235
|
else if (toolName === "expose_downloads")
|
|
2991
3236
|
pushToolFact(facts, "文件", arrayLabel(data.paths), true);
|
|
2992
|
-
else if (toolName === "subagent_message")
|
|
2993
|
-
|
|
3237
|
+
else if (toolName === "subagent_message") {
|
|
3238
|
+
if (typeof data.message === "string" && data.message.trim())
|
|
3239
|
+
previews.push({ kind: "text", content: data.message });
|
|
3240
|
+
}
|
|
2994
3241
|
else if (toolName.startsWith("subagent_"))
|
|
2995
3242
|
pushToolFact(facts, "任务", data.task_id, true);
|
|
2996
3243
|
else if (toolName.startsWith("secret_")) {
|
|
@@ -3004,7 +3251,7 @@ function buildToolUseDisplay(toolName, input) {
|
|
|
3004
3251
|
return normalizeToolDisplay({ subject, purpose, facts, previews });
|
|
3005
3252
|
}
|
|
3006
3253
|
function buildToolResultDisplay(toolName, output, ok) {
|
|
3007
|
-
const data = isRecord(output) ? output : {};
|
|
3254
|
+
const data = toolName.startsWith("subagent_") ? subagentHeader(output) : isRecord(output) ? output : {};
|
|
3008
3255
|
const facts = [];
|
|
3009
3256
|
const previews = [];
|
|
3010
3257
|
let subject = toolDisplaySubject(toolName, data);
|
|
@@ -3017,13 +3264,8 @@ function buildToolResultDisplay(toolName, output, ok) {
|
|
|
3017
3264
|
subject = undefined;
|
|
3018
3265
|
purpose = stringValue(data.description);
|
|
3019
3266
|
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");
|
|
3267
|
+
previews.push(...terminalPreviews(data));
|
|
3268
|
+
facts.push(...terminalFacts(data));
|
|
3027
3269
|
}
|
|
3028
3270
|
else if (toolName === "file_search") {
|
|
3029
3271
|
subject = stringValue(data.query) || stringValue(data.grepPath) || stringValue(data.path);
|
|
@@ -3046,7 +3288,7 @@ function buildToolResultDisplay(toolName, output, ok) {
|
|
|
3046
3288
|
else if (toolName === "image_create") {
|
|
3047
3289
|
subject = stringValue(data.semanticName);
|
|
3048
3290
|
pushToolFact(facts, "图片", `${numberValue(data.returnedImages) ?? arrayLength(data.images)} 张`);
|
|
3049
|
-
pushToolFact(facts, "
|
|
3291
|
+
pushToolFact(facts, "请求尺寸", data.size === "auto" ? "auto(自动策略)" : data.size);
|
|
3050
3292
|
}
|
|
3051
3293
|
else if (toolName === "image_inspect") {
|
|
3052
3294
|
subject = arrayLabel(data.imageRefs);
|
|
@@ -3056,13 +3298,9 @@ function buildToolResultDisplay(toolName, output, ok) {
|
|
|
3056
3298
|
}
|
|
3057
3299
|
else if (toolName.startsWith("subagent_") || toolName === "subagent_run") {
|
|
3058
3300
|
subject = stringValue(data.description) || stringValue(data.task_id) || stringValue(data.agent_id);
|
|
3059
|
-
|
|
3301
|
+
facts.push(...subagentStatusFacts(toolName, output, ok));
|
|
3060
3302
|
pushToolFact(facts, "轮次", data.run_generation);
|
|
3061
3303
|
pushToolFact(facts, "待交付", data.pending_messages);
|
|
3062
|
-
if (toolName === "subagent_message")
|
|
3063
|
-
pushToolFact(facts, "交付说明", "仅入队,不代表已交付或完成");
|
|
3064
|
-
if (data.requires_resume === true)
|
|
3065
|
-
pushToolFact(facts, "后续", "需要显式续跑");
|
|
3066
3304
|
}
|
|
3067
3305
|
else if (toolName === "subagent_message") {
|
|
3068
3306
|
subject = stringValue(data.target);
|
|
@@ -3246,21 +3484,8 @@ function diffLineMarker(line) {
|
|
|
3246
3484
|
function isExecOutput(value) {
|
|
3247
3485
|
return isRecord(value) && typeof value.command === "string" && typeof value.duration_ms === "number";
|
|
3248
3486
|
}
|
|
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");
|
|
3487
|
+
function formatExecToolResult(output, _ok) {
|
|
3488
|
+
return terminalResultText(output);
|
|
3264
3489
|
}
|
|
3265
3490
|
function formatImageGenerationToolResult(output, ok) {
|
|
3266
3491
|
const error = typeof output.error === "string" ? output.error : undefined;
|