@cjhyy/code-shell-core 0.6.0-rc.9 → 0.7.0
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/THIRD_PARTY_NOTICES.md +206 -0
- package/dist/automation/scheduler.d.ts +13 -7
- package/dist/automation/scheduler.js +116 -37
- package/dist/capability-control/service.d.ts +2 -0
- package/dist/capability-control/service.js +4 -2
- package/dist/cc-orchestrator/agent-adapter.d.ts +4 -0
- package/dist/cc-orchestrator/agent-adapter.js +11 -1
- package/dist/cc-orchestrator/codex-session-history.d.ts +14 -1
- package/dist/cc-orchestrator/codex-session-history.js +65 -5
- package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
- package/dist/cc-orchestrator/cwd-normalize.js +19 -0
- package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
- package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
- package/dist/cc-orchestrator/external-agent-changes.js +22 -5
- package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
- package/dist/cc-orchestrator/external-agent-driver.js +265 -50
- package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
- package/dist/cc-orchestrator/external-agent-session-store.js +146 -0
- package/dist/cc-orchestrator/session-history.d.ts +35 -0
- package/dist/cc-orchestrator/session-history.js +98 -15
- package/dist/cli/agent-server-stdio.js +9 -2
- package/dist/context/compaction.d.ts +8 -1
- package/dist/context/compaction.js +49 -4
- package/dist/context/manager.d.ts +16 -2
- package/dist/context/manager.js +103 -19
- package/dist/credentials/access.d.ts +57 -0
- package/dist/credentials/access.js +185 -0
- package/dist/credentials/index.d.ts +3 -1
- package/dist/credentials/index.js +2 -0
- package/dist/credentials/inject-credential-tool.d.ts +3 -1
- package/dist/credentials/inject-credential-tool.js +30 -11
- package/dist/credentials/oauth.d.ts +20 -0
- package/dist/credentials/oauth.js +114 -0
- package/dist/credentials/store.d.ts +1 -0
- package/dist/credentials/store.js +3 -1
- package/dist/credentials/types.d.ts +49 -3
- package/dist/credentials/use-credential-tool.d.ts +9 -1
- package/dist/credentials/use-credential-tool.js +58 -45
- package/dist/engine/engine.d.ts +23 -3
- package/dist/engine/engine.js +380 -255
- package/dist/engine/goal.d.ts +17 -0
- package/dist/engine/goal.js +16 -6
- package/dist/engine/image-policy.d.ts +6 -0
- package/dist/engine/image-policy.js +17 -6
- package/dist/engine/input-attachments.d.ts +13 -0
- package/dist/engine/input-attachments.js +398 -0
- package/dist/engine/model-facade.d.ts +5 -2
- package/dist/engine/model-facade.js +4 -4
- package/dist/engine/parse-task.d.ts +10 -0
- package/dist/engine/parse-task.js +5 -0
- package/dist/engine/run-image-input.d.ts +22 -0
- package/dist/engine/run-image-input.js +195 -0
- package/dist/engine/steer-queue.d.ts +3 -1
- package/dist/engine/steer-queue.js +10 -2
- package/dist/engine/streaming-tool-queue.d.ts +11 -7
- package/dist/engine/streaming-tool-queue.js +11 -7
- package/dist/engine/turn-loop.d.ts +37 -2
- package/dist/engine/turn-loop.js +228 -43
- package/dist/engine/types.d.ts +8 -0
- package/dist/git/worktree/crud.d.ts +69 -0
- package/dist/git/worktree/crud.js +206 -0
- package/dist/git/worktree/diff.d.ts +14 -0
- package/dist/git/worktree/diff.js +82 -0
- package/dist/git/worktree/git-exec.d.ts +7 -0
- package/dist/git/worktree/git-exec.js +51 -0
- package/dist/git/worktree/index.d.ts +5 -0
- package/dist/git/worktree/index.js +5 -0
- package/dist/git/worktree/query.d.ts +42 -0
- package/dist/git/worktree/query.js +121 -0
- package/dist/git/worktree/slug.d.ts +11 -0
- package/dist/git/worktree/slug.js +58 -0
- package/dist/git/worktree.d.ts +1 -84
- package/dist/git/worktree.js +5 -230
- package/dist/hooks/goal-stop-hook.d.ts +33 -1
- package/dist/hooks/goal-stop-hook.js +202 -34
- package/dist/index.d.ts +31 -28
- package/dist/index.js +27 -25
- package/dist/logging/logger.js +6 -6
- package/dist/logging/sanitize-messages.d.ts +10 -2
- package/dist/logging/sanitize-messages.js +21 -6
- package/dist/plugins/installer/checkUpdate.d.ts +4 -1
- package/dist/plugins/installer/checkUpdate.js +4 -2
- package/dist/plugins/installer/install.js +2 -0
- package/dist/plugins/installer/installFromSource.js +9 -1
- package/dist/plugins/installer/parseSource.d.ts +4 -1
- package/dist/plugins/installer/parseSource.js +28 -10
- package/dist/plugins/installer/sourcePath.d.ts +9 -0
- package/dist/plugins/installer/sourcePath.js +50 -0
- package/dist/plugins/installer/update.d.ts +4 -1
- package/dist/plugins/installer/update.js +5 -3
- package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
- package/dist/plugins/parseMarketplaceInput.js +4 -3
- package/dist/plugins/pluginInstaller.js +24 -22
- package/dist/preset/index.d.ts +1 -0
- package/dist/preset/index.js +30 -13
- package/dist/prompt/sections/base.md +1 -1
- package/dist/protocol/chat-session-manager.d.ts +2 -0
- package/dist/protocol/chat-session-manager.js +38 -2
- package/dist/protocol/chat-session.d.ts +3 -0
- package/dist/protocol/chat-session.js +1 -0
- package/dist/protocol/client.d.ts +9 -4
- package/dist/protocol/client.js +18 -1
- package/dist/protocol/server.d.ts +30 -0
- package/dist/protocol/server.js +247 -47
- package/dist/protocol/types.d.ts +49 -0
- package/dist/protocol/types.js +6 -0
- package/dist/run/FileRunStore.js +10 -1
- package/dist/run/Heartbeat.js +12 -0
- package/dist/run/RunApprovalBackend.d.ts +3 -0
- package/dist/run/RunApprovalBackend.js +41 -6
- package/dist/run/RunLock.js +2 -0
- package/dist/run/RunManager.d.ts +2 -0
- package/dist/run/RunManager.js +64 -24
- package/dist/run/ids.d.ts +2 -0
- package/dist/run/ids.js +23 -0
- package/dist/runtime/background-shell.d.ts +1 -0
- package/dist/runtime/background-shell.js +23 -13
- package/dist/runtime/spawn-common.js +10 -0
- package/dist/services/auto-dream.d.ts +15 -4
- package/dist/services/auto-dream.js +20 -20
- package/dist/services/dream-consolidation.d.ts +2 -3
- package/dist/services/dream-consolidation.js +65 -15
- package/dist/services/extract-memories.d.ts +14 -5
- package/dist/services/extract-memories.js +20 -3
- package/dist/services/global-dream-promotion.d.ts +23 -0
- package/dist/services/global-dream-promotion.js +112 -0
- package/dist/services/memory-orchestrator.js +347 -34
- package/dist/session/memory.d.ts +61 -18
- package/dist/session/memory.js +342 -79
- package/dist/session/session-manager.d.ts +35 -1
- package/dist/session/session-manager.js +224 -4
- package/dist/session/transcript.d.ts +1 -1
- package/dist/session/transcript.js +17 -5
- package/dist/settings/manager.d.ts +1 -0
- package/dist/settings/manager.js +87 -37
- package/dist/settings/schema-export.d.ts +2 -3
- package/dist/settings/schema-export.js +2 -3
- package/dist/settings/schema.d.ts +21 -0
- package/dist/settings/schema.js +12 -0
- package/dist/skills/scanner.d.ts +3 -2
- package/dist/skills/scanner.js +12 -9
- 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/background-jobs.d.ts +35 -6
- package/dist/tool-system/builtin/background-jobs.js +116 -7
- package/dist/tool-system/builtin/background-work.d.ts +17 -18
- package/dist/tool-system/builtin/background-work.js +51 -5
- package/dist/tool-system/builtin/config.d.ts +2 -1
- package/dist/tool-system/builtin/config.js +16 -11
- 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 +23 -2
- package/dist/tool-system/builtin/drive-claude-code.js +589 -48
- package/dist/tool-system/builtin/edit.js +5 -2
- package/dist/tool-system/builtin/generate-video.d.ts +1 -0
- package/dist/tool-system/builtin/generate-video.js +13 -4
- package/dist/tool-system/builtin/index.d.ts +8 -3
- package/dist/tool-system/builtin/index.js +50 -22
- package/dist/tool-system/builtin/lsp.d.ts +2 -1
- package/dist/tool-system/builtin/lsp.js +6 -3
- package/dist/tool-system/builtin/memory.js +40 -8
- package/dist/tool-system/builtin/notebook-edit.js +5 -2
- package/dist/tool-system/builtin/powershell.d.ts +5 -2
- package/dist/tool-system/builtin/powershell.js +11 -7
- package/dist/tool-system/builtin/read.js +118 -6
- 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/view-image.d.ts +2 -2
- package/dist/tool-system/builtin/view-image.js +109 -19
- package/dist/tool-system/builtin/worktree.d.ts +4 -4
- package/dist/tool-system/builtin/worktree.js +297 -74
- package/dist/tool-system/builtin/write.js +5 -3
- package/dist/tool-system/context.d.ts +41 -1
- package/dist/tool-system/executor.d.ts +1 -5
- package/dist/tool-system/executor.js +94 -115
- package/dist/tool-system/mcp-manager.d.ts +31 -6
- package/dist/tool-system/mcp-manager.js +193 -78
- package/dist/tool-system/path-policy.d.ts +2 -0
- package/dist/tool-system/path-policy.js +41 -12
- package/dist/tool-system/permission.d.ts +28 -7
- package/dist/tool-system/permission.js +130 -49
- package/dist/tool-system/registry.js +11 -4
- package/dist/tool-system/tool-result-redaction.d.ts +7 -0
- package/dist/tool-system/tool-result-redaction.js +48 -0
- package/dist/tool-system/workspace-bridge.d.ts +11 -0
- package/dist/tool-system/workspace-bridge.js +1 -0
- package/dist/types.d.ts +78 -11
- package/dist/utils/toolDisplay.js +1 -1
- package/package.json +4 -3
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";
|
|
@@ -17,8 +18,9 @@ import { isAbortError } from "../llm/client-base.js";
|
|
|
17
18
|
import { crossedReactiveThreshold } from "./reactive-threshold.js";
|
|
18
19
|
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";
|
|
21
|
+
import { redactSensitiveToolResultsInMessages, toolResultForDisplay, toolResultTranscriptText, toolResultsForDisplay, } from "../tool-system/tool-result-redaction.js";
|
|
20
22
|
import { addTokenUsage, cacheHitRateFromUsage, cumulativeCacheHitRate, } from "./session-usage.js";
|
|
21
|
-
import { createGoalBudgetTracker, recordGoalUsage,
|
|
23
|
+
import { createGoalBudgetTracker, recordGoalUsage, goalBudgetTerminationReason, applyGoalExtension, limitProximity, GOAL_DEFAULT_MAX_STOP_BLOCKS, } from "./goal.js";
|
|
22
24
|
/**
|
|
23
25
|
* 把一个 ToolResult 映射成发给 LLM 的 tool_result ContentBlock。
|
|
24
26
|
* 有 contentBlocks(view_image 的图片块)就原样用作 content;否则
|
|
@@ -59,7 +61,9 @@ export class TurnLoop {
|
|
|
59
61
|
cacheCreationTokens: 0,
|
|
60
62
|
};
|
|
61
63
|
currentCumulativeUsage;
|
|
64
|
+
sensitiveToolResultRedactions = new Map();
|
|
62
65
|
pendingImageMessages = new Set();
|
|
66
|
+
volatileContextMessages = new Set();
|
|
63
67
|
/**
|
|
64
68
|
* Consecutive on_stop blocks (Goal mode kept the agent going). Reset to 0
|
|
65
69
|
* on any unblocked completion. When it reaches config.maxStopBlocks the
|
|
@@ -158,6 +162,9 @@ export class TurnLoop {
|
|
|
158
162
|
for (const msg of this.config.freshImageMessages ?? []) {
|
|
159
163
|
this.pendingImageMessages.add(msg);
|
|
160
164
|
}
|
|
165
|
+
for (const msg of this.config.volatileContextMessages ?? []) {
|
|
166
|
+
this.volatileContextMessages.add(msg);
|
|
167
|
+
}
|
|
161
168
|
// Wrap onStream so a single throwing handler can't silently break
|
|
162
169
|
// the channel for the rest of the run. A 2026-05-25 incident saw a
|
|
163
170
|
// sub-agent's events stop reaching the renderer ~23s into its run —
|
|
@@ -222,6 +229,23 @@ export class TurnLoop {
|
|
|
222
229
|
}
|
|
223
230
|
return result.messages;
|
|
224
231
|
}
|
|
232
|
+
stripVolatileContextMessages(messages) {
|
|
233
|
+
if (this.volatileContextMessages.size === 0)
|
|
234
|
+
return messages;
|
|
235
|
+
let changed = false;
|
|
236
|
+
const stripped = messages.filter((message) => {
|
|
237
|
+
const keep = !this.volatileContextMessages.has(message);
|
|
238
|
+
if (!keep)
|
|
239
|
+
changed = true;
|
|
240
|
+
return keep;
|
|
241
|
+
});
|
|
242
|
+
return changed ? stripped : messages;
|
|
243
|
+
}
|
|
244
|
+
appendVolatileContextMessages(messages) {
|
|
245
|
+
if (this.volatileContextMessages.size === 0)
|
|
246
|
+
return messages;
|
|
247
|
+
return [...this.stripVolatileContextMessages(messages), ...this.volatileContextMessages];
|
|
248
|
+
}
|
|
225
249
|
markPendingImagesConsumed(messages) {
|
|
226
250
|
if (this.pendingImageMessages.size === 0)
|
|
227
251
|
return messages;
|
|
@@ -237,6 +261,20 @@ export class TurnLoop {
|
|
|
237
261
|
}
|
|
238
262
|
return result.messages;
|
|
239
263
|
}
|
|
264
|
+
redactConsumedSensitiveToolResults(messages) {
|
|
265
|
+
if (this.sensitiveToolResultRedactions.size === 0)
|
|
266
|
+
return messages;
|
|
267
|
+
const redacted = redactSensitiveToolResultsInMessages(messages, this.sensitiveToolResultRedactions);
|
|
268
|
+
this.sensitiveToolResultRedactions.clear();
|
|
269
|
+
return redacted;
|
|
270
|
+
}
|
|
271
|
+
modelCallRecordingOptions() {
|
|
272
|
+
if (this.sensitiveToolResultRedactions.size === 0)
|
|
273
|
+
return undefined;
|
|
274
|
+
return {
|
|
275
|
+
sensitiveToolResultRedactions: new Map(this.sensitiveToolResultRedactions),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
240
278
|
trackFreshImageMessage(message) {
|
|
241
279
|
if (messageHasBase64ImagePayload(message)) {
|
|
242
280
|
this.pendingImageMessages.add(message);
|
|
@@ -281,11 +319,17 @@ export class TurnLoop {
|
|
|
281
319
|
if (ctx === this.lastCtxEmit)
|
|
282
320
|
return;
|
|
283
321
|
this.lastCtxEmit = ctx;
|
|
284
|
-
this.config.onStream({
|
|
322
|
+
this.config.onStream({
|
|
323
|
+
type: "usage_update",
|
|
324
|
+
promptTokens: ctx,
|
|
325
|
+
promptTokensSource: overhead > 0 ? "calibrated_estimate" : "heuristic_estimate",
|
|
326
|
+
promptTokensConfidence: overhead > 0 ? "medium" : "low",
|
|
327
|
+
});
|
|
285
328
|
}
|
|
286
329
|
recordResponseUsage(usage) {
|
|
287
330
|
this.currentTurnUsage = addTokenUsage(this.currentTurnUsage, usage);
|
|
288
331
|
this.currentCumulativeUsage = this.deps.recordCumulativeUsage?.(usage);
|
|
332
|
+
this.deps.recordCacheReadDiagnostics?.(usage);
|
|
289
333
|
}
|
|
290
334
|
emitCtxFromUsage(usage, messages) {
|
|
291
335
|
if (!this.config.onStream)
|
|
@@ -320,6 +364,8 @@ export class TurnLoop {
|
|
|
320
364
|
this.config.onStream({
|
|
321
365
|
type: "usage_update",
|
|
322
366
|
promptTokens,
|
|
367
|
+
promptTokensSource: "provider_usage",
|
|
368
|
+
promptTokensConfidence: "high",
|
|
323
369
|
...(usage.cacheReadTokens !== undefined ? { cacheReadTokens: usage.cacheReadTokens } : {}),
|
|
324
370
|
...(usage.cacheCreationTokens !== undefined
|
|
325
371
|
? { cacheCreationTokens: usage.cacheCreationTokens }
|
|
@@ -349,9 +395,17 @@ export class TurnLoop {
|
|
|
349
395
|
* Run the multi-turn agent loop until completion.
|
|
350
396
|
*/
|
|
351
397
|
async run(initialMessages) {
|
|
398
|
+
const result = await this.runUnredacted(initialMessages);
|
|
399
|
+
return { ...result, messages: this.redactConsumedSensitiveToolResults(result.messages) };
|
|
400
|
+
}
|
|
401
|
+
async runUnredacted(initialMessages) {
|
|
352
402
|
let messages = [...initialMessages];
|
|
353
403
|
let finalText = "";
|
|
354
404
|
const budgetTracker = createBudgetTracker();
|
|
405
|
+
// Recent execution evidence for the built-in Goal judge. Values enter this
|
|
406
|
+
// window only after irreversible sensitive-data removal, non-text omission,
|
|
407
|
+
// and per-item truncation at the current-round ToolResult boundary.
|
|
408
|
+
const goalToolResults = [];
|
|
355
409
|
// Goal-mode run-scoped budget tracker (P0). Null when no goal. Stamps a
|
|
356
410
|
// wall-clock start now and accumulates prompt+completion tokens across
|
|
357
411
|
// every turn; the guardrail below force-stops the run once any configured
|
|
@@ -398,7 +452,7 @@ export class TurnLoop {
|
|
|
398
452
|
// they join THIS step's request — no abort, no lost in-flight work. Same
|
|
399
453
|
// loop-top user-push pattern as turnStartInjection / turn-limit warnings
|
|
400
454
|
// below. Push to transcript too so they persist + survive resume.
|
|
401
|
-
this.consumeQueuedSteer(messages, "normal_step");
|
|
455
|
+
await this.consumeQueuedSteer(messages, "normal_step");
|
|
402
456
|
const state = initialTurnState(this.turnCount);
|
|
403
457
|
// Per-turn correlation ID. Every log written through `tlog` (or any
|
|
404
458
|
// child derived from it) is stamped with `turn` + `turnId`, so
|
|
@@ -407,13 +461,18 @@ export class TurnLoop {
|
|
|
407
461
|
// returns; instead, each return-causing branch logs its own terminal
|
|
408
462
|
// event (model_error, completed, etc.).
|
|
409
463
|
const turnId = newTurnId();
|
|
464
|
+
const assistantMessageId = `assistant_${turnId}`;
|
|
410
465
|
const tlog = logger.child({ turn: this.turnCount, turnId });
|
|
411
466
|
this.currentTurnLog = tlog;
|
|
412
467
|
const turnStartedAt = Date.now();
|
|
413
468
|
tlog.info("turn.start", { cat: "turn", messageCount: messages.length });
|
|
414
469
|
// Tag downstream tool-exec / permission lines with this turn's IDs.
|
|
415
470
|
this.deps.toolExecutor.setLogger(tlog);
|
|
416
|
-
this.config.onStream?.({
|
|
471
|
+
this.config.onStream?.({
|
|
472
|
+
type: "stream_request_start",
|
|
473
|
+
turnNumber: this.turnCount,
|
|
474
|
+
messageId: assistantMessageId,
|
|
475
|
+
});
|
|
417
476
|
const turnStartHook = await this.emitHook("on_turn_start", {
|
|
418
477
|
turnNumber: this.turnCount,
|
|
419
478
|
});
|
|
@@ -453,16 +512,26 @@ export class TurnLoop {
|
|
|
453
512
|
// Pre-check: downgrade image payloads that have already had their one
|
|
454
513
|
// model-consumption turn, then run context management. Fresh images in
|
|
455
514
|
// pendingImageMessages are preserved through this next model request.
|
|
515
|
+
const hasPendingSensitiveToolResults = this.sensitiveToolResultRedactions.size > 0;
|
|
456
516
|
messages = this.prepareMessagesForModel(messages);
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
517
|
+
messages = this.stripVolatileContextMessages(messages);
|
|
518
|
+
if (hasPendingSensitiveToolResults) {
|
|
519
|
+
tlog.info("turn.sensitive_tool_result_context_management_skipped", {
|
|
520
|
+
cat: "turn",
|
|
521
|
+
count: this.sensitiveToolResultRedactions.size,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
// Context management (async — may trigger LLM summarization)
|
|
526
|
+
messages = await this.deps.contextManager.manageAsync(messages);
|
|
527
|
+
// manageAsync can itself issue an LLM summarization call lasting several
|
|
528
|
+
// seconds; if the signal aborted during it, stop here rather than
|
|
529
|
+
// proceeding into the (expensive) main model call. Belt to the loop-top
|
|
530
|
+
// brace: this catches an abort that landed *inside* context management.
|
|
531
|
+
if (this.config.signal?.aborted) {
|
|
532
|
+
this.markStopped();
|
|
533
|
+
return { text: finalText, reason: "aborted_streaming", messages };
|
|
534
|
+
}
|
|
466
535
|
}
|
|
467
536
|
// No pre-llm ctx emit here: the messages-only estimate would be ~16k
|
|
468
537
|
// smaller than the real prompt (system + tools not included), making
|
|
@@ -476,7 +545,9 @@ export class TurnLoop {
|
|
|
476
545
|
// call. Microcompact is lossless (just clearing redundant
|
|
477
546
|
// tool_results) so we suppress hook emits for it to keep token
|
|
478
547
|
// overhead down.
|
|
479
|
-
const pending =
|
|
548
|
+
const pending = hasPendingSensitiveToolResults
|
|
549
|
+
? null
|
|
550
|
+
: this.deps.consumePendingCompactInfo?.();
|
|
480
551
|
if (pending && pending.strategy !== "micro") {
|
|
481
552
|
const compactHook = await this.emitHook("post_compact", {
|
|
482
553
|
strategy: pending.strategy,
|
|
@@ -488,14 +559,16 @@ export class TurnLoop {
|
|
|
488
559
|
messages.push(compactInjection);
|
|
489
560
|
}
|
|
490
561
|
}
|
|
562
|
+
messages = this.appendVolatileContextMessages(messages);
|
|
491
563
|
// Model call (with streaming fallback and max_output_tokens continuation)
|
|
492
564
|
// Track tool IDs streamed during this turn to avoid duplicate UI events
|
|
493
565
|
this.streamedToolIds.clear();
|
|
494
|
-
//
|
|
566
|
+
// Tool queue is created before the call, but enqueue happens only after
|
|
567
|
+
// the complete LLMResponse is available below.
|
|
495
568
|
const streamingQueue = new StreamingToolQueue(this.deps.toolExecutor);
|
|
496
569
|
let response;
|
|
497
570
|
try {
|
|
498
|
-
response = await this.callModelWithFallback(messages);
|
|
571
|
+
response = await this.callModelWithFallback(messages, assistantMessageId);
|
|
499
572
|
}
|
|
500
573
|
catch (err) {
|
|
501
574
|
if (err instanceof ContextLimitError) {
|
|
@@ -506,13 +579,14 @@ export class TurnLoop {
|
|
|
506
579
|
tlog.warn("turn.ptl_recovery", { cat: "turn", retry, roundsToDrop: retry });
|
|
507
580
|
messages = dropOldestRounds(messages, retry);
|
|
508
581
|
try {
|
|
509
|
-
response = await this.callModelWithFallback(messages);
|
|
582
|
+
response = await this.callModelWithFallback(messages, assistantMessageId);
|
|
510
583
|
recovered = true;
|
|
511
584
|
break;
|
|
512
585
|
}
|
|
513
586
|
catch (retryErr) {
|
|
514
587
|
if (!(retryErr instanceof ContextLimitError)) {
|
|
515
588
|
this.config.onStream?.({ type: "error", error: formatFriendlyError(retryErr) });
|
|
589
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
516
590
|
return { text: finalText, reason: "model_error", messages };
|
|
517
591
|
}
|
|
518
592
|
}
|
|
@@ -523,6 +597,7 @@ export class TurnLoop {
|
|
|
523
597
|
type: "error",
|
|
524
598
|
error: "Context limit exceeded after 3 recovery attempts",
|
|
525
599
|
});
|
|
600
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
526
601
|
return { text: finalText, reason: "prompt_too_long", messages };
|
|
527
602
|
}
|
|
528
603
|
}
|
|
@@ -536,14 +611,17 @@ export class TurnLoop {
|
|
|
536
611
|
// interrupted turn folds behind the process-card header on reload).
|
|
537
612
|
this.patchOrphanedToolUses(messages);
|
|
538
613
|
this.markStopped();
|
|
614
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
539
615
|
return { text: finalText, reason: "aborted_streaming", messages };
|
|
540
616
|
}
|
|
541
617
|
else {
|
|
542
618
|
this.patchOrphanedToolUses(messages);
|
|
543
619
|
this.config.onStream?.({ type: "error", error: formatFriendlyError(err) });
|
|
620
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
544
621
|
return { text: finalText, reason: "model_error", messages };
|
|
545
622
|
}
|
|
546
623
|
}
|
|
624
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
547
625
|
// Record the response once into current-turn and whole-session counters.
|
|
548
626
|
if (response.usage?.promptTokens !== undefined) {
|
|
549
627
|
this.recordResponseUsage(response.usage);
|
|
@@ -553,7 +631,10 @@ export class TurnLoop {
|
|
|
553
631
|
// compaction decisions use hybrid (actual + delta) estimation rather than
|
|
554
632
|
// pure heuristics. Without this the manager falls back to char/4 estimates.
|
|
555
633
|
if (response.usage?.promptTokens !== undefined) {
|
|
556
|
-
this.
|
|
634
|
+
const contextAnchorMessages = this.stripVolatileContextMessages(messages);
|
|
635
|
+
const anchor = this.deps.contextManager.recordActualUsage(response.usage.promptTokens, contextAnchorMessages.length, contextAnchorMessages);
|
|
636
|
+
if (anchor)
|
|
637
|
+
this.deps.recordContextUsageAnchor?.(anchor);
|
|
557
638
|
}
|
|
558
639
|
messages = this.markPendingImagesConsumed(messages);
|
|
559
640
|
// Truncation that cut off a TOOL CALL: the model overflowed
|
|
@@ -641,7 +722,10 @@ export class TurnLoop {
|
|
|
641
722
|
// is force-stopped regardless of what the model wants to do next (stop OR
|
|
642
723
|
// continue with tool calls). This is the unattended-safety backstop, so
|
|
643
724
|
// it sits BEFORE the "tool calls?" branch — both paths pass the gate.
|
|
644
|
-
|
|
725
|
+
const budgetTermination = goalTracker
|
|
726
|
+
? goalBudgetTerminationReason(goalTracker, Date.now())
|
|
727
|
+
: undefined;
|
|
728
|
+
if (goalTracker && budgetTermination) {
|
|
645
729
|
tlog.info("turn.goal_budget_exhausted", {
|
|
646
730
|
cat: "goal",
|
|
647
731
|
tokensUsed: goalTracker.tokensUsed,
|
|
@@ -650,18 +734,25 @@ export class TurnLoop {
|
|
|
650
734
|
});
|
|
651
735
|
this.config.onStream?.({
|
|
652
736
|
type: "assistant_message",
|
|
737
|
+
messageId: assistantMessageId,
|
|
653
738
|
message: {
|
|
654
739
|
role: "assistant",
|
|
655
740
|
content: "(Goal 预算已耗尽,强制停止。)",
|
|
656
741
|
},
|
|
657
742
|
});
|
|
658
|
-
return {
|
|
743
|
+
return {
|
|
744
|
+
text: finalText,
|
|
745
|
+
reason: "goal_budget_exhausted",
|
|
746
|
+
messages,
|
|
747
|
+
goalTermination: budgetTermination,
|
|
748
|
+
};
|
|
659
749
|
}
|
|
660
750
|
// Post-check: tool calls?
|
|
661
751
|
if (response.toolCalls.length === 0) {
|
|
662
752
|
// No tool use — final answer
|
|
663
753
|
this.config.onStream?.({
|
|
664
754
|
type: "assistant_message",
|
|
755
|
+
messageId: assistantMessageId,
|
|
665
756
|
message: { role: "assistant", content: finalText },
|
|
666
757
|
});
|
|
667
758
|
await this.emitHook("on_turn_end", {
|
|
@@ -669,7 +760,7 @@ export class TurnLoop {
|
|
|
669
760
|
hasToolUse: false,
|
|
670
761
|
});
|
|
671
762
|
messages.push({ role: "assistant", content: finalText });
|
|
672
|
-
if (this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
763
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
673
764
|
continue;
|
|
674
765
|
}
|
|
675
766
|
// on_stop seam: the model wants to stop. Give handlers (Goal mode)
|
|
@@ -678,6 +769,23 @@ export class TurnLoop {
|
|
|
678
769
|
// we run another turn instead of returning. Bounded by
|
|
679
770
|
// maxStopBlocks (consecutive) and the outer maxTurns ceiling.
|
|
680
771
|
const maxStopBlocks = this.config.maxStopBlocks ?? GOAL_DEFAULT_MAX_STOP_BLOCKS;
|
|
772
|
+
if (goalTracker) {
|
|
773
|
+
this.deps.updateGoalJudgeContext?.({
|
|
774
|
+
toolResults: goalToolResults.map((item) => ({
|
|
775
|
+
...item,
|
|
776
|
+
})),
|
|
777
|
+
progress: {
|
|
778
|
+
turnCount: this.turnCount,
|
|
779
|
+
stopRound: this.stopBlockCount + 1,
|
|
780
|
+
elapsedMs: Math.max(0, Date.now() - goalTracker.startedAtMs),
|
|
781
|
+
tokensUsed: goalTracker.tokensUsed,
|
|
782
|
+
tokenBudget: goalTracker.goal.tokenBudget,
|
|
783
|
+
timeBudgetMs: goalTracker.goal.timeBudgetMs,
|
|
784
|
+
maxTurns: this.config.maxTurns,
|
|
785
|
+
maxStopBlocks,
|
|
786
|
+
},
|
|
787
|
+
});
|
|
788
|
+
}
|
|
681
789
|
const stopHook = await this.emitHook("on_stop", {
|
|
682
790
|
goal: this.config.goal,
|
|
683
791
|
finalText,
|
|
@@ -722,6 +830,7 @@ export class TurnLoop {
|
|
|
722
830
|
});
|
|
723
831
|
continue;
|
|
724
832
|
}
|
|
833
|
+
let goalTermination;
|
|
725
834
|
if (stopHook.continueSession && this.stopBlockCount >= maxStopBlocks) {
|
|
726
835
|
// Cap hit: stop anyway, but tell the user why we're not looping
|
|
727
836
|
// forever on an unsatisfiable goal.
|
|
@@ -735,8 +844,10 @@ export class TurnLoop {
|
|
|
735
844
|
status: "exhausted",
|
|
736
845
|
round: this.stopBlockCount,
|
|
737
846
|
});
|
|
847
|
+
goalTermination = "stop_blocks_exhausted";
|
|
738
848
|
this.config.onStream?.({
|
|
739
849
|
type: "assistant_message",
|
|
850
|
+
messageId: assistantMessageId,
|
|
740
851
|
message: {
|
|
741
852
|
role: "assistant",
|
|
742
853
|
content: `(Goal 续跑已达 ${maxStopBlocks} 次上限,先停下。)`,
|
|
@@ -753,10 +864,11 @@ export class TurnLoop {
|
|
|
753
864
|
});
|
|
754
865
|
}
|
|
755
866
|
this.stopBlockCount = 0;
|
|
756
|
-
if (this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
867
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
757
868
|
continue;
|
|
758
869
|
}
|
|
759
|
-
|
|
870
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
871
|
+
return { text: finalText, reason: "completed", messages, goalTermination };
|
|
760
872
|
}
|
|
761
873
|
// Tool execution phase
|
|
762
874
|
tlog.info("turn.tool_use", {
|
|
@@ -799,9 +911,21 @@ export class TurnLoop {
|
|
|
799
911
|
// Record results in transcript and stream
|
|
800
912
|
const resultBlocks = [];
|
|
801
913
|
for (const result of results) {
|
|
914
|
+
if (goalTracker) {
|
|
915
|
+
goalToolResults.push(projectGoalJudgeToolResult(result, this.turnCount));
|
|
916
|
+
// The prompt renderer keeps at most 12, but retaining a few extra
|
|
917
|
+
// bounded projections lets its total-char budget choose the newest useful subset.
|
|
918
|
+
if (goalToolResults.length > 20)
|
|
919
|
+
goalToolResults.splice(0, goalToolResults.length - 20);
|
|
920
|
+
}
|
|
802
921
|
resultBlocks.push(toolResultToBlock(result));
|
|
803
|
-
|
|
804
|
-
|
|
922
|
+
const streamResult = toolResultForDisplay(result);
|
|
923
|
+
const transcriptResult = toolResultTranscriptText(result);
|
|
924
|
+
if (result.sensitive && transcriptResult !== undefined) {
|
|
925
|
+
this.sensitiveToolResultRedactions.set(result.id, transcriptResult);
|
|
926
|
+
}
|
|
927
|
+
this.deps.transcript.appendToolResult(result.id, result.toolName, transcriptResult, result.error, result.sensitive ? undefined : result.contentBlocks);
|
|
928
|
+
this.config.onStream?.({ type: "tool_result", result: streamResult });
|
|
805
929
|
}
|
|
806
930
|
// Fire-and-forget tool use summary (non-blocking). The whole chain is
|
|
807
931
|
// best-effort observability — a thrown onStream handler, a failed dynamic
|
|
@@ -812,9 +936,13 @@ export class TurnLoop {
|
|
|
812
936
|
.then(({ generateToolUseSummary }) => {
|
|
813
937
|
if (!this.deps.model.summarize)
|
|
814
938
|
return;
|
|
815
|
-
return generateToolUseSummary(toolCalls, results, this.deps.model.summarize).then((summary) => {
|
|
939
|
+
return generateToolUseSummary(toolCalls, toolResultsForDisplay(results), this.deps.model.summarize).then((summary) => {
|
|
816
940
|
if (summary) {
|
|
817
|
-
this.config.onStream?.({
|
|
941
|
+
this.config.onStream?.({
|
|
942
|
+
type: "tool_summary",
|
|
943
|
+
summary,
|
|
944
|
+
toolCallIds: toolCalls.map((toolCall) => toolCall.id),
|
|
945
|
+
});
|
|
818
946
|
}
|
|
819
947
|
});
|
|
820
948
|
})
|
|
@@ -866,9 +994,10 @@ export class TurnLoop {
|
|
|
866
994
|
tlog.info("turn.goal_self_reported_complete", { cat: "goal" });
|
|
867
995
|
this.stopBlockCount = 0;
|
|
868
996
|
this.deps.clearPersistedGoal?.();
|
|
869
|
-
if (this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
997
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
870
998
|
continue;
|
|
871
999
|
}
|
|
1000
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
872
1001
|
return { text: finalText, reason: "completed", messages };
|
|
873
1002
|
}
|
|
874
1003
|
// Goal mode: user-initiated cancellation. cancel_goal is the "strong
|
|
@@ -881,7 +1010,7 @@ export class TurnLoop {
|
|
|
881
1010
|
tlog.info("turn.goal_user_cancelled", { cat: "goal" });
|
|
882
1011
|
this.stopBlockCount = 0;
|
|
883
1012
|
this.deps.clearPersistedGoal?.();
|
|
884
|
-
if (this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
1013
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
885
1014
|
continue;
|
|
886
1015
|
}
|
|
887
1016
|
return { text: finalText, reason: "completed", messages };
|
|
@@ -897,12 +1026,14 @@ export class TurnLoop {
|
|
|
897
1026
|
});
|
|
898
1027
|
this.config.onStream?.({
|
|
899
1028
|
type: "assistant_message",
|
|
1029
|
+
messageId: assistantMessageId,
|
|
900
1030
|
message: { role: "assistant", content: finalText },
|
|
901
1031
|
});
|
|
902
1032
|
messages.push({ role: "assistant", content: finalText });
|
|
903
|
-
if (this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
1033
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
904
1034
|
continue;
|
|
905
1035
|
}
|
|
1036
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
906
1037
|
return { text: finalText, reason: "completed", messages };
|
|
907
1038
|
}
|
|
908
1039
|
if (budgetDecision === "nudge") {
|
|
@@ -963,6 +1094,7 @@ export class TurnLoop {
|
|
|
963
1094
|
// no error event, so the UI shows only the "你停止了本轮" line.
|
|
964
1095
|
if (isAbortError(err) || this.config.signal?.aborted) {
|
|
965
1096
|
this.markStopped();
|
|
1097
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
966
1098
|
return { text: finalText, reason: "aborted_streaming", messages };
|
|
967
1099
|
}
|
|
968
1100
|
this.currentTurnLog.error("turn.unhandled_error", {
|
|
@@ -971,6 +1103,7 @@ export class TurnLoop {
|
|
|
971
1103
|
stack: err.stack?.split("\n").slice(0, 4).join("\n"),
|
|
972
1104
|
});
|
|
973
1105
|
this.config.onStream?.({ type: "error", error: formatFriendlyError(err) });
|
|
1106
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
974
1107
|
return { text: finalText, reason: "model_error", messages };
|
|
975
1108
|
}
|
|
976
1109
|
// Max turns reached — do one final summarization call (no tools)
|
|
@@ -979,9 +1112,19 @@ export class TurnLoop {
|
|
|
979
1112
|
maxTurns: this.config.maxTurns,
|
|
980
1113
|
turnCount: this.turnCount,
|
|
981
1114
|
});
|
|
982
|
-
this.consumeQueuedSteer(messages, "finalize_backfill");
|
|
1115
|
+
await this.consumeQueuedSteer(messages, "finalize_backfill");
|
|
1116
|
+
const hasPendingSensitiveToolResults = this.sensitiveToolResultRedactions.size > 0;
|
|
983
1117
|
messages = this.prepareMessagesForModel(messages);
|
|
984
|
-
|
|
1118
|
+
if (hasPendingSensitiveToolResults) {
|
|
1119
|
+
logger.info("turn.sensitive_tool_result_context_management_skipped", {
|
|
1120
|
+
cat: "turn",
|
|
1121
|
+
count: this.sensitiveToolResultRedactions.size,
|
|
1122
|
+
phase: "max_turns_summary",
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
messages = this.deps.contextManager.manage(messages);
|
|
1127
|
+
}
|
|
985
1128
|
messages.push({
|
|
986
1129
|
role: "user",
|
|
987
1130
|
content: "<system-reminder>Turn limit reached. Provide a final summary of what you accomplished and what remains to be done. Do NOT call any tools.</system-reminder>",
|
|
@@ -989,7 +1132,7 @@ export class TurnLoop {
|
|
|
989
1132
|
this.emitCtxFromMessages(messages);
|
|
990
1133
|
try {
|
|
991
1134
|
const summaryResponse = await this.deps.model.call(this.deps.systemPrompt, this.prepareMessagesForModel(messages), [], // No tools available for summary turn
|
|
992
|
-
this.config.onStream, this.config.signal);
|
|
1135
|
+
this.config.onStream, this.config.signal, this.modelCallRecordingOptions());
|
|
993
1136
|
messages = this.markPendingImagesConsumed(messages);
|
|
994
1137
|
if (summaryResponse.text) {
|
|
995
1138
|
finalText = summaryResponse.text;
|
|
@@ -1006,14 +1149,19 @@ export class TurnLoop {
|
|
|
1006
1149
|
});
|
|
1007
1150
|
messages.push({ role: "assistant", content: finalText });
|
|
1008
1151
|
}
|
|
1009
|
-
this.
|
|
1010
|
-
return {
|
|
1152
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
1153
|
+
return {
|
|
1154
|
+
text: finalText,
|
|
1155
|
+
reason: "max_turns",
|
|
1156
|
+
messages,
|
|
1157
|
+
...(this.config.goal ? { goalTermination: "max_turns_exhausted" } : {}),
|
|
1158
|
+
};
|
|
1011
1159
|
}
|
|
1012
1160
|
/**
|
|
1013
1161
|
* Call model with streaming fallback.
|
|
1014
1162
|
* If streaming fails, emit tombstone and retry non-streaming.
|
|
1015
1163
|
*/
|
|
1016
|
-
async callModelWithFallback(messages) {
|
|
1164
|
+
async callModelWithFallback(messages, assistantMessageId) {
|
|
1017
1165
|
// Wrap stream callback to track tool_use_start events and reactive compaction
|
|
1018
1166
|
let streamingResponseTokens = 0;
|
|
1019
1167
|
let reactiveBucket = -1;
|
|
@@ -1045,7 +1193,7 @@ export class TurnLoop {
|
|
|
1045
1193
|
}
|
|
1046
1194
|
: undefined;
|
|
1047
1195
|
try {
|
|
1048
|
-
return await this.deps.model.call(this.deps.systemPrompt, messages, this.deps.tools, wrappedStream, this.config.signal);
|
|
1196
|
+
return await this.deps.model.call(this.deps.systemPrompt, messages, this.deps.tools, wrappedStream, this.config.signal, this.modelCallRecordingOptions());
|
|
1049
1197
|
}
|
|
1050
1198
|
catch (err) {
|
|
1051
1199
|
// If it's a context or rate limit error, don't fallback — propagate
|
|
@@ -1065,23 +1213,36 @@ export class TurnLoop {
|
|
|
1065
1213
|
// really just a cancel. The signal is the authoritative cancel source.
|
|
1066
1214
|
if (isAbortError(err) || this.config.signal?.aborted)
|
|
1067
1215
|
throw err;
|
|
1216
|
+
// Sensitive tool results are model-facing exactly once. A streaming
|
|
1217
|
+
// fallback would re-send the same pending plaintext in a second request,
|
|
1218
|
+
// so fail the turn and let the unified exit redact returned history.
|
|
1219
|
+
if (this.sensitiveToolResultRedactions.size > 0) {
|
|
1220
|
+
this.config.onStream?.({ type: "tombstone", messageId: assistantMessageId });
|
|
1221
|
+
this.currentTurnLog.warn("turn.streaming_fallback_skipped_sensitive", {
|
|
1222
|
+
cat: "turn",
|
|
1223
|
+
error: err.message,
|
|
1224
|
+
});
|
|
1225
|
+
throw err;
|
|
1226
|
+
}
|
|
1068
1227
|
// Streaming might have partially emitted — send tombstone to revoke
|
|
1069
|
-
this.config.onStream?.({ type: "tombstone", messageId:
|
|
1228
|
+
this.config.onStream?.({ type: "tombstone", messageId: assistantMessageId });
|
|
1070
1229
|
this.currentTurnLog.warn("turn.streaming_fallback", {
|
|
1071
1230
|
cat: "turn",
|
|
1072
1231
|
error: err.message,
|
|
1073
1232
|
});
|
|
1074
1233
|
// Retry without streaming
|
|
1075
|
-
return await this.deps.model.callWithoutStreaming(this.deps.systemPrompt, messages, this.deps.tools, this.config.signal);
|
|
1234
|
+
return await this.deps.model.callWithoutStreaming(this.deps.systemPrompt, messages, this.deps.tools, this.config.signal, this.modelCallRecordingOptions());
|
|
1076
1235
|
}
|
|
1077
1236
|
}
|
|
1078
1237
|
get currentTurn() {
|
|
1079
1238
|
return this.turnCount;
|
|
1080
1239
|
}
|
|
1081
|
-
consumeQueuedSteer(messages, source) {
|
|
1240
|
+
async consumeQueuedSteer(messages, source) {
|
|
1082
1241
|
const steered = this.deps.consumeSteer?.(source) ?? [];
|
|
1083
1242
|
let consumed = false;
|
|
1084
|
-
for (
|
|
1243
|
+
for (let index = 0; index < steered.length; index++) {
|
|
1244
|
+
const item = steered[index];
|
|
1245
|
+
const { id, text, clientMessageId } = item;
|
|
1085
1246
|
if (!text)
|
|
1086
1247
|
continue;
|
|
1087
1248
|
if (clientMessageId && this.deps.claimClientMessageId?.(clientMessageId, "steer") === false) {
|
|
@@ -1093,9 +1254,33 @@ export class TurnLoop {
|
|
|
1093
1254
|
});
|
|
1094
1255
|
continue;
|
|
1095
1256
|
}
|
|
1257
|
+
let content;
|
|
1258
|
+
try {
|
|
1259
|
+
content = this.deps.buildSteerUserMessageContent
|
|
1260
|
+
? await this.deps.buildSteerUserMessageContent(item)
|
|
1261
|
+
: text;
|
|
1262
|
+
}
|
|
1263
|
+
catch (err) {
|
|
1264
|
+
if (clientMessageId)
|
|
1265
|
+
this.deps.releaseClientMessageId?.(clientMessageId);
|
|
1266
|
+
const pendingSuffix = steered.slice(index);
|
|
1267
|
+
this.deps.restoreSteer?.(pendingSuffix);
|
|
1268
|
+
logger.warn("steer.prepare.failed_requeued", {
|
|
1269
|
+
clientMessageId,
|
|
1270
|
+
steerId: id,
|
|
1271
|
+
sessionId: this.deps.sessionId,
|
|
1272
|
+
source,
|
|
1273
|
+
restoredCount: pendingSuffix.length,
|
|
1274
|
+
error: err.message,
|
|
1275
|
+
});
|
|
1276
|
+
break;
|
|
1277
|
+
}
|
|
1096
1278
|
consumed = true;
|
|
1097
|
-
|
|
1098
|
-
|
|
1279
|
+
this.deps.setOriginClientMessageId?.(clientMessageId);
|
|
1280
|
+
const message = { role: "user", content };
|
|
1281
|
+
messages.push(message);
|
|
1282
|
+
this.trackFreshImageMessage(message);
|
|
1283
|
+
this.deps.transcript.appendMessage("user", content, { steerId: id, clientMessageId });
|
|
1099
1284
|
this.config.onStream?.({ type: "steer_injected", text, id });
|
|
1100
1285
|
}
|
|
1101
1286
|
return consumed;
|
package/dist/engine/types.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ export interface EngineConfig {
|
|
|
75
75
|
/** Browser automation bridge (browser_* tools). Wired by the host (desktop)
|
|
76
76
|
* after construction via setBrowserBridge. Undefined → tools degrade. */
|
|
77
77
|
browserBridge?: import("../tool-system/browser-bridge.js").BrowserBridge;
|
|
78
|
+
/** Host-backed workspace switch bridge (desktop only). */
|
|
79
|
+
workspaceBridge?: import("../tool-system/workspace-bridge.js").WorkspaceBridge;
|
|
78
80
|
/** Inject a cookie credential into the built-in browser (InjectCredential
|
|
79
81
|
* tool). Wired by the host after construction via setInjectCredential.
|
|
80
82
|
* Undefined → the tool degrades with a clear "no browser" error. */
|
|
@@ -127,6 +129,12 @@ export interface EngineConfig {
|
|
|
127
129
|
* automation shown; tui hidden). Sub-agents override to "subagent".
|
|
128
130
|
*/
|
|
129
131
|
origin?: SessionOrigin;
|
|
132
|
+
/**
|
|
133
|
+
* Host-specific builtin tool surface. Desktop replaces the legacy core
|
|
134
|
+
* worktree tools with SwitchSessionWorkspace because Electron main owns the
|
|
135
|
+
* authoritative workspace switch service and UI refresh path.
|
|
136
|
+
*/
|
|
137
|
+
builtinToolHost?: "desktop";
|
|
130
138
|
/**
|
|
131
139
|
* Optional shared EngineRuntime providing pre-constructed shared resources
|
|
132
140
|
* (modelPool, toolRegistry, etc.). When provided, Engine uses these instead
|