@cjhyy/code-shell-core 0.6.0-rc.9 → 0.7.0-beta.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/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 +2 -0
- package/dist/cc-orchestrator/agent-adapter.js +7 -1
- package/dist/cc-orchestrator/codex-session-history.js +1 -1
- 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-driver.d.ts +1 -0
- package/dist/cc-orchestrator/external-agent-driver.js +102 -51
- 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.js +2 -2
- 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 +56 -0
- package/dist/credentials/access.js +183 -0
- package/dist/credentials/index.d.ts +1 -0
- package/dist/credentials/index.js +1 -0
- package/dist/credentials/inject-credential-tool.d.ts +3 -1
- package/dist/credentials/inject-credential-tool.js +30 -11
- package/dist/credentials/types.d.ts +2 -2
- 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 +17 -1
- package/dist/engine/engine.js +247 -89
- 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 +255 -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/streaming-tool-queue.d.ts +11 -7
- package/dist/engine/streaming-tool-queue.js +11 -7
- package/dist/engine/turn-loop.d.ts +7 -1
- package/dist/engine/turn-loop.js +117 -27
- 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/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 +16 -9
- 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 +246 -46
- package/dist/protocol/types.d.ts +47 -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 +190 -3
- 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/background-jobs.d.ts +10 -1
- package/dist/tool-system/builtin/background-jobs.js +12 -1
- 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/drive-claude-code.d.ts +16 -2
- package/dist/tool-system/builtin/drive-claude-code.js +301 -47
- 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 +37 -21
- 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/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 +23 -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 +18 -5
- package/dist/tool-system/mcp-manager.js +140 -74
- 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 +55 -4
- package/dist/utils/toolDisplay.js +1 -1
- package/package.json +4 -3
package/dist/engine/turn-loop.js
CHANGED
|
@@ -17,6 +17,7 @@ import { isAbortError } from "../llm/client-base.js";
|
|
|
17
17
|
import { crossedReactiveThreshold } from "./reactive-threshold.js";
|
|
18
18
|
import { COMPLETE_GOAL_TOOL_NAME } from "../tool-system/builtin/complete-goal.js";
|
|
19
19
|
import { CANCEL_GOAL_TOOL_NAME } from "../tool-system/builtin/cancel-goal.js";
|
|
20
|
+
import { redactSensitiveToolResultsInMessages, toolResultForDisplay, toolResultTranscriptText, toolResultsForDisplay, } from "../tool-system/tool-result-redaction.js";
|
|
20
21
|
import { addTokenUsage, cacheHitRateFromUsage, cumulativeCacheHitRate, } from "./session-usage.js";
|
|
21
22
|
import { createGoalBudgetTracker, recordGoalUsage, goalBudgetExceeded, applyGoalExtension, limitProximity, GOAL_DEFAULT_MAX_STOP_BLOCKS, } from "./goal.js";
|
|
22
23
|
/**
|
|
@@ -59,6 +60,7 @@ export class TurnLoop {
|
|
|
59
60
|
cacheCreationTokens: 0,
|
|
60
61
|
};
|
|
61
62
|
currentCumulativeUsage;
|
|
63
|
+
sensitiveToolResultRedactions = new Map();
|
|
62
64
|
pendingImageMessages = new Set();
|
|
63
65
|
/**
|
|
64
66
|
* Consecutive on_stop blocks (Goal mode kept the agent going). Reset to 0
|
|
@@ -237,6 +239,20 @@ export class TurnLoop {
|
|
|
237
239
|
}
|
|
238
240
|
return result.messages;
|
|
239
241
|
}
|
|
242
|
+
redactConsumedSensitiveToolResults(messages) {
|
|
243
|
+
if (this.sensitiveToolResultRedactions.size === 0)
|
|
244
|
+
return messages;
|
|
245
|
+
const redacted = redactSensitiveToolResultsInMessages(messages, this.sensitiveToolResultRedactions);
|
|
246
|
+
this.sensitiveToolResultRedactions.clear();
|
|
247
|
+
return redacted;
|
|
248
|
+
}
|
|
249
|
+
modelCallRecordingOptions() {
|
|
250
|
+
if (this.sensitiveToolResultRedactions.size === 0)
|
|
251
|
+
return undefined;
|
|
252
|
+
return {
|
|
253
|
+
sensitiveToolResultRedactions: new Map(this.sensitiveToolResultRedactions),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
240
256
|
trackFreshImageMessage(message) {
|
|
241
257
|
if (messageHasBase64ImagePayload(message)) {
|
|
242
258
|
this.pendingImageMessages.add(message);
|
|
@@ -281,7 +297,12 @@ export class TurnLoop {
|
|
|
281
297
|
if (ctx === this.lastCtxEmit)
|
|
282
298
|
return;
|
|
283
299
|
this.lastCtxEmit = ctx;
|
|
284
|
-
this.config.onStream({
|
|
300
|
+
this.config.onStream({
|
|
301
|
+
type: "usage_update",
|
|
302
|
+
promptTokens: ctx,
|
|
303
|
+
promptTokensSource: overhead > 0 ? "calibrated_estimate" : "heuristic_estimate",
|
|
304
|
+
promptTokensConfidence: overhead > 0 ? "medium" : "low",
|
|
305
|
+
});
|
|
285
306
|
}
|
|
286
307
|
recordResponseUsage(usage) {
|
|
287
308
|
this.currentTurnUsage = addTokenUsage(this.currentTurnUsage, usage);
|
|
@@ -320,6 +341,8 @@ export class TurnLoop {
|
|
|
320
341
|
this.config.onStream({
|
|
321
342
|
type: "usage_update",
|
|
322
343
|
promptTokens,
|
|
344
|
+
promptTokensSource: "provider_usage",
|
|
345
|
+
promptTokensConfidence: "high",
|
|
323
346
|
...(usage.cacheReadTokens !== undefined ? { cacheReadTokens: usage.cacheReadTokens } : {}),
|
|
324
347
|
...(usage.cacheCreationTokens !== undefined
|
|
325
348
|
? { cacheCreationTokens: usage.cacheCreationTokens }
|
|
@@ -349,6 +372,10 @@ export class TurnLoop {
|
|
|
349
372
|
* Run the multi-turn agent loop until completion.
|
|
350
373
|
*/
|
|
351
374
|
async run(initialMessages) {
|
|
375
|
+
const result = await this.runUnredacted(initialMessages);
|
|
376
|
+
return { ...result, messages: this.redactConsumedSensitiveToolResults(result.messages) };
|
|
377
|
+
}
|
|
378
|
+
async runUnredacted(initialMessages) {
|
|
352
379
|
let messages = [...initialMessages];
|
|
353
380
|
let finalText = "";
|
|
354
381
|
const budgetTracker = createBudgetTracker();
|
|
@@ -407,13 +434,18 @@ export class TurnLoop {
|
|
|
407
434
|
// returns; instead, each return-causing branch logs its own terminal
|
|
408
435
|
// event (model_error, completed, etc.).
|
|
409
436
|
const turnId = newTurnId();
|
|
437
|
+
const assistantMessageId = `assistant_${turnId}`;
|
|
410
438
|
const tlog = logger.child({ turn: this.turnCount, turnId });
|
|
411
439
|
this.currentTurnLog = tlog;
|
|
412
440
|
const turnStartedAt = Date.now();
|
|
413
441
|
tlog.info("turn.start", { cat: "turn", messageCount: messages.length });
|
|
414
442
|
// Tag downstream tool-exec / permission lines with this turn's IDs.
|
|
415
443
|
this.deps.toolExecutor.setLogger(tlog);
|
|
416
|
-
this.config.onStream?.({
|
|
444
|
+
this.config.onStream?.({
|
|
445
|
+
type: "stream_request_start",
|
|
446
|
+
turnNumber: this.turnCount,
|
|
447
|
+
messageId: assistantMessageId,
|
|
448
|
+
});
|
|
417
449
|
const turnStartHook = await this.emitHook("on_turn_start", {
|
|
418
450
|
turnNumber: this.turnCount,
|
|
419
451
|
});
|
|
@@ -453,16 +485,25 @@ export class TurnLoop {
|
|
|
453
485
|
// Pre-check: downgrade image payloads that have already had their one
|
|
454
486
|
// model-consumption turn, then run context management. Fresh images in
|
|
455
487
|
// pendingImageMessages are preserved through this next model request.
|
|
488
|
+
const hasPendingSensitiveToolResults = this.sensitiveToolResultRedactions.size > 0;
|
|
456
489
|
messages = this.prepareMessagesForModel(messages);
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
490
|
+
if (hasPendingSensitiveToolResults) {
|
|
491
|
+
tlog.info("turn.sensitive_tool_result_context_management_skipped", {
|
|
492
|
+
cat: "turn",
|
|
493
|
+
count: this.sensitiveToolResultRedactions.size,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
// Context management (async — may trigger LLM summarization)
|
|
498
|
+
messages = await this.deps.contextManager.manageAsync(messages);
|
|
499
|
+
// manageAsync can itself issue an LLM summarization call lasting several
|
|
500
|
+
// seconds; if the signal aborted during it, stop here rather than
|
|
501
|
+
// proceeding into the (expensive) main model call. Belt to the loop-top
|
|
502
|
+
// brace: this catches an abort that landed *inside* context management.
|
|
503
|
+
if (this.config.signal?.aborted) {
|
|
504
|
+
this.markStopped();
|
|
505
|
+
return { text: finalText, reason: "aborted_streaming", messages };
|
|
506
|
+
}
|
|
466
507
|
}
|
|
467
508
|
// No pre-llm ctx emit here: the messages-only estimate would be ~16k
|
|
468
509
|
// smaller than the real prompt (system + tools not included), making
|
|
@@ -476,7 +517,9 @@ export class TurnLoop {
|
|
|
476
517
|
// call. Microcompact is lossless (just clearing redundant
|
|
477
518
|
// tool_results) so we suppress hook emits for it to keep token
|
|
478
519
|
// overhead down.
|
|
479
|
-
const pending =
|
|
520
|
+
const pending = hasPendingSensitiveToolResults
|
|
521
|
+
? null
|
|
522
|
+
: this.deps.consumePendingCompactInfo?.();
|
|
480
523
|
if (pending && pending.strategy !== "micro") {
|
|
481
524
|
const compactHook = await this.emitHook("post_compact", {
|
|
482
525
|
strategy: pending.strategy,
|
|
@@ -491,11 +534,12 @@ export class TurnLoop {
|
|
|
491
534
|
// Model call (with streaming fallback and max_output_tokens continuation)
|
|
492
535
|
// Track tool IDs streamed during this turn to avoid duplicate UI events
|
|
493
536
|
this.streamedToolIds.clear();
|
|
494
|
-
//
|
|
537
|
+
// Tool queue is created before the call, but enqueue happens only after
|
|
538
|
+
// the complete LLMResponse is available below.
|
|
495
539
|
const streamingQueue = new StreamingToolQueue(this.deps.toolExecutor);
|
|
496
540
|
let response;
|
|
497
541
|
try {
|
|
498
|
-
response = await this.callModelWithFallback(messages);
|
|
542
|
+
response = await this.callModelWithFallback(messages, assistantMessageId);
|
|
499
543
|
}
|
|
500
544
|
catch (err) {
|
|
501
545
|
if (err instanceof ContextLimitError) {
|
|
@@ -506,13 +550,14 @@ export class TurnLoop {
|
|
|
506
550
|
tlog.warn("turn.ptl_recovery", { cat: "turn", retry, roundsToDrop: retry });
|
|
507
551
|
messages = dropOldestRounds(messages, retry);
|
|
508
552
|
try {
|
|
509
|
-
response = await this.callModelWithFallback(messages);
|
|
553
|
+
response = await this.callModelWithFallback(messages, assistantMessageId);
|
|
510
554
|
recovered = true;
|
|
511
555
|
break;
|
|
512
556
|
}
|
|
513
557
|
catch (retryErr) {
|
|
514
558
|
if (!(retryErr instanceof ContextLimitError)) {
|
|
515
559
|
this.config.onStream?.({ type: "error", error: formatFriendlyError(retryErr) });
|
|
560
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
516
561
|
return { text: finalText, reason: "model_error", messages };
|
|
517
562
|
}
|
|
518
563
|
}
|
|
@@ -523,6 +568,7 @@ export class TurnLoop {
|
|
|
523
568
|
type: "error",
|
|
524
569
|
error: "Context limit exceeded after 3 recovery attempts",
|
|
525
570
|
});
|
|
571
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
526
572
|
return { text: finalText, reason: "prompt_too_long", messages };
|
|
527
573
|
}
|
|
528
574
|
}
|
|
@@ -536,14 +582,17 @@ export class TurnLoop {
|
|
|
536
582
|
// interrupted turn folds behind the process-card header on reload).
|
|
537
583
|
this.patchOrphanedToolUses(messages);
|
|
538
584
|
this.markStopped();
|
|
585
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
539
586
|
return { text: finalText, reason: "aborted_streaming", messages };
|
|
540
587
|
}
|
|
541
588
|
else {
|
|
542
589
|
this.patchOrphanedToolUses(messages);
|
|
543
590
|
this.config.onStream?.({ type: "error", error: formatFriendlyError(err) });
|
|
591
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
544
592
|
return { text: finalText, reason: "model_error", messages };
|
|
545
593
|
}
|
|
546
594
|
}
|
|
595
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
547
596
|
// Record the response once into current-turn and whole-session counters.
|
|
548
597
|
if (response.usage?.promptTokens !== undefined) {
|
|
549
598
|
this.recordResponseUsage(response.usage);
|
|
@@ -553,7 +602,9 @@ export class TurnLoop {
|
|
|
553
602
|
// compaction decisions use hybrid (actual + delta) estimation rather than
|
|
554
603
|
// pure heuristics. Without this the manager falls back to char/4 estimates.
|
|
555
604
|
if (response.usage?.promptTokens !== undefined) {
|
|
556
|
-
this.deps.contextManager.recordActualUsage(response.usage.promptTokens, messages.length);
|
|
605
|
+
const anchor = this.deps.contextManager.recordActualUsage(response.usage.promptTokens, messages.length, messages);
|
|
606
|
+
if (anchor)
|
|
607
|
+
this.deps.recordContextUsageAnchor?.(anchor);
|
|
557
608
|
}
|
|
558
609
|
messages = this.markPendingImagesConsumed(messages);
|
|
559
610
|
// Truncation that cut off a TOOL CALL: the model overflowed
|
|
@@ -650,6 +701,7 @@ export class TurnLoop {
|
|
|
650
701
|
});
|
|
651
702
|
this.config.onStream?.({
|
|
652
703
|
type: "assistant_message",
|
|
704
|
+
messageId: assistantMessageId,
|
|
653
705
|
message: {
|
|
654
706
|
role: "assistant",
|
|
655
707
|
content: "(Goal 预算已耗尽,强制停止。)",
|
|
@@ -662,6 +714,7 @@ export class TurnLoop {
|
|
|
662
714
|
// No tool use — final answer
|
|
663
715
|
this.config.onStream?.({
|
|
664
716
|
type: "assistant_message",
|
|
717
|
+
messageId: assistantMessageId,
|
|
665
718
|
message: { role: "assistant", content: finalText },
|
|
666
719
|
});
|
|
667
720
|
await this.emitHook("on_turn_end", {
|
|
@@ -737,6 +790,7 @@ export class TurnLoop {
|
|
|
737
790
|
});
|
|
738
791
|
this.config.onStream?.({
|
|
739
792
|
type: "assistant_message",
|
|
793
|
+
messageId: assistantMessageId,
|
|
740
794
|
message: {
|
|
741
795
|
role: "assistant",
|
|
742
796
|
content: `(Goal 续跑已达 ${maxStopBlocks} 次上限,先停下。)`,
|
|
@@ -756,6 +810,7 @@ export class TurnLoop {
|
|
|
756
810
|
if (this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
757
811
|
continue;
|
|
758
812
|
}
|
|
813
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
759
814
|
return { text: finalText, reason: "completed", messages };
|
|
760
815
|
}
|
|
761
816
|
// Tool execution phase
|
|
@@ -800,8 +855,13 @@ export class TurnLoop {
|
|
|
800
855
|
const resultBlocks = [];
|
|
801
856
|
for (const result of results) {
|
|
802
857
|
resultBlocks.push(toolResultToBlock(result));
|
|
803
|
-
|
|
804
|
-
|
|
858
|
+
const streamResult = toolResultForDisplay(result);
|
|
859
|
+
const transcriptResult = toolResultTranscriptText(result);
|
|
860
|
+
if (result.sensitive && transcriptResult !== undefined) {
|
|
861
|
+
this.sensitiveToolResultRedactions.set(result.id, transcriptResult);
|
|
862
|
+
}
|
|
863
|
+
this.deps.transcript.appendToolResult(result.id, result.toolName, transcriptResult, result.error, result.sensitive ? undefined : result.contentBlocks);
|
|
864
|
+
this.config.onStream?.({ type: "tool_result", result: streamResult });
|
|
805
865
|
}
|
|
806
866
|
// Fire-and-forget tool use summary (non-blocking). The whole chain is
|
|
807
867
|
// best-effort observability — a thrown onStream handler, a failed dynamic
|
|
@@ -812,9 +872,13 @@ export class TurnLoop {
|
|
|
812
872
|
.then(({ generateToolUseSummary }) => {
|
|
813
873
|
if (!this.deps.model.summarize)
|
|
814
874
|
return;
|
|
815
|
-
return generateToolUseSummary(toolCalls, results, this.deps.model.summarize).then((summary) => {
|
|
875
|
+
return generateToolUseSummary(toolCalls, toolResultsForDisplay(results), this.deps.model.summarize).then((summary) => {
|
|
816
876
|
if (summary) {
|
|
817
|
-
this.config.onStream?.({
|
|
877
|
+
this.config.onStream?.({
|
|
878
|
+
type: "tool_summary",
|
|
879
|
+
summary,
|
|
880
|
+
toolCallIds: toolCalls.map((toolCall) => toolCall.id),
|
|
881
|
+
});
|
|
818
882
|
}
|
|
819
883
|
});
|
|
820
884
|
})
|
|
@@ -869,6 +933,7 @@ export class TurnLoop {
|
|
|
869
933
|
if (this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
870
934
|
continue;
|
|
871
935
|
}
|
|
936
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
872
937
|
return { text: finalText, reason: "completed", messages };
|
|
873
938
|
}
|
|
874
939
|
// Goal mode: user-initiated cancellation. cancel_goal is the "strong
|
|
@@ -897,12 +962,14 @@ export class TurnLoop {
|
|
|
897
962
|
});
|
|
898
963
|
this.config.onStream?.({
|
|
899
964
|
type: "assistant_message",
|
|
965
|
+
messageId: assistantMessageId,
|
|
900
966
|
message: { role: "assistant", content: finalText },
|
|
901
967
|
});
|
|
902
968
|
messages.push({ role: "assistant", content: finalText });
|
|
903
969
|
if (this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
904
970
|
continue;
|
|
905
971
|
}
|
|
972
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
906
973
|
return { text: finalText, reason: "completed", messages };
|
|
907
974
|
}
|
|
908
975
|
if (budgetDecision === "nudge") {
|
|
@@ -963,6 +1030,7 @@ export class TurnLoop {
|
|
|
963
1030
|
// no error event, so the UI shows only the "你停止了本轮" line.
|
|
964
1031
|
if (isAbortError(err) || this.config.signal?.aborted) {
|
|
965
1032
|
this.markStopped();
|
|
1033
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
966
1034
|
return { text: finalText, reason: "aborted_streaming", messages };
|
|
967
1035
|
}
|
|
968
1036
|
this.currentTurnLog.error("turn.unhandled_error", {
|
|
@@ -971,6 +1039,7 @@ export class TurnLoop {
|
|
|
971
1039
|
stack: err.stack?.split("\n").slice(0, 4).join("\n"),
|
|
972
1040
|
});
|
|
973
1041
|
this.config.onStream?.({ type: "error", error: formatFriendlyError(err) });
|
|
1042
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
974
1043
|
return { text: finalText, reason: "model_error", messages };
|
|
975
1044
|
}
|
|
976
1045
|
// Max turns reached — do one final summarization call (no tools)
|
|
@@ -980,8 +1049,18 @@ export class TurnLoop {
|
|
|
980
1049
|
turnCount: this.turnCount,
|
|
981
1050
|
});
|
|
982
1051
|
this.consumeQueuedSteer(messages, "finalize_backfill");
|
|
1052
|
+
const hasPendingSensitiveToolResults = this.sensitiveToolResultRedactions.size > 0;
|
|
983
1053
|
messages = this.prepareMessagesForModel(messages);
|
|
984
|
-
|
|
1054
|
+
if (hasPendingSensitiveToolResults) {
|
|
1055
|
+
logger.info("turn.sensitive_tool_result_context_management_skipped", {
|
|
1056
|
+
cat: "turn",
|
|
1057
|
+
count: this.sensitiveToolResultRedactions.size,
|
|
1058
|
+
phase: "max_turns_summary",
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
messages = this.deps.contextManager.manage(messages);
|
|
1063
|
+
}
|
|
985
1064
|
messages.push({
|
|
986
1065
|
role: "user",
|
|
987
1066
|
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 +1068,7 @@ export class TurnLoop {
|
|
|
989
1068
|
this.emitCtxFromMessages(messages);
|
|
990
1069
|
try {
|
|
991
1070
|
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);
|
|
1071
|
+
this.config.onStream, this.config.signal, this.modelCallRecordingOptions());
|
|
993
1072
|
messages = this.markPendingImagesConsumed(messages);
|
|
994
1073
|
if (summaryResponse.text) {
|
|
995
1074
|
finalText = summaryResponse.text;
|
|
@@ -1006,14 +1085,14 @@ export class TurnLoop {
|
|
|
1006
1085
|
});
|
|
1007
1086
|
messages.push({ role: "assistant", content: finalText });
|
|
1008
1087
|
}
|
|
1009
|
-
this.
|
|
1088
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
1010
1089
|
return { text: finalText, reason: "max_turns", messages };
|
|
1011
1090
|
}
|
|
1012
1091
|
/**
|
|
1013
1092
|
* Call model with streaming fallback.
|
|
1014
1093
|
* If streaming fails, emit tombstone and retry non-streaming.
|
|
1015
1094
|
*/
|
|
1016
|
-
async callModelWithFallback(messages) {
|
|
1095
|
+
async callModelWithFallback(messages, assistantMessageId) {
|
|
1017
1096
|
// Wrap stream callback to track tool_use_start events and reactive compaction
|
|
1018
1097
|
let streamingResponseTokens = 0;
|
|
1019
1098
|
let reactiveBucket = -1;
|
|
@@ -1045,7 +1124,7 @@ export class TurnLoop {
|
|
|
1045
1124
|
}
|
|
1046
1125
|
: undefined;
|
|
1047
1126
|
try {
|
|
1048
|
-
return await this.deps.model.call(this.deps.systemPrompt, messages, this.deps.tools, wrappedStream, this.config.signal);
|
|
1127
|
+
return await this.deps.model.call(this.deps.systemPrompt, messages, this.deps.tools, wrappedStream, this.config.signal, this.modelCallRecordingOptions());
|
|
1049
1128
|
}
|
|
1050
1129
|
catch (err) {
|
|
1051
1130
|
// If it's a context or rate limit error, don't fallback — propagate
|
|
@@ -1065,14 +1144,25 @@ export class TurnLoop {
|
|
|
1065
1144
|
// really just a cancel. The signal is the authoritative cancel source.
|
|
1066
1145
|
if (isAbortError(err) || this.config.signal?.aborted)
|
|
1067
1146
|
throw err;
|
|
1147
|
+
// Sensitive tool results are model-facing exactly once. A streaming
|
|
1148
|
+
// fallback would re-send the same pending plaintext in a second request,
|
|
1149
|
+
// so fail the turn and let the unified exit redact returned history.
|
|
1150
|
+
if (this.sensitiveToolResultRedactions.size > 0) {
|
|
1151
|
+
this.config.onStream?.({ type: "tombstone", messageId: assistantMessageId });
|
|
1152
|
+
this.currentTurnLog.warn("turn.streaming_fallback_skipped_sensitive", {
|
|
1153
|
+
cat: "turn",
|
|
1154
|
+
error: err.message,
|
|
1155
|
+
});
|
|
1156
|
+
throw err;
|
|
1157
|
+
}
|
|
1068
1158
|
// Streaming might have partially emitted — send tombstone to revoke
|
|
1069
|
-
this.config.onStream?.({ type: "tombstone", messageId:
|
|
1159
|
+
this.config.onStream?.({ type: "tombstone", messageId: assistantMessageId });
|
|
1070
1160
|
this.currentTurnLog.warn("turn.streaming_fallback", {
|
|
1071
1161
|
cat: "turn",
|
|
1072
1162
|
error: err.message,
|
|
1073
1163
|
});
|
|
1074
1164
|
// Retry without streaming
|
|
1075
|
-
return await this.deps.model.callWithoutStreaming(this.deps.systemPrompt, messages, this.deps.tools, this.config.signal);
|
|
1165
|
+
return await this.deps.model.callWithoutStreaming(this.deps.systemPrompt, messages, this.deps.tools, this.config.signal, this.modelCallRecordingOptions());
|
|
1076
1166
|
}
|
|
1077
1167
|
}
|
|
1078
1168
|
get currentTurn() {
|
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
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SandboxBackend } from "../../tool-system/sandbox/index.js";
|
|
2
|
+
export interface WorktreeSession {
|
|
3
|
+
originalCwd: string;
|
|
4
|
+
worktreePath: string;
|
|
5
|
+
worktreeName: string;
|
|
6
|
+
worktreeBranch: string;
|
|
7
|
+
originalBranch?: string;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
}
|
|
11
|
+
/** Per-platform setup/cleanup scripts (a project's localEnvironment). */
|
|
12
|
+
export interface PlatformScripts {
|
|
13
|
+
default?: string;
|
|
14
|
+
macos?: string;
|
|
15
|
+
linux?: string;
|
|
16
|
+
windows?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CreateWorktreeOptions {
|
|
19
|
+
prefix?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pick the setup/cleanup script for the running platform, falling back to
|
|
23
|
+
* `default`. Empty/whitespace-only scripts are treated as absent so a project
|
|
24
|
+
* can leave a platform key blank without spawning an empty shell. `platform`
|
|
25
|
+
* defaults to `process.platform` so callers usually omit it; tests pass a
|
|
26
|
+
* fixed value.
|
|
27
|
+
*/
|
|
28
|
+
export declare function selectPlatformScript(scripts: PlatformScripts | undefined, platform?: NodeJS.Platform): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Create an isolated git worktree for an agent session.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createWorktree(cwd: string, slug: string, sessionId: string, opts?: CreateWorktreeOptions): Promise<WorktreeSession>;
|
|
33
|
+
export interface WorktreeSetupResult {
|
|
34
|
+
/** True if no setup script was configured for this platform (nothing ran). */
|
|
35
|
+
skipped: boolean;
|
|
36
|
+
/** True if a script ran and exited 0. */
|
|
37
|
+
ok: boolean;
|
|
38
|
+
/** Combined stdout/stderr, for surfacing to the user on failure. */
|
|
39
|
+
output: string;
|
|
40
|
+
/** Exit code when a script ran; undefined when skipped. */
|
|
41
|
+
exitCode?: number | null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Run a project's `localEnvironment.setupScripts` once, in the freshly-created
|
|
45
|
+
* worktree's root, right after `git worktree add`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function runWorktreeSetup(worktreePath: string, script: string | undefined, opts?: {
|
|
48
|
+
sandbox?: SandboxBackend;
|
|
49
|
+
shellEnv?: Record<string, string>;
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
signal?: AbortSignal;
|
|
52
|
+
}): Promise<WorktreeSetupResult>;
|
|
53
|
+
export interface RemoveWorktreeResult {
|
|
54
|
+
/** True once `git worktree remove` completed and the directory is gone. */
|
|
55
|
+
dirRemoved: boolean;
|
|
56
|
+
/** The branch targeted for deletion when removeBranch=true. */
|
|
57
|
+
branch?: string;
|
|
58
|
+
/** True when removeBranch=true and branch deletion completed. */
|
|
59
|
+
branchDeleted?: boolean;
|
|
60
|
+
/** Non-empty when the worktree directory is gone but branch deletion failed. */
|
|
61
|
+
branchError?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface RemoveWorktreeOptions {
|
|
64
|
+
prefix?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Remove a worktree and optionally its branch.
|
|
68
|
+
*/
|
|
69
|
+
export declare function removeWorktree(worktreePath: string, removeBranch?: boolean, opts?: RemoveWorktreeOptions): RemoveWorktreeResult;
|