neoctl 0.2.45 → 0.2.47
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-tool.js +2 -1
- package/dist/agents/agent-tool.js.map +1 -1
- package/dist/context/compaction.d.ts +6 -0
- package/dist/context/compaction.js +10 -5
- package/dist/context/compaction.js.map +1 -1
- package/dist/context/prompts.js +2 -0
- package/dist/context/prompts.js.map +1 -1
- package/dist/core/abortable-stream.d.ts +4 -0
- package/dist/core/abortable-stream.js +52 -0
- package/dist/core/abortable-stream.js.map +1 -0
- package/dist/core/compaction-progress.d.ts +5 -0
- package/dist/core/compaction-progress.js +38 -0
- package/dist/core/compaction-progress.js.map +1 -0
- package/dist/core/execution-state.d.ts +12 -0
- package/dist/core/execution-state.js +19 -0
- package/dist/core/execution-state.js.map +1 -0
- package/dist/core/image-registry.d.ts +4 -2
- package/dist/core/image-registry.js +32 -7
- package/dist/core/image-registry.js.map +1 -1
- package/dist/core/image-storage.js +4 -0
- package/dist/core/image-storage.js.map +1 -1
- package/dist/core/query-engine.d.ts +31 -1
- package/dist/core/query-engine.js +198 -70
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +5 -1
- package/dist/core/query.js +77 -22
- package/dist/core/query.js.map +1 -1
- package/dist/core/session-images.d.ts +52 -0
- package/dist/core/session-images.js +395 -0
- package/dist/core/session-images.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/model/model-gateway.d.ts +4 -0
- package/dist/model/model-gateway.js.map +1 -1
- package/dist/model/openai-chat-mapper.js +2 -0
- package/dist/model/openai-chat-mapper.js.map +1 -1
- package/dist/model/openai-mappers.js +0 -2
- package/dist/model/openai-mappers.js.map +1 -1
- package/dist/model/openai-responses-mapper.js +3 -0
- package/dist/model/openai-responses-mapper.js.map +1 -1
- package/dist/model/tool-input-stream.d.ts +31 -0
- package/dist/model/tool-input-stream.js +226 -0
- package/dist/model/tool-input-stream.js.map +1 -0
- package/dist/repl/index.js +2 -0
- package/dist/repl/index.js.map +1 -1
- package/dist/session/session-store.d.ts +9 -0
- package/dist/session/session-store.js +20 -2
- package/dist/session/session-store.js.map +1 -1
- package/dist/tasks/subagent-tools.js +1 -1
- package/dist/tasks/subagent-tools.js.map +1 -1
- package/dist/tools/builtins/exec-process-manager.d.ts +4 -1
- package/dist/tools/builtins/exec-process-manager.js +26 -2
- package/dist/tools/builtins/exec-process-manager.js.map +1 -1
- package/dist/tools/builtins/exec-tool.js +1 -1
- package/dist/tools/builtins/exec-tool.js.map +1 -1
- package/dist/tools/builtins/framework-feedback-tool.d.ts +26 -0
- package/dist/tools/builtins/framework-feedback-tool.js +97 -0
- package/dist/tools/builtins/framework-feedback-tool.js.map +1 -0
- package/dist/tools/builtins/image-generation-tool.d.ts +2 -0
- package/dist/tools/builtins/image-generation-tool.js +31 -141
- package/dist/tools/builtins/image-generation-tool.js.map +1 -1
- package/dist/tools/builtins/image-loader-tool.d.ts +6 -1
- package/dist/tools/builtins/image-loader-tool.js +48 -26
- package/dist/tools/builtins/image-loader-tool.js.map +1 -1
- package/dist/tools/builtins/image-note-tool.js +2 -2
- package/dist/tools/builtins/image-note-tool.js.map +1 -1
- package/dist/tools/run-tool-use.js +4 -2
- package/dist/tools/run-tool-use.js.map +1 -1
- package/dist/tools/terminal-output-store.d.ts +1 -0
- package/dist/tools/terminal-output-store.js +13 -1
- package/dist/tools/terminal-output-store.js.map +1 -1
- package/dist/tools/tool-catalog.d.ts +1 -0
- package/dist/tools/tool-catalog.js +2 -0
- package/dist/tools/tool-catalog.js.map +1 -1
- package/dist/tools/tool.d.ts +1 -0
- package/dist/tools/tool.js.map +1 -1
- package/dist/types/events.d.ts +11 -0
- package/dist/types/messages.d.ts +3 -1
- package/dist/types/messages.js.map +1 -1
- package/dist/web/demo-mode.d.ts +37 -0
- package/dist/web/demo-mode.js +141 -0
- package/dist/web/demo-mode.js.map +1 -0
- package/dist/web/demo-tools.d.ts +37 -0
- package/dist/web/demo-tools.js +259 -0
- package/dist/web/demo-tools.js.map +1 -0
- package/dist/web/demo-video.d.ts +1 -0
- package/dist/web/demo-video.js +3 -0
- package/dist/web/demo-video.js.map +1 -0
- package/dist/web/image-result-metadata.d.ts +6 -0
- package/dist/web/image-result-metadata.js +3 -0
- package/dist/web/image-result-metadata.js.map +1 -1
- package/dist/web/index.d.ts +57 -3
- package/dist/web/index.js +388 -24
- package/dist/web/index.js.map +1 -1
- package/dist/web/tool-activity.d.ts +9 -0
- package/dist/web/tool-activity.js +19 -0
- package/dist/web/tool-activity.js.map +1 -0
- package/dist/web/tool-observation-result.d.ts +1 -0
- package/dist/web/tool-observation-result.js +35 -0
- package/dist/web/tool-observation-result.js.map +1 -0
- package/package.json +1 -1
package/dist/web/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { applyToolInputChanges } from "../model/tool-input-stream.js";
|
|
3
|
+
import { toolActivity } from "./tool-activity.js";
|
|
4
|
+
import { toolObservationResult } from "./tool-observation-result.js";
|
|
5
|
+
import { DemoMode, DEMO_MARKER, DEMO_PROMPT } from "./demo-mode.js";
|
|
6
|
+
import { DemoTools } from "./demo-tools.js";
|
|
7
|
+
import { createTextMessage } from "../types/messages.js";
|
|
2
8
|
import { imageResultMetadata } from "./image-result-metadata.js";
|
|
3
9
|
import { subagentHeader, subagentStatusFacts } from "./status-semantics.js";
|
|
4
10
|
import { handlePromptConfigRequest } from "./prompt-config-protocol.js";
|
|
@@ -26,6 +32,7 @@ import { ExecProcessManager } from "../tools/builtins/exec-process-manager.js";
|
|
|
26
32
|
import { listDirectoryTool, readFileTool } from "../tools/builtins/filesystem-tools.js";
|
|
27
33
|
import { grepTool } from "../tools/builtins/grep-tool.js";
|
|
28
34
|
import { searchTool } from "../tools/builtins/search-tool.js";
|
|
35
|
+
import { createFrameworkFeedbackTool, FrameworkFeedbackStore } from "../tools/builtins/framework-feedback-tool.js";
|
|
29
36
|
import { planTool } from "../tools/builtins/plan-tool.js";
|
|
30
37
|
import { createOpenAIImageGenerationTool } from "../tools/builtins/image-generation-tool.js";
|
|
31
38
|
import { createLoadImageTool } from "../tools/builtins/image-loader-tool.js";
|
|
@@ -165,6 +172,8 @@ export async function createWebRuntime(options = {}) {
|
|
|
165
172
|
if (modelConfig?.provider === "openai")
|
|
166
173
|
registerTool(createOpenAIImageGenerationTool(), { source: "builtin" });
|
|
167
174
|
registerTool(planTool, { source: "builtin" });
|
|
175
|
+
const frameworkFeedback = new FrameworkFeedbackStore(options.frameworkFeedbackDir);
|
|
176
|
+
registerTool(createFrameworkFeedbackTool(frameworkFeedback), { source: "builtin" });
|
|
168
177
|
for (const tool of options.externalTools ?? [])
|
|
169
178
|
registerTool(tool, { source: "external" });
|
|
170
179
|
for (const plugin of pluginCatalog) {
|
|
@@ -232,6 +241,7 @@ export async function createWebRuntime(options = {}) {
|
|
|
232
241
|
activateSession({ engine, taskStore });
|
|
233
242
|
const initialMetrics = await engine.contextMetrics();
|
|
234
243
|
runtime = {
|
|
244
|
+
frameworkFeedback,
|
|
235
245
|
engine,
|
|
236
246
|
appPromptStore,
|
|
237
247
|
communicationLogger,
|
|
@@ -526,6 +536,24 @@ function webRuntimeScopeKey(scope) {
|
|
|
526
536
|
}
|
|
527
537
|
export class WebRepl {
|
|
528
538
|
runtime;
|
|
539
|
+
demoOptions;
|
|
540
|
+
pendingContinuation;
|
|
541
|
+
runControl() {
|
|
542
|
+
const execution = this.runtime.engine.getExecutionState?.();
|
|
543
|
+
const resumable = (execution?.status === "paused" || execution?.status === "interrupted")
|
|
544
|
+
&& (!this.activeAbortController || this.activeAbortController.signal.aborted);
|
|
545
|
+
const waiting = this.pendingContinuation?.engine === this.runtime.engine;
|
|
546
|
+
return {
|
|
547
|
+
state: waiting ? "resuming" : resumable ? execution.status : this.busy ? "running" : "idle",
|
|
548
|
+
canPause: !waiting && !resumable && !this.stoppingRun && !this.immediateSend && Boolean(this.activeAbortController),
|
|
549
|
+
canResume: resumable && !waiting && !this.immediateSend,
|
|
550
|
+
reason: execution?.reason,
|
|
551
|
+
timer: execution?.elapsedMs === undefined ? undefined : {
|
|
552
|
+
elapsedMs: execution.elapsedMs,
|
|
553
|
+
running: execution.status === "running" && !waiting,
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
}
|
|
529
557
|
agentContentReader = createAgentContentDetailResolver();
|
|
530
558
|
subscribers = new Set();
|
|
531
559
|
eventSequence = 0;
|
|
@@ -537,6 +565,7 @@ export class WebRepl {
|
|
|
537
565
|
lineId = 0;
|
|
538
566
|
assistantLineId;
|
|
539
567
|
thinkingLineId;
|
|
568
|
+
previousTurnToolId;
|
|
540
569
|
finalizedThinkingLineId;
|
|
541
570
|
liveToolLineIds = new Map();
|
|
542
571
|
activeAbortController;
|
|
@@ -544,6 +573,7 @@ export class WebRepl {
|
|
|
544
573
|
lines;
|
|
545
574
|
status;
|
|
546
575
|
busy = false;
|
|
576
|
+
compactingSession;
|
|
547
577
|
queuedInput;
|
|
548
578
|
queuedAttachments;
|
|
549
579
|
foregroundRun;
|
|
@@ -556,8 +586,91 @@ export class WebRepl {
|
|
|
556
586
|
runtimeContextPublishQueue = Promise.resolve();
|
|
557
587
|
backgroundSessionRuns = new Map();
|
|
558
588
|
suppressReattachedStreaming = new Set();
|
|
559
|
-
|
|
589
|
+
demoSessions = new WeakMap();
|
|
590
|
+
demoStatus = new WeakMap();
|
|
591
|
+
demoMessageQueues = new WeakMap();
|
|
592
|
+
demoStarting = false;
|
|
593
|
+
isDemo(engine = this.runtime.engine) {
|
|
594
|
+
const session = engine.snapshot().session;
|
|
595
|
+
const key = `${session?.sessionId}:${session?.firstWindowId}`;
|
|
596
|
+
const cached = this.demoStatus.get(engine);
|
|
597
|
+
if (cached?.key === key)
|
|
598
|
+
return cached.active || this.demoSessions.has(engine);
|
|
599
|
+
if (cached)
|
|
600
|
+
this.demoSessions.delete(engine);
|
|
601
|
+
const active = engine.getDisplayEntries().some(entry => entry.type === "message" && entry.message.metadata?.demoMode === DEMO_MARKER);
|
|
602
|
+
this.demoStatus.set(engine, { key, active });
|
|
603
|
+
if (active)
|
|
604
|
+
this.demoSessions.set(engine, null);
|
|
605
|
+
return active;
|
|
606
|
+
}
|
|
607
|
+
async startDemo(expectedSessionId) {
|
|
608
|
+
const engine = this.runtime.engine;
|
|
609
|
+
if (!expectedSessionId || expectedSessionId !== engine.snapshot().session?.sessionId)
|
|
610
|
+
return actionFailure("SESSION_MISMATCH", "会话已切换");
|
|
611
|
+
if (this.demoStarting || this.isDemo() || this.busy || this.foregroundRun || this.stoppingRun || this.queuedInput !== undefined
|
|
612
|
+
|| this.backgroundTaskCount || engine.getDisplayEntries().length || this.lines.some(line => line.kind !== "system")) {
|
|
613
|
+
return actionFailure("DEMO_REQUIRES_EMPTY_SESSION", "仅空闲的空会话可以进入演示模式");
|
|
614
|
+
}
|
|
615
|
+
// Reserve synchronously before any await: double clicks and concurrent submits cannot start a second run.
|
|
616
|
+
this.demoStarting = true;
|
|
617
|
+
this.busy = true;
|
|
618
|
+
let demo;
|
|
619
|
+
try {
|
|
620
|
+
demo = new DemoMode(new DemoTools(this.runtime.tools, this.runtime.taskStore, this.runtime.execProcessManager), this.demoOptions.wait);
|
|
621
|
+
this.demoSessions.set(engine, demo);
|
|
622
|
+
const marker = createTextMessage("system", "");
|
|
623
|
+
marker.isMeta = true;
|
|
624
|
+
marker.metadata = { demoMode: DEMO_MARKER };
|
|
625
|
+
await engine.recordDisplayMessage(marker);
|
|
626
|
+
await demo.backend.prepare(engine.snapshot().session.sessionDir);
|
|
627
|
+
demo.controller.signal.throwIfAborted();
|
|
628
|
+
engine.setSimulation(demo);
|
|
629
|
+
this.startRun(DEMO_PROMPT);
|
|
630
|
+
return { ok: true };
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
demo?.stop();
|
|
634
|
+
this.busy = false;
|
|
635
|
+
this.broadcastSync();
|
|
636
|
+
return actionFailure("DEMO_START_FAILED", error instanceof Error ? error.message : String(error));
|
|
637
|
+
}
|
|
638
|
+
finally {
|
|
639
|
+
this.demoStarting = false;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async recordDemoInput(text, attachments) {
|
|
643
|
+
const engine = this.runtime.engine;
|
|
644
|
+
if (!text.trim() && attachments.length === 0)
|
|
645
|
+
return { ok: true };
|
|
646
|
+
const previous = this.demoMessageQueues.get(engine) ?? Promise.resolve();
|
|
647
|
+
const pending = previous.catch(() => undefined).then(async () => {
|
|
648
|
+
const payload = buildWebPromptPayload(text, attachments);
|
|
649
|
+
const message = createTextMessage("user", payload.text);
|
|
650
|
+
if (payload.blocks)
|
|
651
|
+
message.blocks = payload.blocks;
|
|
652
|
+
const stored = await engine.recordDisplayMessage(message);
|
|
653
|
+
if (this.runtime.engine === engine) {
|
|
654
|
+
renderMessage(stored, line => this.append(line));
|
|
655
|
+
this.broadcastSync();
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
this.demoMessageQueues.set(engine, pending);
|
|
659
|
+
try {
|
|
660
|
+
await pending;
|
|
661
|
+
return { ok: true };
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
665
|
+
}
|
|
666
|
+
finally {
|
|
667
|
+
if (this.demoMessageQueues.get(engine) === pending)
|
|
668
|
+
this.demoMessageQueues.delete(engine);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
constructor(runtime, demoOptions = {}) {
|
|
560
672
|
this.runtime = runtime;
|
|
673
|
+
this.demoOptions = demoOptions;
|
|
561
674
|
const lineId = { current: 0 };
|
|
562
675
|
this.lines = initialLines(runtime, lineId);
|
|
563
676
|
this.lineId = lineId.current;
|
|
@@ -614,7 +727,7 @@ export class WebRepl {
|
|
|
614
727
|
redact: value => this.runtime.engine.redactDisplayValue(value),
|
|
615
728
|
}, request);
|
|
616
729
|
}
|
|
617
|
-
terminalOutput(sessionId, runId, stream, offset, limitBytes) {
|
|
730
|
+
terminalOutput(sessionId, runId, stream, offset, limitBytes, tail = false) {
|
|
618
731
|
const session = this.runtime.engine.snapshot().session;
|
|
619
732
|
if (!session?.sessionDir || !sessionId || sessionId !== session.sessionId || !runId || !["stdout", "stderr"].includes(stream))
|
|
620
733
|
return undefined;
|
|
@@ -624,7 +737,7 @@ export class WebRepl {
|
|
|
624
737
|
const registered = manager.registerOwnerSession(sessionId, session.sessionDir);
|
|
625
738
|
if (!registered.ok)
|
|
626
739
|
return undefined;
|
|
627
|
-
const page = manager.readOutput(sessionId, runId, { stream: stream, offset, limitBytes });
|
|
740
|
+
const page = manager.readOutput(sessionId, runId, { stream: stream, offset, limitBytes, tail });
|
|
628
741
|
// Cursor addresses stored bytes, not the possibly additionally redacted response length.
|
|
629
742
|
return page.ok ? this.runtime.engine.redactDisplayValue({ sessionId, runId, ...page.value }) : undefined;
|
|
630
743
|
}
|
|
@@ -640,7 +753,9 @@ export class WebRepl {
|
|
|
640
753
|
return {
|
|
641
754
|
lines: this.lines.map(line => line.toolError ? { ...line, toolError: this.runtime.engine.redactDisplayValue?.(line.toolError) ?? line.toolError } : line),
|
|
642
755
|
status: this.timedStatus(),
|
|
643
|
-
busy: this.busy,
|
|
756
|
+
busy: this.runControl().canResume ? false : this.busy,
|
|
757
|
+
runControl: this.runControl(),
|
|
758
|
+
demoMode: this.isDemo(),
|
|
644
759
|
queuedInput: this.queuedInput,
|
|
645
760
|
backgroundTaskCount: backgroundTasks.length,
|
|
646
761
|
backgroundTasks,
|
|
@@ -894,6 +1009,12 @@ export class WebRepl {
|
|
|
894
1009
|
this.publishRuntimeContext();
|
|
895
1010
|
}
|
|
896
1011
|
async submit(text, attachments = []) {
|
|
1012
|
+
if (this.runControl().canResume && (!text.trimStart().startsWith("/") || !text.trim()))
|
|
1013
|
+
return this.continueRun(text, attachments);
|
|
1014
|
+
if (this.pendingContinuation)
|
|
1015
|
+
return { ok: false, error: "续接请求已接收,请勿重复发送" };
|
|
1016
|
+
if (this.isDemo())
|
|
1017
|
+
return this.recordDemoInput(text, attachments);
|
|
897
1018
|
if (this.stoppingRun || this.immediateSend)
|
|
898
1019
|
return { ok: false, error: "正在停止上一轮请求,请稍后发送" };
|
|
899
1020
|
const trimmed = text.trim();
|
|
@@ -916,6 +1037,14 @@ export class WebRepl {
|
|
|
916
1037
|
return { ok: true };
|
|
917
1038
|
}
|
|
918
1039
|
async submitImmediately(text, attachments = []) {
|
|
1040
|
+
if (this.runControl().canResume) {
|
|
1041
|
+
const result = this.continueRun(text, attachments);
|
|
1042
|
+
return result.ok ? { ok: true, interrupted: false } : result;
|
|
1043
|
+
}
|
|
1044
|
+
if (this.isDemo()) {
|
|
1045
|
+
const result = await this.recordDemoInput(text, attachments);
|
|
1046
|
+
return result.ok ? { ok: true, interrupted: false } : result;
|
|
1047
|
+
}
|
|
919
1048
|
if (this.immediateSend)
|
|
920
1049
|
return { ok: false, error: "正在发送,请勿重复提交" };
|
|
921
1050
|
if (this.stoppingRun)
|
|
@@ -948,8 +1077,8 @@ export class WebRepl {
|
|
|
948
1077
|
this.queuedInput = mergeWebQueuedInput(this.queuedInput, text);
|
|
949
1078
|
this.queuedAttachments = [...(this.queuedAttachments ?? []), ...attachments].slice(0, 20);
|
|
950
1079
|
}
|
|
951
|
-
startRun(text, attachments = []) {
|
|
952
|
-
const run = this.handleCommandOrPrompt(text, attachments).catch((error) => {
|
|
1080
|
+
startRun(text, attachments = [], continuationOnly = false) {
|
|
1081
|
+
const run = this.handleCommandOrPrompt(text, attachments, continuationOnly).catch((error) => {
|
|
953
1082
|
this.append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
954
1083
|
this.setBusy(false);
|
|
955
1084
|
this.setStatus({ ...this.status, phase: "ready", detail: undefined });
|
|
@@ -1013,6 +1142,7 @@ export class WebRepl {
|
|
|
1013
1142
|
this.runtime.usage.reset();
|
|
1014
1143
|
this.status = initialStatus(this.runtime, metrics);
|
|
1015
1144
|
const lineId = { current: 0 };
|
|
1145
|
+
this.previousTurnToolId = undefined;
|
|
1016
1146
|
this.lines = initialLines(this.runtime, lineId);
|
|
1017
1147
|
this.lineId = lineId.current;
|
|
1018
1148
|
this.assistantLineId = undefined;
|
|
@@ -1186,9 +1316,28 @@ export class WebRepl {
|
|
|
1186
1316
|
}
|
|
1187
1317
|
async compactSession() {
|
|
1188
1318
|
const engine = this.runtime.engine;
|
|
1319
|
+
if (this.isDemo())
|
|
1320
|
+
return actionFailure("DEMO_SCRIPTED", "演示会话由后台安排压缩");
|
|
1321
|
+
if (this.compactingSession === engine)
|
|
1322
|
+
return actionFailure("COMPACTION_IN_PROGRESS", "Context compaction is already running");
|
|
1323
|
+
const showProgress = !this.busy;
|
|
1324
|
+
const runToken = this.foregroundRunToken;
|
|
1325
|
+
const controller = showProgress ? new AbortController() : undefined;
|
|
1326
|
+
if (controller)
|
|
1327
|
+
this.activeAbortController = controller;
|
|
1328
|
+
let finishForeground;
|
|
1329
|
+
const foreground = showProgress ? new Promise(resolve => { finishForeground = resolve; }) : undefined;
|
|
1330
|
+
if (foreground)
|
|
1331
|
+
this.foregroundRun = foreground;
|
|
1332
|
+
if (showProgress) {
|
|
1333
|
+
this.compactingSession = engine;
|
|
1334
|
+
this.busy = true;
|
|
1335
|
+
this.setStatus({ ...this.status, phase: "compacting", detail: "manualcompact: generating context summary", activityTick: this.status.activityTick + 1 });
|
|
1336
|
+
}
|
|
1189
1337
|
try {
|
|
1190
|
-
|
|
1191
|
-
|
|
1338
|
+
// An idle click executes now through the settings barrier, not the chat/input queue.
|
|
1339
|
+
const result = await engine.updateSessionSettings({ compact: true }, { abortSignal: controller?.signal });
|
|
1340
|
+
if (this.runtime.engine === engine && this.foregroundRunToken === runToken) {
|
|
1192
1341
|
if (result.compaction?.report)
|
|
1193
1342
|
this.appendCompaction(result.compaction.report);
|
|
1194
1343
|
if (!result.deferred)
|
|
@@ -1200,6 +1349,26 @@ export class WebRepl {
|
|
|
1200
1349
|
catch (error) {
|
|
1201
1350
|
return actionFailure("COMPACTION_FAILED", error instanceof Error ? error.message : String(error));
|
|
1202
1351
|
}
|
|
1352
|
+
finally {
|
|
1353
|
+
if (controller && this.activeAbortController === controller)
|
|
1354
|
+
this.activeAbortController = undefined;
|
|
1355
|
+
if (this.compactingSession === engine)
|
|
1356
|
+
this.compactingSession = undefined;
|
|
1357
|
+
if (this.foregroundRun === foreground)
|
|
1358
|
+
this.foregroundRun = undefined;
|
|
1359
|
+
finishForeground?.();
|
|
1360
|
+
if (showProgress && this.runtime.engine === engine && this.foregroundRunToken === runToken) {
|
|
1361
|
+
this.busy = false;
|
|
1362
|
+
this.setStatus({ ...this.status, phase: "ready", detail: undefined, activityTick: this.status.activityTick + 1 });
|
|
1363
|
+
const queued = this.queuedInput;
|
|
1364
|
+
const attachments = this.queuedAttachments ?? [];
|
|
1365
|
+
if (queued !== undefined) {
|
|
1366
|
+
this.queuedInput = undefined;
|
|
1367
|
+
this.queuedAttachments = undefined;
|
|
1368
|
+
this.startRun(queued, attachments);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1203
1372
|
}
|
|
1204
1373
|
loginForm(providerValue) {
|
|
1205
1374
|
const provider = parseLoginProvider(providerValue);
|
|
@@ -1244,9 +1413,72 @@ export class WebRepl {
|
|
|
1244
1413
|
this.setStatus({ ...this.status, metrics, activityTick: this.status.activityTick + 1 });
|
|
1245
1414
|
this.publishRuntimeContext();
|
|
1246
1415
|
}
|
|
1416
|
+
async listFrameworkFeedback(page, pageSize) {
|
|
1417
|
+
if (!this.runtime.frameworkFeedback)
|
|
1418
|
+
throw new Error("Framework feedback storage unavailable");
|
|
1419
|
+
return this.runtime.frameworkFeedback.list(page, pageSize);
|
|
1420
|
+
}
|
|
1421
|
+
async deleteFrameworkFeedback(id) {
|
|
1422
|
+
if (typeof id !== "string" || !/^\d{13}-[a-f0-9-]{36}$/.test(id))
|
|
1423
|
+
return actionFailure("INVALID_REQUEST", "建议编号无效");
|
|
1424
|
+
try {
|
|
1425
|
+
if (!this.runtime.frameworkFeedback)
|
|
1426
|
+
throw new Error("unavailable");
|
|
1427
|
+
return { ok: true, deleted: await this.runtime.frameworkFeedback.delete(id) };
|
|
1428
|
+
}
|
|
1429
|
+
catch {
|
|
1430
|
+
return actionFailure("FEEDBACK_DELETE_FAILED", "删除失败,请重试");
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
pauseRun() {
|
|
1434
|
+
if (this.runControl().canResume)
|
|
1435
|
+
return { ok: true };
|
|
1436
|
+
if (!this.runControl().canPause)
|
|
1437
|
+
return actionFailure("INVALID_REQUEST", "当前没有可暂停的会话执行");
|
|
1438
|
+
this.runtime.engine.markExecutionPaused();
|
|
1439
|
+
const queuedText = this.queuedInput;
|
|
1440
|
+
const queuedAttachments = this.queuedAttachments;
|
|
1441
|
+
// Do not await cooperative cancellation: the UI must stop immediately.
|
|
1442
|
+
void this.stopForegroundRun("用户暂停了会话").catch(() => this.broadcastSync());
|
|
1443
|
+
this.queuedInput = queuedText;
|
|
1444
|
+
this.queuedAttachments = queuedAttachments;
|
|
1445
|
+
this.status = { ...this.status, phase: "ready", detail: "paused", currentTool: undefined, retryCooldownUntil: undefined };
|
|
1446
|
+
this.broadcastSync();
|
|
1447
|
+
return { ok: true };
|
|
1448
|
+
}
|
|
1449
|
+
continueRun(text = "", attachments = []) {
|
|
1450
|
+
if (!this.runControl().canResume)
|
|
1451
|
+
return actionFailure("INVALID_REQUEST", "没有可恢复的执行,或续接请求已经提交");
|
|
1452
|
+
const useQueue = !text.trim() && attachments.length === 0 && this.queuedInput !== undefined;
|
|
1453
|
+
const request = { engine: this.runtime.engine,
|
|
1454
|
+
text: text || (useQueue ? this.queuedInput : ""),
|
|
1455
|
+
attachments: useQueue ? this.queuedAttachments ?? [] : attachments };
|
|
1456
|
+
this.queuedInput = undefined;
|
|
1457
|
+
this.queuedAttachments = undefined;
|
|
1458
|
+
this.pendingContinuation = request;
|
|
1459
|
+
this.broadcastSync();
|
|
1460
|
+
// Accept immediately, but never overlap two queries on the same mutable engine.
|
|
1461
|
+
void (async () => {
|
|
1462
|
+
try {
|
|
1463
|
+
await this.stoppingRun;
|
|
1464
|
+
await this.foregroundRun;
|
|
1465
|
+
if (this.pendingContinuation !== request || this.runtime.engine !== request.engine)
|
|
1466
|
+
return;
|
|
1467
|
+
this.pendingContinuation = undefined;
|
|
1468
|
+
this.startRun(request.text, request.attachments, !request.text.trim() && request.attachments.length === 0);
|
|
1469
|
+
}
|
|
1470
|
+
finally {
|
|
1471
|
+
if (this.pendingContinuation === request)
|
|
1472
|
+
this.pendingContinuation = undefined;
|
|
1473
|
+
this.broadcastSync();
|
|
1474
|
+
}
|
|
1475
|
+
})().catch(error => this.append({ kind: "error", text: error instanceof Error ? error.message : String(error) }));
|
|
1476
|
+
return { ok: true };
|
|
1477
|
+
}
|
|
1247
1478
|
async interrupt() {
|
|
1248
1479
|
if (this.immediateSend)
|
|
1249
1480
|
await this.immediateSend;
|
|
1481
|
+
this.demoSessions.get(this.runtime.engine)?.stop();
|
|
1250
1482
|
const interrupted = await this.stopForegroundRun("Interrupted from neo web");
|
|
1251
1483
|
return { ok: true, interrupted };
|
|
1252
1484
|
}
|
|
@@ -1261,6 +1493,10 @@ export class WebRepl {
|
|
|
1261
1493
|
append(line, streaming = false) {
|
|
1262
1494
|
const id = ++this.lineId;
|
|
1263
1495
|
const nextLine = { id, ...line };
|
|
1496
|
+
if (line.activity && line.toolName && line.toolUseId && !line.image) {
|
|
1497
|
+
nextLine.connectPreviousTool = this.previousTurnToolId !== undefined;
|
|
1498
|
+
this.previousTurnToolId = id;
|
|
1499
|
+
}
|
|
1264
1500
|
this.lines.push(nextLine);
|
|
1265
1501
|
if (streaming)
|
|
1266
1502
|
this.broadcastDelta({ type: "line.append", line: nextLine });
|
|
@@ -1283,6 +1519,9 @@ export class WebRepl {
|
|
|
1283
1519
|
this.broadcastSync();
|
|
1284
1520
|
}
|
|
1285
1521
|
replaceLine(id, patch) {
|
|
1522
|
+
const previous = this.lines.find(line => line.id === id);
|
|
1523
|
+
if (previous?.activity && patch.titleStatus)
|
|
1524
|
+
patch.activity = { ...previous.activity, phase: patch.titleStatus === "success" ? "completed" : "failed" };
|
|
1286
1525
|
this.lines = this.lines.map((line) => line.id === id ? { ...line, ...patch } : line);
|
|
1287
1526
|
this.broadcastSync();
|
|
1288
1527
|
}
|
|
@@ -1431,6 +1670,7 @@ export class WebRepl {
|
|
|
1431
1670
|
signal: terminal.signal,
|
|
1432
1671
|
terminationReason: terminal.termination_reason,
|
|
1433
1672
|
outputAvailability: terminal.output_ref.availability,
|
|
1673
|
+
outputRevision: terminal.output_ref.streams,
|
|
1434
1674
|
}));
|
|
1435
1675
|
const sessionTasks = [...this.backgroundSessionRuns.values()].map((run) => ({
|
|
1436
1676
|
kind: "session",
|
|
@@ -1451,6 +1691,14 @@ export class WebRepl {
|
|
|
1451
1691
|
.sort((left, right) => createdAtTime(right.createdAt) - createdAtTime(left.createdAt));
|
|
1452
1692
|
}
|
|
1453
1693
|
async detachRunningForeground(reason) {
|
|
1694
|
+
if (this.pendingContinuation || this.stoppingRun)
|
|
1695
|
+
throw new Error("上一轮请求正在取消,请稍后切换会话");
|
|
1696
|
+
if (this.demoStarting)
|
|
1697
|
+
throw new Error("演示模式正在启动");
|
|
1698
|
+
if (this.isDemo() && this.busy) {
|
|
1699
|
+
this.demoSessions.get(this.runtime.engine)?.stop();
|
|
1700
|
+
return this.stopForegroundRun(reason);
|
|
1701
|
+
}
|
|
1454
1702
|
if (!this.busy)
|
|
1455
1703
|
return false;
|
|
1456
1704
|
const snapshot = this.runtime.engine.snapshot().session;
|
|
@@ -1512,6 +1760,12 @@ export class WebRepl {
|
|
|
1512
1760
|
finalizeLiveLine(id) {
|
|
1513
1761
|
if (id === undefined)
|
|
1514
1762
|
return;
|
|
1763
|
+
const previous = this.lines.find(line => line.id === id);
|
|
1764
|
+
if (previous?.activity && ["generating", "queued", "running"].includes(previous.activity.phase)) {
|
|
1765
|
+
const activity = { ...previous.activity, phase: previous.titleStatus === "success" ? "completed" : previous.titleStatus === "failure" ? "failed" : "interrupted" };
|
|
1766
|
+
this.lines = this.lines.map(line => line.id === id ? { ...line, activity } : line);
|
|
1767
|
+
this.queueDeltaOperation({ type: "line.patch", id, patch: { activity } });
|
|
1768
|
+
}
|
|
1515
1769
|
this.lines = this.lines.map((line) => line.id === id ? { ...line, live: false } : line);
|
|
1516
1770
|
this.queueDeltaOperation({ type: "line.patch", id, patch: { live: false } });
|
|
1517
1771
|
}
|
|
@@ -1531,6 +1785,8 @@ export class WebRepl {
|
|
|
1531
1785
|
cancelLiveToolLines(reason) {
|
|
1532
1786
|
for (const id of this.liveToolLineIds.values()) {
|
|
1533
1787
|
const line = this.lines.find((item) => item.id === id);
|
|
1788
|
+
if (line?.activity)
|
|
1789
|
+
this.patchLiveToolLine(line.toolUseId, { activity: { ...line.activity, phase: "interrupted" } });
|
|
1534
1790
|
const steps = (line?.toolStream?.steps ?? []).map((step) => step.status === "running" ? { ...step, status: "failed", phase: "cancelled" } : step);
|
|
1535
1791
|
this.lines = this.lines.map((item) => item.id === id ? { ...item, live: false, kind: "error", titleStatus: "failure", bodyTitle: reason, toolStream: { ...item.toolStream, phase: "cancelled", message: reason, steps } } : item);
|
|
1536
1792
|
this.queueDeltaOperation({ type: "line.patch", id, patch: { live: false, kind: "error", titleStatus: "failure", bodyTitle: reason, toolStream: { ...line?.toolStream, phase: "cancelled", message: reason, steps } } });
|
|
@@ -1631,7 +1887,12 @@ export class WebRepl {
|
|
|
1631
1887
|
else {
|
|
1632
1888
|
stream.message = progress.message;
|
|
1633
1889
|
}
|
|
1634
|
-
|
|
1890
|
+
const data = progress.data;
|
|
1891
|
+
const observation = data && typeof data.task_id === "string" && Number.isSafeInteger(data.run_generation)
|
|
1892
|
+
? { ...line.observation, task_id: data.task_id, run_generation: data.run_generation }
|
|
1893
|
+
: data && typeof data.session_id === "string" && ["terminal.started", "terminal.output.delta"].includes(String(data.type)) && ["terminal_run", "terminal_control"].includes(event.toolUse.name)
|
|
1894
|
+
? { ...line.observation, session_id: data.session_id, status: "running", command: typeof data.command === "string" ? data.command : line.observation?.command } : line.observation;
|
|
1895
|
+
this.patchLiveToolLine(event.toolUse.id, { toolStream: stream, observation });
|
|
1635
1896
|
}
|
|
1636
1897
|
applyAvailableToolResult(event) {
|
|
1637
1898
|
const liveLineId = this.liveToolLineIds.get(event.toolUse.id);
|
|
@@ -1663,19 +1924,28 @@ export class WebRepl {
|
|
|
1663
1924
|
}
|
|
1664
1925
|
}
|
|
1665
1926
|
handleEvent(event) {
|
|
1927
|
+
// Model lifecycle defines membership only. The frontend owns the fixed
|
|
1928
|
+
// two-second playback and must not be interrupted by the next request.
|
|
1929
|
+
if (event.type === "state" && event.phase === "calling_model")
|
|
1930
|
+
this.previousTurnToolId = undefined;
|
|
1666
1931
|
this.reduce(event);
|
|
1667
1932
|
if (event.type === "timing.updated") {
|
|
1668
1933
|
if (event.timing.kind === "tool") {
|
|
1669
1934
|
const line = this.lines.find(item => item.timing?.id === event.timing.id)
|
|
1670
1935
|
?? this.lines.find(item => item.id === this.liveToolLineIds.get(event.timing.toolUseId));
|
|
1671
1936
|
if (line) {
|
|
1672
|
-
|
|
1673
|
-
this.
|
|
1937
|
+
const activity = line.activity && event.timing.status === "running" ? { ...line.activity, phase: "running" } : line.activity;
|
|
1938
|
+
this.lines = this.lines.map(item => item.id === line.id ? { ...item, timing: event.timing, activity } : item);
|
|
1939
|
+
this.queueDeltaOperation({ type: "line.patch", id: line.id, patch: { timing: event.timing, activity } });
|
|
1674
1940
|
}
|
|
1675
1941
|
}
|
|
1676
1942
|
this.queueDeltaStatus();
|
|
1677
1943
|
return;
|
|
1678
1944
|
}
|
|
1945
|
+
if (event.type === "message" && event.message.metadata?.loadedImages === true && typeof event.message.metadata.sourceToolUseId === "string") {
|
|
1946
|
+
renderObservedImages(event.message, line => this.append(line));
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1679
1949
|
if (event.type === "message" && this.matchesPendingUserImageEcho(event.message)) {
|
|
1680
1950
|
const messageId = this.pendingUserImageEchoMessageId;
|
|
1681
1951
|
this.pendingUserImageEchoIds = undefined;
|
|
@@ -1684,7 +1954,35 @@ export class WebRepl {
|
|
|
1684
1954
|
this.broadcastSync();
|
|
1685
1955
|
return;
|
|
1686
1956
|
}
|
|
1687
|
-
if (event.type === "tool_call.started" || event.type === "tool_call.
|
|
1957
|
+
if (event.type === "tool_call.started" || event.type === "tool_call.input") {
|
|
1958
|
+
let id = this.liveToolLineIds.get(event.callId);
|
|
1959
|
+
let previous = this.lines.find(line => line.id === id);
|
|
1960
|
+
const name = event.name || previous?.toolName || "tool";
|
|
1961
|
+
if (id === undefined) {
|
|
1962
|
+
const activity = { phase: "generating", input: {} };
|
|
1963
|
+
id = this.append({ ...formatToolUse({ id: event.callId, name, input: {} }), activity, live: true });
|
|
1964
|
+
this.liveToolLineIds.set(event.callId, id);
|
|
1965
|
+
previous = this.lines.find(line => line.id === id);
|
|
1966
|
+
}
|
|
1967
|
+
if (previous?.toolName !== name && name !== "tool")
|
|
1968
|
+
this.patchLiveToolLine(event.callId, { toolName: name, title: toolTitle(name, "running"), toolPresentation: toolPresentationForSource(name) });
|
|
1969
|
+
if (event.type === "tool_call.input") {
|
|
1970
|
+
const activity = { phase: "generating", input: applyToolInputChanges(previous?.activity?.input || {}, event.changes), truncated: event.truncated };
|
|
1971
|
+
this.lines = this.lines.map(line => line.id === id ? { ...line, activity } : line);
|
|
1972
|
+
this.queueDeltaOperation({ type: "line.activity.input", id, changes: event.changes, truncated: event.truncated });
|
|
1973
|
+
}
|
|
1974
|
+
this.queueDeltaStatus();
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
if (event.type === "tool_call.ready") {
|
|
1978
|
+
const patch = { ...formatToolUse(event.toolUse), activity: toolActivity(event.toolUse.input, "queued"), live: true };
|
|
1979
|
+
if (this.liveToolLineIds.has(event.toolUse.id))
|
|
1980
|
+
this.patchLiveToolLine(event.toolUse.id, patch);
|
|
1981
|
+
else
|
|
1982
|
+
this.liveToolLineIds.set(event.toolUse.id, this.append(patch));
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
if (event.type === "tool_call.delta") {
|
|
1688
1986
|
this.queueDeltaStatus();
|
|
1689
1987
|
return;
|
|
1690
1988
|
}
|
|
@@ -1696,6 +1994,10 @@ export class WebRepl {
|
|
|
1696
1994
|
this.broadcastSync();
|
|
1697
1995
|
return;
|
|
1698
1996
|
}
|
|
1997
|
+
if (event.type === "assistant.output.done") {
|
|
1998
|
+
this.queueDeltaStatus();
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
1699
2001
|
if (event.type === "assistant.delta") {
|
|
1700
2002
|
this.finalizeThinkingLine();
|
|
1701
2003
|
const id = this.assistantLineId ?? this.append({ kind: "assistant", text: "", live: true }, true);
|
|
@@ -1788,8 +2090,12 @@ export class WebRepl {
|
|
|
1788
2090
|
this.finalizeLiveLine(this.assistantLineId);
|
|
1789
2091
|
this.finalizeThinkingLine();
|
|
1790
2092
|
this.assistantLineId = undefined;
|
|
1791
|
-
const
|
|
1792
|
-
this.liveToolLineIds.
|
|
2093
|
+
const patch = { ...formatToolUse(event.toolUse), activity: toolActivity(event.toolUse.input, "queued"), live: true };
|
|
2094
|
+
const existing = this.liveToolLineIds.get(event.toolUse.id);
|
|
2095
|
+
if (existing === undefined)
|
|
2096
|
+
this.liveToolLineIds.set(event.toolUse.id, this.append(patch));
|
|
2097
|
+
else
|
|
2098
|
+
this.patchLiveToolLine(event.toolUse.id, patch);
|
|
1793
2099
|
return;
|
|
1794
2100
|
}
|
|
1795
2101
|
if (event.type === "tool.finished") {
|
|
@@ -1818,7 +2124,7 @@ export class WebRepl {
|
|
|
1818
2124
|
.map((block) => block.imageId);
|
|
1819
2125
|
return imageIds.length === pending.length && imageIds.every((imageId, index) => imageId === pending[index]);
|
|
1820
2126
|
}
|
|
1821
|
-
async handleCommandOrPrompt(text, attachments = []) {
|
|
2127
|
+
async handleCommandOrPrompt(text, attachments = [], continuationOnly = false) {
|
|
1822
2128
|
const command = parseReplCommand(text);
|
|
1823
2129
|
if (command.type === "exit") {
|
|
1824
2130
|
this.append(systemLine("neo web server is still running. Close this tab or stop the server process with Ctrl+C."));
|
|
@@ -1918,7 +2224,8 @@ export class WebRepl {
|
|
|
1918
2224
|
}
|
|
1919
2225
|
const promptPayload = buildWebPromptPayload(command.text, attachments);
|
|
1920
2226
|
const optimisticMessageId = `web-user-${this.lineId + 1}`;
|
|
1921
|
-
|
|
2227
|
+
if (!continuationOnly)
|
|
2228
|
+
this.append({ kind: "user", text: promptPayload.displayText, messageId: optimisticMessageId });
|
|
1922
2229
|
const optimisticImageIds = (promptPayload.blocks ?? [])
|
|
1923
2230
|
.filter((block) => block.type === "image" && typeof block.imageId === "string")
|
|
1924
2231
|
.map((block) => block.imageId);
|
|
@@ -1932,12 +2239,14 @@ export class WebRepl {
|
|
|
1932
2239
|
this.setStatus({ ...this.status, phase: "running", detail: "working", modelOutput: undefined, usage: undefined, streamedOutputTokens: 0, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined });
|
|
1933
2240
|
const engine = this.runtime.engine;
|
|
1934
2241
|
try {
|
|
1935
|
-
|
|
2242
|
+
const options = {
|
|
1936
2243
|
abortSignal: abortController.signal,
|
|
1937
2244
|
blocks: promptPayload.blocks,
|
|
1938
2245
|
displayText: promptPayload.displayText,
|
|
1939
2246
|
stopAfterTurn: () => this.queuedInput !== undefined,
|
|
1940
|
-
}
|
|
2247
|
+
};
|
|
2248
|
+
const stream = continuationOnly ? engine.continueExecution(options) : engine.sendUserText(promptPayload.text, options);
|
|
2249
|
+
for await (const event of stream) {
|
|
1941
2250
|
if (this.foregroundRunToken !== runToken)
|
|
1942
2251
|
continue;
|
|
1943
2252
|
if (this.runtime.engine !== engine)
|
|
@@ -1960,6 +2269,7 @@ export class WebRepl {
|
|
|
1960
2269
|
}
|
|
1961
2270
|
}
|
|
1962
2271
|
finally {
|
|
2272
|
+
this.demoSessions.get(engine)?.backend.stop();
|
|
1963
2273
|
if (this.foregroundRunToken !== runToken)
|
|
1964
2274
|
return;
|
|
1965
2275
|
if (this.runtime.engine !== engine)
|
|
@@ -1970,6 +2280,11 @@ export class WebRepl {
|
|
|
1970
2280
|
this.finalizeForegroundView();
|
|
1971
2281
|
this.pendingUserImageEchoIds = undefined;
|
|
1972
2282
|
this.pendingUserImageEchoMessageId = undefined;
|
|
2283
|
+
if (engine.getExecutionState?.()?.status === "interrupted") {
|
|
2284
|
+
this.setBusy(false);
|
|
2285
|
+
this.setStatus({ ...this.status, phase: "ready", detail: "interrupted", retryCooldownUntil: undefined });
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
1973
2288
|
const queuedText = this.queuedInput;
|
|
1974
2289
|
const queuedAttach = this.queuedAttachments;
|
|
1975
2290
|
this.queuedInput = undefined;
|
|
@@ -2170,11 +2485,16 @@ export function compactLineOperations(operations) {
|
|
|
2170
2485
|
previous.text += operation.text;
|
|
2171
2486
|
continue;
|
|
2172
2487
|
}
|
|
2488
|
+
if (operation.type === "line.activity.input" && previous?.type === "line.activity.input" && previous.id === operation.id) {
|
|
2489
|
+
previous.changes.push(...operation.changes);
|
|
2490
|
+
previous.truncated = operation.truncated;
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2173
2493
|
if (operation.type === "line.patch" && previous?.type === "line.patch" && previous.id === operation.id) {
|
|
2174
2494
|
previous.patch = { ...previous.patch, ...operation.patch };
|
|
2175
2495
|
continue;
|
|
2176
2496
|
}
|
|
2177
|
-
compacted.push(operation.type === "line.patch" ? { ...operation, patch: { ...operation.patch } } : { ...operation });
|
|
2497
|
+
compacted.push(operation.type === "line.patch" ? { ...operation, patch: { ...operation.patch } } : operation.type === "line.activity.input" ? { ...operation, changes: [...operation.changes] } : { ...operation });
|
|
2178
2498
|
}
|
|
2179
2499
|
return compacted;
|
|
2180
2500
|
}
|
|
@@ -2232,7 +2552,7 @@ export async function handleWebRequest(req, res, router) {
|
|
|
2232
2552
|
}
|
|
2233
2553
|
if (req.method === "GET" && url.pathname === "/api/terminal-output") {
|
|
2234
2554
|
res.setHeader("Cache-Control", "no-store");
|
|
2235
|
-
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));
|
|
2555
|
+
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), url.searchParams.get("tail") === "true");
|
|
2236
2556
|
return page ? sendJson(res, page) : sendJson(res, { error: "终端输出不可获取或无权访问" }, 404);
|
|
2237
2557
|
}
|
|
2238
2558
|
if (req.method === "GET" && url.pathname === "/api/state")
|
|
@@ -2263,6 +2583,19 @@ export async function handleWebRequest(req, res, router) {
|
|
|
2263
2583
|
? await workspaceRepl.deleteWorkspaceDirectory(body.path)
|
|
2264
2584
|
: actionFailure("CWD_NOT_CONFIGURED", "workspace navigation is not configured"));
|
|
2265
2585
|
}
|
|
2586
|
+
if (req.method === "POST" && url.pathname === "/api/framework-feedback/delete") {
|
|
2587
|
+
const body = await readJsonBody(req);
|
|
2588
|
+
return sendJson(res, await repl.deleteFrameworkFeedback(body?.id));
|
|
2589
|
+
}
|
|
2590
|
+
if (req.method === "GET" && url.pathname === "/api/framework-feedback") {
|
|
2591
|
+
res.setHeader("Cache-Control", "no-store");
|
|
2592
|
+
try {
|
|
2593
|
+
return sendJson(res, await repl.listFrameworkFeedback(Number(url.searchParams.get("page") ?? 1), Number(url.searchParams.get("pageSize") ?? 10)));
|
|
2594
|
+
}
|
|
2595
|
+
catch {
|
|
2596
|
+
return sendJson(res, { ok: false, error: "建议加载失败" }, 500);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2266
2599
|
if (req.method === "GET" && url.pathname === "/api/tools")
|
|
2267
2600
|
return sendJson(res, repl.globalTools());
|
|
2268
2601
|
if (req.method === "POST" && url.pathname === "/api/tools/global") {
|
|
@@ -2321,6 +2654,8 @@ export async function handleWebRequest(req, res, router) {
|
|
|
2321
2654
|
const body = await readJsonBody(req);
|
|
2322
2655
|
return sendJson(res, await repl.setSessionModel(body.model, body.reasoning));
|
|
2323
2656
|
}
|
|
2657
|
+
if (req.method === "POST" && url.pathname === "/api/demo")
|
|
2658
|
+
return sendJson(res, await repl.startDemo(url.searchParams.get("sessionId") ?? ""));
|
|
2324
2659
|
if (req.method === "POST" && url.pathname === "/api/compact")
|
|
2325
2660
|
return sendJson(res, await repl.compactSession());
|
|
2326
2661
|
if (req.method === "POST" && url.pathname === "/api/fast-mode") {
|
|
@@ -2339,6 +2674,10 @@ export async function handleWebRequest(req, res, router) {
|
|
|
2339
2674
|
const body = await readJsonBody(req);
|
|
2340
2675
|
return sendJson(res, await repl.submitImmediately(String(body.text ?? ""), sanitizeWebAttachments(body.attachments)));
|
|
2341
2676
|
}
|
|
2677
|
+
if (req.method === "POST" && url.pathname === "/api/run/pause")
|
|
2678
|
+
return sendJson(res, repl.pauseRun());
|
|
2679
|
+
if (req.method === "POST" && url.pathname === "/api/run/continue")
|
|
2680
|
+
return sendJson(res, repl.continueRun());
|
|
2342
2681
|
if (req.method === "POST" && url.pathname === "/api/interrupt")
|
|
2343
2682
|
return sendJson(res, await repl.interrupt());
|
|
2344
2683
|
if (req.method === "POST" && url.pathname === "/api/queue/cancel")
|
|
@@ -2479,7 +2818,7 @@ function restoredHistoryLines(runtime) {
|
|
|
2479
2818
|
if (line.toolUseId && line.titleStatus && historyToolLineIndexes.has(line.toolUseId)) {
|
|
2480
2819
|
const index = historyToolLineIndexes.get(line.toolUseId);
|
|
2481
2820
|
const started = lines[index];
|
|
2482
|
-
lines[index] = { ...line, presentationLevel: mergePresentationLevel(line.presentationLevel, started.presentationLevel), toolDisplay: mergeToolDisplays(line.toolDisplay, started.toolDisplay) };
|
|
2821
|
+
lines[index] = { ...line, activity: started.activity ? { ...started.activity, phase: line.titleStatus === "success" ? "completed" : "failed" } : undefined, presentationLevel: mergePresentationLevel(line.presentationLevel, started.presentationLevel), toolDisplay: mergeToolDisplays(line.toolDisplay, started.toolDisplay) };
|
|
2483
2822
|
return index + 1;
|
|
2484
2823
|
}
|
|
2485
2824
|
lines.push(line);
|
|
@@ -2505,6 +2844,10 @@ function restoredHistoryLines(runtime) {
|
|
|
2505
2844
|
}
|
|
2506
2845
|
for (const [entryIndex, entry] of entries.entries()) {
|
|
2507
2846
|
if (entry.type === "message") {
|
|
2847
|
+
if (entry.message.metadata?.loadedImages === true && typeof entry.message.metadata.sourceToolUseId === "string") {
|
|
2848
|
+
renderObservedImages(entry.message, append);
|
|
2849
|
+
continue;
|
|
2850
|
+
}
|
|
2508
2851
|
renderMessage(entry.message, append, undefined, {
|
|
2509
2852
|
includeToolUseBlocks: true,
|
|
2510
2853
|
includeThinkingBlocks: true,
|
|
@@ -2520,6 +2863,8 @@ function restoredHistoryLines(runtime) {
|
|
|
2520
2863
|
const matches = line.toolUseId ? toolTimings.get(line.toolUseId) : undefined;
|
|
2521
2864
|
if (matches?.length === 1)
|
|
2522
2865
|
line.timing = matches[0];
|
|
2866
|
+
if (line.activity && !line.titleStatus)
|
|
2867
|
+
line.activity = { ...line.activity, phase: "interrupted" };
|
|
2523
2868
|
}
|
|
2524
2869
|
return lines;
|
|
2525
2870
|
}
|
|
@@ -2675,6 +3020,12 @@ function reduceStatus(status, event) {
|
|
|
2675
3020
|
return { ...status, metrics: event.metrics, inputTokenUpdatedAt: event.metrics.estimatedInputTokens !== status.metrics?.estimatedInputTokens ? Date.now() : status.inputTokenUpdatedAt, activityTick: status.activityTick + 1 };
|
|
2676
3021
|
if (event.type === "usage")
|
|
2677
3022
|
return { ...status, usage: event.usage, inputTokenUpdatedAt: event.usage.inputTokens !== undefined ? Date.now() : status.inputTokenUpdatedAt, outputTokenUpdatedAt: event.usage.outputTokens !== undefined ? Date.now() : status.outputTokenUpdatedAt, activityTick: status.activityTick + 1 };
|
|
3023
|
+
if (event.type === "assistant.output.done")
|
|
3024
|
+
return status.modelOutput?.kind === "text" ? { ...status, modelOutput: undefined, activityTick: status.activityTick + 1 } : status;
|
|
3025
|
+
if (event.type === "message" && event.message.role === "assistant" && event.message.blocks.some(block => block.type === "text") && status.modelOutput?.kind === "text")
|
|
3026
|
+
return { ...status, modelOutput: undefined, activityTick: status.activityTick + 1 };
|
|
3027
|
+
if (event.type === "assistant.delta" && !event.text)
|
|
3028
|
+
return status;
|
|
2678
3029
|
if (event.type === "assistant.delta")
|
|
2679
3030
|
return { ...status, phase: "calling_model", modelOutput: { kind: "text" }, streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.text), outputTokenUpdatedAt: Date.now(), activityTick: status.activityTick + 1 };
|
|
2680
3031
|
if (event.type === "thinking.delta")
|
|
@@ -3065,6 +3416,15 @@ function renderMessageImages(message, append, messageId = message.id) {
|
|
|
3065
3416
|
}
|
|
3066
3417
|
return rendered;
|
|
3067
3418
|
}
|
|
3419
|
+
function renderObservedImages(message, append) {
|
|
3420
|
+
for (const [index, block] of message.blocks.entries()) {
|
|
3421
|
+
if (block.type !== "image")
|
|
3422
|
+
continue;
|
|
3423
|
+
const line = imageLineForBlock("tool_result", block, message.id, index);
|
|
3424
|
+
if (line)
|
|
3425
|
+
append({ ...line, observationOnly: true, messageId: message.id, parentToolName: "image_inspect", parentToolUseId: message.metadata?.sourceToolUseId });
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3068
3428
|
function imageLineForBlock(role, block, messageId, blockIndex) {
|
|
3069
3429
|
const kind = kindForRole(role);
|
|
3070
3430
|
if (kind === "meta")
|
|
@@ -3150,6 +3510,8 @@ function compactionLine(report, createdAt = new Date().toISOString(), current =
|
|
|
3150
3510
|
return {
|
|
3151
3511
|
kind: "meta",
|
|
3152
3512
|
title: "Context compaction",
|
|
3513
|
+
presentationLevel: "primary",
|
|
3514
|
+
collapsible: false,
|
|
3153
3515
|
text: formatCompactionReportSummary(report),
|
|
3154
3516
|
format: "plain",
|
|
3155
3517
|
compaction: { ...report, createdAt, current },
|
|
@@ -3190,14 +3552,15 @@ function thinkingLine(text, live = false) {
|
|
|
3190
3552
|
return { kind: "thinking", title: titleForKind("thinking"), text, previewStyle: "summary", summaryMaxLines: THINKING_SUMMARY_MAX_LINES, live };
|
|
3191
3553
|
}
|
|
3192
3554
|
function formatToolUse(toolUse) {
|
|
3555
|
+
const activity = toolActivity(toolUse.input, "running");
|
|
3193
3556
|
const toolPresentation = toolPresentationForSource(toolUse.name);
|
|
3194
3557
|
const presentationLevel = toolPresentationLevel(toolUse.name, toolUse.input);
|
|
3195
3558
|
if (toolUse.name === "plan_update" && isWebPlanPayload(toolUse.input))
|
|
3196
|
-
return { kind: "tool", toolName: toolUse.name, toolPresentation, title: toolPresentation.label, bodyTitle: webPlanBodyTitle(toolUse.input), text: serializeWebPlanPayload(toolUse.input), presentationLevel, collapsible: true };
|
|
3559
|
+
return { kind: "tool", activity, toolName: toolUse.name, toolPresentation, toolUseId: toolUse.id, title: toolPresentation.label, bodyTitle: webPlanBodyTitle(toolUse.input), text: serializeWebPlanPayload(toolUse.input), presentationLevel, collapsible: true };
|
|
3197
3560
|
if (toolUse.name === "image_create")
|
|
3198
|
-
return { kind: "tool", toolName: toolUse.name, toolPresentation, toolUseId: toolUse.id, title: toolPresentation.label, bodyTitle: "图片模型处理中", text: JSON.stringify(toolUse.input ?? {}, null, 2), format: "plain", previewStyle: "summary", presentationLevel: "primary", collapsible: true };
|
|
3561
|
+
return { kind: "tool", activity, toolName: toolUse.name, toolPresentation, toolUseId: toolUse.id, title: toolPresentation.label, bodyTitle: "图片模型处理中", text: JSON.stringify(toolUse.input ?? {}, null, 2), format: "plain", previewStyle: "summary", presentationLevel: "primary", collapsible: true };
|
|
3199
3562
|
const summary = summarizeToolUse(toolUse.name, toolUse.input);
|
|
3200
|
-
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 };
|
|
3563
|
+
return { kind: "tool", activity, 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 };
|
|
3201
3564
|
}
|
|
3202
3565
|
function formatToolResultLine(toolName, output, ok, presentToolResult) {
|
|
3203
3566
|
// Hidden fallback reports in historical agent payloads must not enter line.text
|
|
@@ -3212,6 +3575,7 @@ function formatToolResultLine(toolName, output, ok, presentToolResult) {
|
|
|
3212
3575
|
: formatToolResult(toolName, toolName.startsWith("subagent_") ? redactToolDetail(output) : output, ok);
|
|
3213
3576
|
return {
|
|
3214
3577
|
kind: ok ? "tool" : "error",
|
|
3578
|
+
observation: toolObservationResult(toolName, output),
|
|
3215
3579
|
toolError: ok ? undefined : toolErrorText(output, ok).slice(0, 2000),
|
|
3216
3580
|
toolName,
|
|
3217
3581
|
toolPresentation: toolPresentationForSource(toolName),
|