@cjhyy/code-shell-core 0.7.0-beta.1 → 0.7.1
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/arena/arena.d.ts +2 -0
- package/dist/arena/arena.js +30 -1
- package/dist/arena/phases/adjudication.d.ts +2 -1
- package/dist/arena/phases/adjudication.js +2 -1
- package/dist/arena/phases/build-consensus.d.ts +2 -1
- package/dist/arena/phases/build-consensus.js +3 -1
- package/dist/arena/phases/cross-review.d.ts +3 -1
- package/dist/arena/phases/cross-review.js +8 -2
- package/dist/arena/phases/debate-rounds.d.ts +2 -1
- package/dist/arena/phases/debate-rounds.js +4 -2
- package/dist/arena/phases/participant-research.d.ts +2 -1
- package/dist/arena/phases/participant-research.js +3 -1
- package/dist/arena/phases/planning-detail-expansion.d.ts +2 -1
- package/dist/arena/phases/planning-detail-expansion.js +2 -1
- package/dist/arena/planner.d.ts +2 -1
- package/dist/arena/planner.js +2 -1
- package/dist/arena/types.d.ts +5 -1
- package/dist/cc-orchestrator/agent-adapter.d.ts +2 -0
- package/dist/cc-orchestrator/agent-adapter.js +4 -0
- package/dist/cc-orchestrator/codex-session-history.d.ts +14 -1
- package/dist/cc-orchestrator/codex-session-history.js +64 -4
- package/dist/cc-orchestrator/external-agent-changes.js +22 -5
- package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -1
- package/dist/cc-orchestrator/external-agent-driver.js +202 -38
- package/dist/cc-orchestrator/session-history.d.ts +35 -0
- package/dist/cc-orchestrator/session-history.js +96 -13
- package/dist/cli/agent-server-tcp.js +13 -2
- package/dist/context/manager.d.ts +3 -3
- package/dist/context/manager.js +6 -6
- package/dist/context/token-counter.js +5 -3
- package/dist/context/tool-result-storage.d.ts +6 -0
- package/dist/context/tool-result-storage.js +25 -4
- package/dist/credentials/access.d.ts +11 -1
- package/dist/credentials/access.js +77 -1
- package/dist/credentials/index.d.ts +3 -1
- package/dist/credentials/index.js +2 -0
- package/dist/credentials/oauth.d.ts +25 -0
- package/dist/credentials/oauth.js +179 -0
- package/dist/credentials/store.d.ts +2 -1
- package/dist/credentials/store.js +19 -8
- package/dist/credentials/types.d.ts +84 -1
- package/dist/credentials/types.js +16 -1
- package/dist/engine/engine.d.ts +67 -34
- package/dist/engine/engine.js +448 -247
- package/dist/engine/goal.d.ts +19 -0
- package/dist/engine/goal.js +16 -6
- package/dist/engine/input-attachments.js +156 -13
- package/dist/engine/run-image-input.d.ts +22 -0
- package/dist/engine/run-image-input.js +195 -0
- package/dist/engine/session-title.d.ts +2 -1
- package/dist/engine/session-title.js +4 -1
- package/dist/engine/steer-queue.d.ts +3 -1
- package/dist/engine/steer-queue.js +10 -2
- package/dist/engine/turn-loop.d.ts +48 -1
- package/dist/engine/turn-loop.js +307 -37
- package/dist/engine/types.d.ts +6 -2
- package/dist/git/worktree/crud.d.ts +3 -0
- package/dist/git/worktree/crud.js +32 -3
- package/dist/git/worktree/git-exec.d.ts +2 -2
- package/dist/git/worktree/git-exec.js +47 -11
- package/dist/git/worktree/query.d.ts +8 -7
- package/dist/git/worktree/query.js +27 -20
- package/dist/hooks/events.d.ts +3 -0
- package/dist/hooks/events.js +0 -3
- package/dist/hooks/goal-stop-hook.d.ts +44 -2
- package/dist/hooks/goal-stop-hook.js +775 -52
- package/dist/hooks/registry.js +3 -0
- package/dist/hooks/shell-runner.d.ts +12 -1
- package/dist/hooks/shell-runner.js +160 -9
- package/dist/index.d.ts +7 -6
- package/dist/index.js +6 -5
- package/dist/llm/client-base.js +12 -10
- package/dist/llm/types.d.ts +12 -5
- package/dist/plugins/pluginCommandHook.d.ts +4 -4
- package/dist/plugins/pluginCommandHook.js +111 -13
- package/dist/preset/index.js +14 -4
- package/dist/protocol/chat-session-manager.d.ts +13 -2
- package/dist/protocol/chat-session-manager.js +90 -18
- package/dist/protocol/chat-session.d.ts +12 -0
- package/dist/protocol/chat-session.js +30 -5
- package/dist/protocol/client.d.ts +5 -2
- package/dist/protocol/client.js +22 -1
- package/dist/protocol/server.d.ts +25 -11
- package/dist/protocol/server.js +291 -73
- package/dist/protocol/types.d.ts +36 -2
- package/dist/protocol/types.js +2 -0
- package/dist/services/dream-consolidation.d.ts +3 -0
- package/dist/services/dream-consolidation.js +4 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/oauth.d.ts +34 -10
- package/dist/services/oauth.js +233 -98
- package/dist/session/session-manager.d.ts +35 -6
- package/dist/session/session-manager.js +396 -27
- package/dist/session/transcript.d.ts +30 -1
- package/dist/session/transcript.js +119 -4
- package/dist/tool-system/builtin/agent-notifications.d.ts +11 -4
- package/dist/tool-system/builtin/agent-notifications.js +19 -7
- package/dist/tool-system/builtin/agent.js +5 -1
- package/dist/tool-system/builtin/arena.js +1 -0
- package/dist/tool-system/builtin/background-jobs.d.ts +28 -5
- package/dist/tool-system/builtin/background-jobs.js +109 -7
- package/dist/tool-system/builtin/background-work.d.ts +6 -1
- package/dist/tool-system/builtin/background-work.js +5 -1
- package/dist/tool-system/builtin/bash.d.ts +3 -5
- package/dist/tool-system/builtin/bash.js +10 -5
- package/dist/tool-system/builtin/browser-tools.d.ts +2 -2
- package/dist/tool-system/builtin/cron-list.definition.d.ts +3 -0
- package/dist/tool-system/builtin/cron-list.definition.js +6 -0
- package/dist/tool-system/builtin/cron.d.ts +1 -2
- package/dist/tool-system/builtin/cron.js +9 -7
- package/dist/tool-system/builtin/drive-claude-code.d.ts +7 -0
- package/dist/tool-system/builtin/drive-claude-code.js +307 -20
- package/dist/tool-system/builtin/edit.d.ts +2 -1
- package/dist/tool-system/builtin/edit.js +12 -4
- package/dist/tool-system/builtin/generate-video.d.ts +4 -0
- package/dist/tool-system/builtin/generate-video.js +138 -21
- package/dist/tool-system/builtin/glob.d.ts +2 -1
- package/dist/tool-system/builtin/glob.js +28 -3
- package/dist/tool-system/builtin/grep.d.ts +1 -0
- package/dist/tool-system/builtin/grep.js +82 -17
- package/dist/tool-system/builtin/index.d.ts +25 -11
- package/dist/tool-system/builtin/index.js +60 -5
- package/dist/tool-system/builtin/sleep.d.ts +1 -2
- package/dist/tool-system/builtin/sleep.definition.d.ts +8 -0
- package/dist/tool-system/builtin/sleep.definition.js +28 -0
- package/dist/tool-system/builtin/sleep.js +1 -22
- package/dist/tool-system/builtin/video-providers.d.ts +12 -15
- package/dist/tool-system/builtin/video-providers.js +1 -0
- package/dist/tool-system/builtin/view-image.d.ts +2 -2
- package/dist/tool-system/builtin/web-fetch.js +44 -3
- package/dist/tool-system/builtin/worktree.js +25 -7
- package/dist/tool-system/builtin/write.d.ts +2 -1
- package/dist/tool-system/builtin/write.js +14 -4
- package/dist/tool-system/context.d.ts +35 -5
- package/dist/tool-system/executor.js +24 -8
- package/dist/tool-system/mcp-manager.d.ts +20 -2
- package/dist/tool-system/mcp-manager.js +111 -12
- package/dist/tool-system/path-policy.d.ts +19 -0
- package/dist/tool-system/path-policy.js +62 -1
- package/dist/tool-system/permission.d.ts +43 -3
- package/dist/tool-system/permission.js +383 -30
- package/dist/tool-system/registry.d.ts +3 -2
- package/dist/tool-system/registry.js +52 -34
- package/dist/types.d.ts +38 -7
- package/package.json +1 -1
package/dist/engine/turn-loop.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { initialTurnState, newTurnId } from "./turn-state.js";
|
|
8
8
|
import { formatFriendlyError } from "./friendly-error.js";
|
|
9
|
+
import { projectGoalJudgeToolResult, } from "../hooks/goal-stop-hook.js";
|
|
9
10
|
import { wrapHookMessages } from "../hooks/inject.js";
|
|
10
11
|
import { ContextLimitError } from "../exceptions.js";
|
|
11
12
|
import { logger } from "../logging/logger.js";
|
|
@@ -19,7 +20,7 @@ import { COMPLETE_GOAL_TOOL_NAME } from "../tool-system/builtin/complete-goal.js
|
|
|
19
20
|
import { CANCEL_GOAL_TOOL_NAME } from "../tool-system/builtin/cancel-goal.js";
|
|
20
21
|
import { redactSensitiveToolResultsInMessages, toolResultForDisplay, toolResultTranscriptText, toolResultsForDisplay, } from "../tool-system/tool-result-redaction.js";
|
|
21
22
|
import { addTokenUsage, cacheHitRateFromUsage, cumulativeCacheHitRate, } from "./session-usage.js";
|
|
22
|
-
import { createGoalBudgetTracker, recordGoalUsage,
|
|
23
|
+
import { createGoalBudgetTracker, recordGoalUsage, goalBudgetTerminationReason, applyGoalExtension, limitProximity, GOAL_DEFAULT_MAX_STOP_BLOCKS, } from "./goal.js";
|
|
23
24
|
/**
|
|
24
25
|
* 把一个 ToolResult 映射成发给 LLM 的 tool_result ContentBlock。
|
|
25
26
|
* 有 contentBlocks(view_image 的图片块)就原样用作 content;否则
|
|
@@ -62,6 +63,11 @@ export class TurnLoop {
|
|
|
62
63
|
currentCumulativeUsage;
|
|
63
64
|
sensitiveToolResultRedactions = new Map();
|
|
64
65
|
pendingImageMessages = new Set();
|
|
66
|
+
volatileContextMessages = new Set();
|
|
67
|
+
/** Last outer-loop turn for which the model returned a complete response. */
|
|
68
|
+
lastCompletedModelTurn = 0;
|
|
69
|
+
/** Last model turn whose transcript/persistence boundary was finalized. */
|
|
70
|
+
lastFinalizedTurn = 0;
|
|
65
71
|
/**
|
|
66
72
|
* Consecutive on_stop blocks (Goal mode kept the agent going). Reset to 0
|
|
67
73
|
* on any unblocked completion. When it reaches config.maxStopBlocks the
|
|
@@ -126,6 +132,20 @@ export class TurnLoop {
|
|
|
126
132
|
}
|
|
127
133
|
return { ...next, maxStopBlocks: nextCap };
|
|
128
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Private Goal-judge accounting seam. Judge calls bypass ModelFacade, so the
|
|
137
|
+
* Engine forwards their provider usage here while the run-scoped tracker is
|
|
138
|
+
* live. The returned reason lets the hook suppress verdict side effects when
|
|
139
|
+
* this very request crossed a hard token/time budget.
|
|
140
|
+
*/
|
|
141
|
+
recordGoalJudgeUsage(usage) {
|
|
142
|
+
if (!this.goalTracker)
|
|
143
|
+
return undefined;
|
|
144
|
+
if (usage) {
|
|
145
|
+
recordGoalUsage(this.goalTracker, (usage.promptTokens ?? 0) + (usage.completionTokens ?? 0));
|
|
146
|
+
}
|
|
147
|
+
return goalBudgetTerminationReason(this.goalTracker, Date.now());
|
|
148
|
+
}
|
|
129
149
|
/**
|
|
130
150
|
* Goal mode only: if the run is nearing EITHER stop ceiling (maxTurns or
|
|
131
151
|
* maxStopBlocks) and we haven't announced it yet, emit one approaching_limit
|
|
@@ -160,6 +180,9 @@ export class TurnLoop {
|
|
|
160
180
|
for (const msg of this.config.freshImageMessages ?? []) {
|
|
161
181
|
this.pendingImageMessages.add(msg);
|
|
162
182
|
}
|
|
183
|
+
for (const msg of this.config.volatileContextMessages ?? []) {
|
|
184
|
+
this.volatileContextMessages.add(msg);
|
|
185
|
+
}
|
|
163
186
|
// Wrap onStream so a single throwing handler can't silently break
|
|
164
187
|
// the channel for the rest of the run. A 2026-05-25 incident saw a
|
|
165
188
|
// sub-agent's events stop reaching the renderer ~23s into its run —
|
|
@@ -212,6 +235,21 @@ export class TurnLoop {
|
|
|
212
235
|
return;
|
|
213
236
|
this.deps.transcript.appendTurnStopped();
|
|
214
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Close one completed model turn exactly once. The transcript boundary must
|
|
240
|
+
* be durable before the Engine flushes usage, heartbeat, and turnCount via
|
|
241
|
+
* onTurnBoundary, so crash recovery never folds two assistant responses into
|
|
242
|
+
* the same transcript turn or observes state ahead of the transcript.
|
|
243
|
+
*/
|
|
244
|
+
finalizeModelTurn() {
|
|
245
|
+
if (this.lastCompletedModelTurn !== this.turnCount ||
|
|
246
|
+
this.lastFinalizedTurn === this.turnCount) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
this.lastFinalizedTurn = this.turnCount;
|
|
250
|
+
this.deps.transcript.appendTurnBoundary();
|
|
251
|
+
this.config.onTurnBoundary?.(this.turnCount);
|
|
252
|
+
}
|
|
215
253
|
prepareMessagesForModel(messages) {
|
|
216
254
|
const preserveMessages = this.pendingImageMessages.size > 0 ? this.pendingImageMessages : undefined;
|
|
217
255
|
const result = downgradeImagePayloadsInHistory(messages, { preserveMessages });
|
|
@@ -224,6 +262,23 @@ export class TurnLoop {
|
|
|
224
262
|
}
|
|
225
263
|
return result.messages;
|
|
226
264
|
}
|
|
265
|
+
stripVolatileContextMessages(messages) {
|
|
266
|
+
if (this.volatileContextMessages.size === 0)
|
|
267
|
+
return messages;
|
|
268
|
+
let changed = false;
|
|
269
|
+
const stripped = messages.filter((message) => {
|
|
270
|
+
const keep = !this.volatileContextMessages.has(message);
|
|
271
|
+
if (!keep)
|
|
272
|
+
changed = true;
|
|
273
|
+
return keep;
|
|
274
|
+
});
|
|
275
|
+
return changed ? stripped : messages;
|
|
276
|
+
}
|
|
277
|
+
appendVolatileContextMessages(messages) {
|
|
278
|
+
if (this.volatileContextMessages.size === 0)
|
|
279
|
+
return messages;
|
|
280
|
+
return [...this.stripVolatileContextMessages(messages), ...this.volatileContextMessages];
|
|
281
|
+
}
|
|
227
282
|
markPendingImagesConsumed(messages) {
|
|
228
283
|
if (this.pendingImageMessages.size === 0)
|
|
229
284
|
return messages;
|
|
@@ -307,6 +362,7 @@ export class TurnLoop {
|
|
|
307
362
|
recordResponseUsage(usage) {
|
|
308
363
|
this.currentTurnUsage = addTokenUsage(this.currentTurnUsage, usage);
|
|
309
364
|
this.currentCumulativeUsage = this.deps.recordCumulativeUsage?.(usage);
|
|
365
|
+
this.deps.recordCacheReadDiagnostics?.(usage);
|
|
310
366
|
}
|
|
311
367
|
emitCtxFromUsage(usage, messages) {
|
|
312
368
|
if (!this.config.onStream)
|
|
@@ -379,13 +435,20 @@ export class TurnLoop {
|
|
|
379
435
|
let messages = [...initialMessages];
|
|
380
436
|
let finalText = "";
|
|
381
437
|
const budgetTracker = createBudgetTracker();
|
|
438
|
+
// Recent execution evidence for the built-in Goal judge. Values enter this
|
|
439
|
+
// window only after irreversible sensitive-data removal, non-text omission,
|
|
440
|
+
// and per-item truncation at the current-round ToolResult boundary.
|
|
441
|
+
const goalToolResults = [];
|
|
442
|
+
const sensitiveGoalResultTools = this.config.goal
|
|
443
|
+
? new Set(this.deps.tools.filter((tool) => tool.sensitiveResult).map((tool) => tool.name))
|
|
444
|
+
: undefined;
|
|
382
445
|
// Goal-mode run-scoped budget tracker (P0). Null when no goal. Stamps a
|
|
383
446
|
// wall-clock start now and accumulates prompt+completion tokens across
|
|
384
447
|
// every turn; the guardrail below force-stops the run once any configured
|
|
385
448
|
// budget is blown — the unattended-safety backstop.
|
|
386
|
-
this.goalTracker
|
|
387
|
-
|
|
388
|
-
|
|
449
|
+
if (!this.goalTracker && this.config.goal) {
|
|
450
|
+
this.goalTracker = createGoalBudgetTracker(this.config.goal, Date.now());
|
|
451
|
+
}
|
|
389
452
|
const goalTracker = this.goalTracker;
|
|
390
453
|
// Fresh run: re-arm the approaching-limit announcement.
|
|
391
454
|
this.approachAnnounced = false;
|
|
@@ -398,6 +461,25 @@ export class TurnLoop {
|
|
|
398
461
|
// hook emits, guards) and surfaces them as a model_error result.
|
|
399
462
|
try {
|
|
400
463
|
while (this.turnCount < this.config.maxTurns) {
|
|
464
|
+
const preTurnGoalTermination = goalTracker
|
|
465
|
+
? goalBudgetTerminationReason(goalTracker, Date.now())
|
|
466
|
+
: undefined;
|
|
467
|
+
if (preTurnGoalTermination) {
|
|
468
|
+
this.config.onStream?.({
|
|
469
|
+
type: "assistant_message",
|
|
470
|
+
message: {
|
|
471
|
+
role: "assistant",
|
|
472
|
+
content: "(Goal 预算已耗尽,强制停止。)",
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
476
|
+
return {
|
|
477
|
+
text: finalText,
|
|
478
|
+
reason: "goal_budget_exhausted",
|
|
479
|
+
messages,
|
|
480
|
+
goalTermination: preTurnGoalTermination,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
401
483
|
this.turnCount++;
|
|
402
484
|
this.currentTurnUsage = {
|
|
403
485
|
promptTokens: 0,
|
|
@@ -425,7 +507,7 @@ export class TurnLoop {
|
|
|
425
507
|
// they join THIS step's request — no abort, no lost in-flight work. Same
|
|
426
508
|
// loop-top user-push pattern as turnStartInjection / turn-limit warnings
|
|
427
509
|
// below. Push to transcript too so they persist + survive resume.
|
|
428
|
-
this.consumeQueuedSteer(messages, "normal_step");
|
|
510
|
+
await this.consumeQueuedSteer(messages, "normal_step");
|
|
429
511
|
const state = initialTurnState(this.turnCount);
|
|
430
512
|
// Per-turn correlation ID. Every log written through `tlog` (or any
|
|
431
513
|
// child derived from it) is stamped with `turn` + `turnId`, so
|
|
@@ -487,6 +569,7 @@ export class TurnLoop {
|
|
|
487
569
|
// pendingImageMessages are preserved through this next model request.
|
|
488
570
|
const hasPendingSensitiveToolResults = this.sensitiveToolResultRedactions.size > 0;
|
|
489
571
|
messages = this.prepareMessagesForModel(messages);
|
|
572
|
+
messages = this.stripVolatileContextMessages(messages);
|
|
490
573
|
if (hasPendingSensitiveToolResults) {
|
|
491
574
|
tlog.info("turn.sensitive_tool_result_context_management_skipped", {
|
|
492
575
|
cat: "turn",
|
|
@@ -495,7 +578,7 @@ export class TurnLoop {
|
|
|
495
578
|
}
|
|
496
579
|
else {
|
|
497
580
|
// Context management (async — may trigger LLM summarization)
|
|
498
|
-
messages = await this.deps.contextManager.manageAsync(messages);
|
|
581
|
+
messages = await this.deps.contextManager.manageAsync(messages, this.config.signal);
|
|
499
582
|
// manageAsync can itself issue an LLM summarization call lasting several
|
|
500
583
|
// seconds; if the signal aborted during it, stop here rather than
|
|
501
584
|
// proceeding into the (expensive) main model call. Belt to the loop-top
|
|
@@ -531,6 +614,7 @@ export class TurnLoop {
|
|
|
531
614
|
messages.push(compactInjection);
|
|
532
615
|
}
|
|
533
616
|
}
|
|
617
|
+
messages = this.appendVolatileContextMessages(messages);
|
|
534
618
|
// Model call (with streaming fallback and max_output_tokens continuation)
|
|
535
619
|
// Track tool IDs streamed during this turn to avoid duplicate UI events
|
|
536
620
|
this.streamedToolIds.clear();
|
|
@@ -592,28 +676,50 @@ export class TurnLoop {
|
|
|
592
676
|
return { text: finalText, reason: "model_error", messages };
|
|
593
677
|
}
|
|
594
678
|
}
|
|
679
|
+
this.lastCompletedModelTurn = this.turnCount;
|
|
595
680
|
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
596
681
|
// Record the response once into current-turn and whole-session counters.
|
|
597
682
|
if (response.usage?.promptTokens !== undefined) {
|
|
598
683
|
this.recordResponseUsage(response.usage);
|
|
599
684
|
this.emitCtxFromUsage(response.usage, messages);
|
|
600
685
|
}
|
|
686
|
+
// Charge every provider response to the run-scoped Goal budget before
|
|
687
|
+
// any truncation path can retry or continue the outer turn loop.
|
|
688
|
+
if (goalTracker && response.usage) {
|
|
689
|
+
const used = (response.usage.promptTokens ?? 0) + (response.usage.completionTokens ?? 0);
|
|
690
|
+
recordGoalUsage(goalTracker, used);
|
|
691
|
+
}
|
|
692
|
+
let budgetTermination = goalTracker
|
|
693
|
+
? goalBudgetTerminationReason(goalTracker, Date.now())
|
|
694
|
+
: undefined;
|
|
601
695
|
// Feed actual token usage back to the context manager so subsequent
|
|
602
696
|
// compaction decisions use hybrid (actual + delta) estimation rather than
|
|
603
697
|
// pure heuristics. Without this the manager falls back to char/4 estimates.
|
|
604
698
|
if (response.usage?.promptTokens !== undefined) {
|
|
605
|
-
const
|
|
699
|
+
const contextAnchorMessages = this.stripVolatileContextMessages(messages);
|
|
700
|
+
const anchor = this.deps.contextManager.recordActualUsage(response.usage.promptTokens, contextAnchorMessages.length, contextAnchorMessages);
|
|
606
701
|
if (anchor)
|
|
607
702
|
this.deps.recordContextUsageAnchor?.(anchor);
|
|
608
703
|
}
|
|
609
704
|
messages = this.markPendingImagesConsumed(messages);
|
|
705
|
+
// stopBlockCount guards only the no-tool final-answer loop evaluated by
|
|
706
|
+
// on_stop (text -> block -> text -> block). A response that structurally
|
|
707
|
+
// contains tool_use has left that streak, regardless of whether the call
|
|
708
|
+
// will execute successfully. Reset here, before truncated-tool and later
|
|
709
|
+
// queued-steer paths can continue the outer loop. Repeated ineffective
|
|
710
|
+
// tool_use turns still consume turns and are bounded by maxTurns.
|
|
711
|
+
if (response.toolCalls.length > 0) {
|
|
712
|
+
this.stopBlockCount = 0;
|
|
713
|
+
}
|
|
610
714
|
// Truncation that cut off a TOOL CALL: the model overflowed
|
|
611
715
|
// max_output_tokens mid tool-call, so the arg JSON is incomplete (e.g. a
|
|
612
716
|
// Write whose `content` was clipped, leaving file_path unset). Executing
|
|
613
717
|
// it raised a misleading "Missing required parameter: file_path". Instead,
|
|
614
718
|
// tell the model its output was truncated and let the next turn retry —
|
|
615
719
|
// bounded by the outer maxTurns loop.
|
|
616
|
-
if (
|
|
720
|
+
if (!budgetTermination &&
|
|
721
|
+
isTruncatedStop(response.stopReason) &&
|
|
722
|
+
response.toolCalls.length > 0) {
|
|
617
723
|
tlog.info("turn.truncated_tool_call", {
|
|
618
724
|
cat: "turn",
|
|
619
725
|
toolCount: response.toolCalls.length,
|
|
@@ -625,13 +731,15 @@ export class TurnLoop {
|
|
|
625
731
|
role: "user",
|
|
626
732
|
content: "<system-reminder>Your previous response was truncated by the max output token limit before the tool call finished, so its arguments are incomplete. Do not assume it ran. Either retry with a smaller/more focused tool call (e.g. write the file in sections via Edit), or raise this model's maxOutputTokens.</system-reminder>",
|
|
627
733
|
});
|
|
734
|
+
this.finalizeModelTurn();
|
|
628
735
|
continue;
|
|
629
736
|
}
|
|
630
737
|
// Handle max_output_tokens: if response was truncated, do continuation
|
|
631
738
|
// (up to 3 times). Truncation is reported as finish_reason "length"
|
|
632
739
|
// (OpenAI) or stop_reason "max_tokens" (Anthropic) — isTruncatedStop
|
|
633
740
|
// accepts both, so the OpenAI streaming path triggers continuation too.
|
|
634
|
-
if (
|
|
741
|
+
if (!budgetTermination &&
|
|
742
|
+
isTruncatedStop(response.stopReason) &&
|
|
635
743
|
response.toolCalls.length === 0 &&
|
|
636
744
|
response.text) {
|
|
637
745
|
let combinedText = response.text;
|
|
@@ -642,6 +750,11 @@ export class TurnLoop {
|
|
|
642
750
|
// more model calls, emitting text after Stop.
|
|
643
751
|
if (this.config.signal?.aborted)
|
|
644
752
|
break;
|
|
753
|
+
budgetTermination = goalTracker
|
|
754
|
+
? goalBudgetTerminationReason(goalTracker, Date.now())
|
|
755
|
+
: undefined;
|
|
756
|
+
if (budgetTermination)
|
|
757
|
+
break;
|
|
645
758
|
tlog.info("turn.max_tokens_continuation", { cat: "turn", retry: retry + 1 });
|
|
646
759
|
const contMessages = [
|
|
647
760
|
...messages,
|
|
@@ -653,18 +766,40 @@ export class TurnLoop {
|
|
|
653
766
|
];
|
|
654
767
|
try {
|
|
655
768
|
const contResponse = await this.deps.model.call(this.deps.systemPrompt, this.prepareMessagesForModel(contMessages), this.deps.tools, this.config.onStream, this.config.signal);
|
|
769
|
+
// Continuations are separate provider responses, so preserve the
|
|
770
|
+
// same structural tool_use invariant before processing this one.
|
|
771
|
+
if (contResponse.toolCalls.length > 0) {
|
|
772
|
+
this.stopBlockCount = 0;
|
|
773
|
+
}
|
|
656
774
|
if (contResponse.usage?.promptTokens !== undefined) {
|
|
657
775
|
this.recordResponseUsage(contResponse.usage);
|
|
658
776
|
continuedResponse = true;
|
|
659
777
|
}
|
|
778
|
+
if (goalTracker && contResponse.usage) {
|
|
779
|
+
const used = (contResponse.usage.promptTokens ?? 0) +
|
|
780
|
+
(contResponse.usage.completionTokens ?? 0);
|
|
781
|
+
recordGoalUsage(goalTracker, used);
|
|
782
|
+
}
|
|
783
|
+
budgetTermination = goalTracker
|
|
784
|
+
? goalBudgetTerminationReason(goalTracker, Date.now())
|
|
785
|
+
: undefined;
|
|
660
786
|
combinedText += contResponse.text;
|
|
661
|
-
if (
|
|
787
|
+
if (budgetTermination ||
|
|
788
|
+
!isTruncatedStop(contResponse.stopReason) ||
|
|
789
|
+
contResponse.toolCalls.length > 0) {
|
|
662
790
|
response = { ...contResponse, text: combinedText };
|
|
663
791
|
break;
|
|
664
792
|
}
|
|
665
793
|
}
|
|
666
|
-
catch {
|
|
667
|
-
|
|
794
|
+
catch (err) {
|
|
795
|
+
if (isAbortError(err) || this.config.signal?.aborted) {
|
|
796
|
+
this.markStopped();
|
|
797
|
+
this.finalizeModelTurn();
|
|
798
|
+
return { text: finalText, reason: "aborted_streaming", messages };
|
|
799
|
+
}
|
|
800
|
+
this.config.onStream?.({ type: "error", error: formatFriendlyError(err) });
|
|
801
|
+
this.finalizeModelTurn();
|
|
802
|
+
return { text: finalText, reason: "model_error", messages };
|
|
668
803
|
}
|
|
669
804
|
}
|
|
670
805
|
response = { ...response, text: combinedText };
|
|
@@ -672,16 +807,10 @@ export class TurnLoop {
|
|
|
672
807
|
this.emitCtxFromUsage(response.usage, messages);
|
|
673
808
|
}
|
|
674
809
|
}
|
|
675
|
-
// Goal-mode run-scoped accounting: add this turn's total token usage
|
|
676
|
-
// (prompt + completion) to the running total. Done after continuation so
|
|
677
|
-
// continued output is counted against the budget.
|
|
678
|
-
if (goalTracker && response.usage) {
|
|
679
|
-
const used = (response.usage.promptTokens ?? 0) + (response.usage.completionTokens ?? 0);
|
|
680
|
-
recordGoalUsage(goalTracker, used);
|
|
681
|
-
}
|
|
682
810
|
// Aborted?
|
|
683
811
|
if (this.config.signal?.aborted) {
|
|
684
812
|
this.markStopped();
|
|
813
|
+
this.finalizeModelTurn();
|
|
685
814
|
return { text: finalText, reason: "aborted_streaming", messages };
|
|
686
815
|
}
|
|
687
816
|
// Accumulate text
|
|
@@ -692,7 +821,10 @@ export class TurnLoop {
|
|
|
692
821
|
// is force-stopped regardless of what the model wants to do next (stop OR
|
|
693
822
|
// continue with tool calls). This is the unattended-safety backstop, so
|
|
694
823
|
// it sits BEFORE the "tool calls?" branch — both paths pass the gate.
|
|
695
|
-
|
|
824
|
+
budgetTermination = goalTracker
|
|
825
|
+
? (budgetTermination ?? goalBudgetTerminationReason(goalTracker, Date.now()))
|
|
826
|
+
: undefined;
|
|
827
|
+
if (goalTracker && budgetTermination) {
|
|
696
828
|
tlog.info("turn.goal_budget_exhausted", {
|
|
697
829
|
cat: "goal",
|
|
698
830
|
tokensUsed: goalTracker.tokensUsed,
|
|
@@ -707,7 +839,13 @@ export class TurnLoop {
|
|
|
707
839
|
content: "(Goal 预算已耗尽,强制停止。)",
|
|
708
840
|
},
|
|
709
841
|
});
|
|
710
|
-
|
|
842
|
+
this.finalizeModelTurn();
|
|
843
|
+
return {
|
|
844
|
+
text: finalText,
|
|
845
|
+
reason: "goal_budget_exhausted",
|
|
846
|
+
messages,
|
|
847
|
+
goalTermination: budgetTermination,
|
|
848
|
+
};
|
|
711
849
|
}
|
|
712
850
|
// Post-check: tool calls?
|
|
713
851
|
if (response.toolCalls.length === 0) {
|
|
@@ -722,7 +860,8 @@ export class TurnLoop {
|
|
|
722
860
|
hasToolUse: false,
|
|
723
861
|
});
|
|
724
862
|
messages.push({ role: "assistant", content: finalText });
|
|
725
|
-
|
|
863
|
+
this.finalizeModelTurn();
|
|
864
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
726
865
|
continue;
|
|
727
866
|
}
|
|
728
867
|
// on_stop seam: the model wants to stop. Give handlers (Goal mode)
|
|
@@ -731,11 +870,79 @@ export class TurnLoop {
|
|
|
731
870
|
// we run another turn instead of returning. Bounded by
|
|
732
871
|
// maxStopBlocks (consecutive) and the outer maxTurns ceiling.
|
|
733
872
|
const maxStopBlocks = this.config.maxStopBlocks ?? GOAL_DEFAULT_MAX_STOP_BLOCKS;
|
|
873
|
+
if (goalTracker) {
|
|
874
|
+
this.deps.updateGoalJudgeContext?.({
|
|
875
|
+
toolResults: goalToolResults.map((item) => ({
|
|
876
|
+
...item,
|
|
877
|
+
})),
|
|
878
|
+
progress: {
|
|
879
|
+
turnCount: this.turnCount,
|
|
880
|
+
stopRound: this.stopBlockCount + 1,
|
|
881
|
+
elapsedMs: Math.max(0, Date.now() - goalTracker.startedAtMs),
|
|
882
|
+
tokensUsed: goalTracker.tokensUsed,
|
|
883
|
+
tokenBudget: goalTracker.goal.tokenBudget,
|
|
884
|
+
timeBudgetMs: goalTracker.goal.timeBudgetMs,
|
|
885
|
+
maxTurns: this.config.maxTurns,
|
|
886
|
+
maxStopBlocks,
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
}
|
|
734
890
|
const stopHook = await this.emitHook("on_stop", {
|
|
735
891
|
goal: this.config.goal,
|
|
736
892
|
finalText,
|
|
737
893
|
turnCount: this.turnCount,
|
|
738
894
|
});
|
|
895
|
+
// The primary-model judge is a real billed request. Its hook forwards
|
|
896
|
+
// usage through recordGoalJudgeUsage above; re-check immediately so an
|
|
897
|
+
// over-budget verdict cannot authorize another main-model turn.
|
|
898
|
+
const hookGoalTermination = stopHook.goalTermination;
|
|
899
|
+
const hookBudgetTermination = hookGoalTermination === "token_budget_exhausted" ||
|
|
900
|
+
hookGoalTermination === "time_budget_exhausted"
|
|
901
|
+
? hookGoalTermination
|
|
902
|
+
: undefined;
|
|
903
|
+
const postJudgeBudgetTermination = goalTracker
|
|
904
|
+
? (hookBudgetTermination ?? goalBudgetTerminationReason(goalTracker, Date.now()))
|
|
905
|
+
: undefined;
|
|
906
|
+
if (goalTracker && postJudgeBudgetTermination) {
|
|
907
|
+
tlog.info("turn.goal_budget_exhausted_after_judge", {
|
|
908
|
+
cat: "goal",
|
|
909
|
+
reason: postJudgeBudgetTermination,
|
|
910
|
+
tokensUsed: goalTracker.tokensUsed,
|
|
911
|
+
tokenBudget: goalTracker.goal.tokenBudget,
|
|
912
|
+
timeBudgetMs: goalTracker.goal.timeBudgetMs,
|
|
913
|
+
});
|
|
914
|
+
this.config.onStream?.({
|
|
915
|
+
type: "assistant_message",
|
|
916
|
+
messageId: assistantMessageId,
|
|
917
|
+
message: {
|
|
918
|
+
role: "assistant",
|
|
919
|
+
content: "(Goal 预算已耗尽,强制停止。)",
|
|
920
|
+
},
|
|
921
|
+
});
|
|
922
|
+
return {
|
|
923
|
+
text: finalText,
|
|
924
|
+
reason: "goal_budget_exhausted",
|
|
925
|
+
messages,
|
|
926
|
+
goalTermination: postJudgeBudgetTermination,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
if (goalTracker && hookGoalTermination === "judge_prompt_too_large") {
|
|
930
|
+
tlog.warn("turn.goal_judge_prompt_too_large", { cat: "goal" });
|
|
931
|
+
this.config.onStream?.({
|
|
932
|
+
type: "assistant_message",
|
|
933
|
+
messageId: assistantMessageId,
|
|
934
|
+
message: {
|
|
935
|
+
role: "assistant",
|
|
936
|
+
content: "(Goal 裁判输入超过安全上限,无法判定;本次已停止,目标已保留。)",
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
return {
|
|
940
|
+
text: finalText,
|
|
941
|
+
reason: "completed",
|
|
942
|
+
messages,
|
|
943
|
+
goalTermination: hookGoalTermination,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
739
946
|
// The judge's structured verdict (set by GoalStopHook in result.data)
|
|
740
947
|
// rides back here so we can show goal progress WITHOUT a second LLM
|
|
741
948
|
// call — `gaps` is whatever the judge already computed.
|
|
@@ -775,6 +982,7 @@ export class TurnLoop {
|
|
|
775
982
|
});
|
|
776
983
|
continue;
|
|
777
984
|
}
|
|
985
|
+
let goalTermination;
|
|
778
986
|
if (stopHook.continueSession && this.stopBlockCount >= maxStopBlocks) {
|
|
779
987
|
// Cap hit: stop anyway, but tell the user why we're not looping
|
|
780
988
|
// forever on an unsatisfiable goal.
|
|
@@ -788,6 +996,7 @@ export class TurnLoop {
|
|
|
788
996
|
status: "exhausted",
|
|
789
997
|
round: this.stopBlockCount,
|
|
790
998
|
});
|
|
999
|
+
goalTermination = "stop_blocks_exhausted";
|
|
791
1000
|
this.config.onStream?.({
|
|
792
1001
|
type: "assistant_message",
|
|
793
1002
|
messageId: assistantMessageId,
|
|
@@ -807,11 +1016,11 @@ export class TurnLoop {
|
|
|
807
1016
|
});
|
|
808
1017
|
}
|
|
809
1018
|
this.stopBlockCount = 0;
|
|
810
|
-
if (this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
1019
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
811
1020
|
continue;
|
|
812
1021
|
}
|
|
813
1022
|
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
814
|
-
return { text: finalText, reason: "completed", messages };
|
|
1023
|
+
return { text: finalText, reason: "completed", messages, goalTermination };
|
|
815
1024
|
}
|
|
816
1025
|
// Tool execution phase
|
|
817
1026
|
tlog.info("turn.tool_use", {
|
|
@@ -854,6 +1063,16 @@ export class TurnLoop {
|
|
|
854
1063
|
// Record results in transcript and stream
|
|
855
1064
|
const resultBlocks = [];
|
|
856
1065
|
for (const result of results) {
|
|
1066
|
+
if (goalTracker) {
|
|
1067
|
+
goalToolResults.push(projectGoalJudgeToolResult(result, this.turnCount, sensitiveGoalResultTools?.has(result.toolName) === true));
|
|
1068
|
+
// Never evict the front of one legal batch: the default executor may
|
|
1069
|
+
// return 25 results in a turn. Keep at least maxToolCallsPerTurn so
|
|
1070
|
+
// the judge renderer can preserve metadata for the entire newest batch.
|
|
1071
|
+
const residencyLimit = Math.max(20, this.config.maxToolCallsPerTurn);
|
|
1072
|
+
if (goalToolResults.length > residencyLimit) {
|
|
1073
|
+
goalToolResults.splice(0, goalToolResults.length - residencyLimit);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
857
1076
|
resultBlocks.push(toolResultToBlock(result));
|
|
858
1077
|
const streamResult = toolResultForDisplay(result);
|
|
859
1078
|
const transcriptResult = toolResultTranscriptText(result);
|
|
@@ -930,7 +1149,8 @@ export class TurnLoop {
|
|
|
930
1149
|
tlog.info("turn.goal_self_reported_complete", { cat: "goal" });
|
|
931
1150
|
this.stopBlockCount = 0;
|
|
932
1151
|
this.deps.clearPersistedGoal?.();
|
|
933
|
-
|
|
1152
|
+
this.finalizeModelTurn();
|
|
1153
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
934
1154
|
continue;
|
|
935
1155
|
}
|
|
936
1156
|
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
@@ -946,7 +1166,8 @@ export class TurnLoop {
|
|
|
946
1166
|
tlog.info("turn.goal_user_cancelled", { cat: "goal" });
|
|
947
1167
|
this.stopBlockCount = 0;
|
|
948
1168
|
this.deps.clearPersistedGoal?.();
|
|
949
|
-
|
|
1169
|
+
this.finalizeModelTurn();
|
|
1170
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
950
1171
|
continue;
|
|
951
1172
|
}
|
|
952
1173
|
return { text: finalText, reason: "completed", messages };
|
|
@@ -966,7 +1187,8 @@ export class TurnLoop {
|
|
|
966
1187
|
message: { role: "assistant", content: finalText },
|
|
967
1188
|
});
|
|
968
1189
|
messages.push({ role: "assistant", content: finalText });
|
|
969
|
-
|
|
1190
|
+
this.finalizeModelTurn();
|
|
1191
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
970
1192
|
continue;
|
|
971
1193
|
}
|
|
972
1194
|
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
@@ -1008,9 +1230,7 @@ export class TurnLoop {
|
|
|
1008
1230
|
hasToolUse: true,
|
|
1009
1231
|
toolCallCount: toolCalls.length,
|
|
1010
1232
|
});
|
|
1011
|
-
|
|
1012
|
-
this.deps.transcript.appendTurnBoundary();
|
|
1013
|
-
this.config.onTurnBoundary?.(this.turnCount);
|
|
1233
|
+
this.finalizeModelTurn();
|
|
1014
1234
|
tlog.info("turn.end", {
|
|
1015
1235
|
cat: "turn",
|
|
1016
1236
|
duration_ms: Date.now() - turnStartedAt,
|
|
@@ -1025,6 +1245,9 @@ export class TurnLoop {
|
|
|
1025
1245
|
// engine's post-run saveState records model_error instead of leaving
|
|
1026
1246
|
// the session stuck at "active".
|
|
1027
1247
|
this.patchOrphanedToolUses(messages);
|
|
1248
|
+
// If the model already returned, this is still a completed model turn.
|
|
1249
|
+
// The idempotent helper is a no-op when its normal branch finalized first.
|
|
1250
|
+
this.finalizeModelTurn();
|
|
1028
1251
|
// A user-initiated Stop also bubbles here (an AbortError thrown from the
|
|
1029
1252
|
// per-turn scaffolding). Treat it as a clean abort, not a model_error —
|
|
1030
1253
|
// no error event, so the UI shows only the "你停止了本轮" line.
|
|
@@ -1048,7 +1271,7 @@ export class TurnLoop {
|
|
|
1048
1271
|
maxTurns: this.config.maxTurns,
|
|
1049
1272
|
turnCount: this.turnCount,
|
|
1050
1273
|
});
|
|
1051
|
-
this.consumeQueuedSteer(messages, "finalize_backfill");
|
|
1274
|
+
await this.consumeQueuedSteer(messages, "finalize_backfill");
|
|
1052
1275
|
const hasPendingSensitiveToolResults = this.sensitiveToolResultRedactions.size > 0;
|
|
1053
1276
|
messages = this.prepareMessagesForModel(messages);
|
|
1054
1277
|
if (hasPendingSensitiveToolResults) {
|
|
@@ -1069,12 +1292,28 @@ export class TurnLoop {
|
|
|
1069
1292
|
try {
|
|
1070
1293
|
const summaryResponse = await this.deps.model.call(this.deps.systemPrompt, this.prepareMessagesForModel(messages), [], // No tools available for summary turn
|
|
1071
1294
|
this.config.onStream, this.config.signal, this.modelCallRecordingOptions());
|
|
1295
|
+
if (summaryResponse.usage?.promptTokens !== undefined) {
|
|
1296
|
+
this.recordResponseUsage(summaryResponse.usage);
|
|
1297
|
+
}
|
|
1298
|
+
if (goalTracker && summaryResponse.usage) {
|
|
1299
|
+
recordGoalUsage(goalTracker, (summaryResponse.usage.promptTokens ?? 0) + (summaryResponse.usage.completionTokens ?? 0));
|
|
1300
|
+
}
|
|
1301
|
+
if (this.config.signal?.aborted) {
|
|
1302
|
+
this.markStopped();
|
|
1303
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
1304
|
+
return { text: finalText, reason: "aborted_streaming", messages };
|
|
1305
|
+
}
|
|
1072
1306
|
messages = this.markPendingImagesConsumed(messages);
|
|
1073
1307
|
if (summaryResponse.text) {
|
|
1074
1308
|
finalText = summaryResponse.text;
|
|
1075
1309
|
}
|
|
1076
1310
|
}
|
|
1077
|
-
catch {
|
|
1311
|
+
catch (err) {
|
|
1312
|
+
if (isAbortError(err) || this.config.signal?.aborted) {
|
|
1313
|
+
this.markStopped();
|
|
1314
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
1315
|
+
return { text: finalText, reason: "aborted_streaming", messages };
|
|
1316
|
+
}
|
|
1078
1317
|
// If even summary fails, just return what we have
|
|
1079
1318
|
this.currentTurnLog.warn("turn.summary_failed", { cat: "turn" });
|
|
1080
1319
|
}
|
|
@@ -1086,7 +1325,12 @@ export class TurnLoop {
|
|
|
1086
1325
|
messages.push({ role: "assistant", content: finalText });
|
|
1087
1326
|
}
|
|
1088
1327
|
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
1089
|
-
return {
|
|
1328
|
+
return {
|
|
1329
|
+
text: finalText,
|
|
1330
|
+
reason: "max_turns",
|
|
1331
|
+
messages,
|
|
1332
|
+
...(this.config.goal ? { goalTermination: "max_turns_exhausted" } : {}),
|
|
1333
|
+
};
|
|
1090
1334
|
}
|
|
1091
1335
|
/**
|
|
1092
1336
|
* Call model with streaming fallback.
|
|
@@ -1168,10 +1412,12 @@ export class TurnLoop {
|
|
|
1168
1412
|
get currentTurn() {
|
|
1169
1413
|
return this.turnCount;
|
|
1170
1414
|
}
|
|
1171
|
-
consumeQueuedSteer(messages, source) {
|
|
1415
|
+
async consumeQueuedSteer(messages, source) {
|
|
1172
1416
|
const steered = this.deps.consumeSteer?.(source) ?? [];
|
|
1173
1417
|
let consumed = false;
|
|
1174
|
-
for (
|
|
1418
|
+
for (let index = 0; index < steered.length; index++) {
|
|
1419
|
+
const item = steered[index];
|
|
1420
|
+
const { id, text, clientMessageId } = item;
|
|
1175
1421
|
if (!text)
|
|
1176
1422
|
continue;
|
|
1177
1423
|
if (clientMessageId && this.deps.claimClientMessageId?.(clientMessageId, "steer") === false) {
|
|
@@ -1183,9 +1429,33 @@ export class TurnLoop {
|
|
|
1183
1429
|
});
|
|
1184
1430
|
continue;
|
|
1185
1431
|
}
|
|
1432
|
+
let content;
|
|
1433
|
+
try {
|
|
1434
|
+
content = this.deps.buildSteerUserMessageContent
|
|
1435
|
+
? await this.deps.buildSteerUserMessageContent(item)
|
|
1436
|
+
: text;
|
|
1437
|
+
}
|
|
1438
|
+
catch (err) {
|
|
1439
|
+
if (clientMessageId)
|
|
1440
|
+
this.deps.releaseClientMessageId?.(clientMessageId);
|
|
1441
|
+
const pendingSuffix = steered.slice(index);
|
|
1442
|
+
this.deps.restoreSteer?.(pendingSuffix);
|
|
1443
|
+
logger.warn("steer.prepare.failed_requeued", {
|
|
1444
|
+
clientMessageId,
|
|
1445
|
+
steerId: id,
|
|
1446
|
+
sessionId: this.deps.sessionId,
|
|
1447
|
+
source,
|
|
1448
|
+
restoredCount: pendingSuffix.length,
|
|
1449
|
+
error: err.message,
|
|
1450
|
+
});
|
|
1451
|
+
break;
|
|
1452
|
+
}
|
|
1186
1453
|
consumed = true;
|
|
1187
|
-
|
|
1188
|
-
|
|
1454
|
+
this.deps.setOriginClientMessageId?.(clientMessageId);
|
|
1455
|
+
const message = { role: "user", content };
|
|
1456
|
+
messages.push(message);
|
|
1457
|
+
this.trackFreshImageMessage(message);
|
|
1458
|
+
this.deps.transcript.appendMessage("user", content, { steerId: id, clientMessageId });
|
|
1189
1459
|
this.config.onStream?.({ type: "steer_injected", text, id });
|
|
1190
1460
|
}
|
|
1191
1461
|
return consumed;
|