@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.
Files changed (168) hide show
  1. package/THIRD_PARTY_NOTICES.md +206 -0
  2. package/dist/automation/scheduler.d.ts +13 -7
  3. package/dist/automation/scheduler.js +116 -37
  4. package/dist/capability-control/service.d.ts +2 -0
  5. package/dist/capability-control/service.js +4 -2
  6. package/dist/cc-orchestrator/agent-adapter.d.ts +2 -0
  7. package/dist/cc-orchestrator/agent-adapter.js +7 -1
  8. package/dist/cc-orchestrator/codex-session-history.js +1 -1
  9. package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
  10. package/dist/cc-orchestrator/cwd-normalize.js +19 -0
  11. package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
  12. package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
  13. package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
  14. package/dist/cc-orchestrator/external-agent-driver.js +102 -51
  15. package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
  16. package/dist/cc-orchestrator/external-agent-session-store.js +146 -0
  17. package/dist/cc-orchestrator/session-history.js +2 -2
  18. package/dist/cli/agent-server-stdio.js +9 -2
  19. package/dist/context/compaction.d.ts +8 -1
  20. package/dist/context/compaction.js +49 -4
  21. package/dist/context/manager.d.ts +16 -2
  22. package/dist/context/manager.js +103 -19
  23. package/dist/credentials/access.d.ts +56 -0
  24. package/dist/credentials/access.js +183 -0
  25. package/dist/credentials/index.d.ts +1 -0
  26. package/dist/credentials/index.js +1 -0
  27. package/dist/credentials/inject-credential-tool.d.ts +3 -1
  28. package/dist/credentials/inject-credential-tool.js +30 -11
  29. package/dist/credentials/types.d.ts +2 -2
  30. package/dist/credentials/use-credential-tool.d.ts +9 -1
  31. package/dist/credentials/use-credential-tool.js +58 -45
  32. package/dist/engine/engine.d.ts +17 -1
  33. package/dist/engine/engine.js +247 -89
  34. package/dist/engine/image-policy.d.ts +6 -0
  35. package/dist/engine/image-policy.js +17 -6
  36. package/dist/engine/input-attachments.d.ts +13 -0
  37. package/dist/engine/input-attachments.js +255 -0
  38. package/dist/engine/model-facade.d.ts +5 -2
  39. package/dist/engine/model-facade.js +4 -4
  40. package/dist/engine/parse-task.d.ts +10 -0
  41. package/dist/engine/parse-task.js +5 -0
  42. package/dist/engine/streaming-tool-queue.d.ts +11 -7
  43. package/dist/engine/streaming-tool-queue.js +11 -7
  44. package/dist/engine/turn-loop.d.ts +7 -1
  45. package/dist/engine/turn-loop.js +117 -27
  46. package/dist/engine/types.d.ts +8 -0
  47. package/dist/git/worktree/crud.d.ts +69 -0
  48. package/dist/git/worktree/crud.js +206 -0
  49. package/dist/git/worktree/diff.d.ts +14 -0
  50. package/dist/git/worktree/diff.js +82 -0
  51. package/dist/git/worktree/git-exec.d.ts +7 -0
  52. package/dist/git/worktree/git-exec.js +51 -0
  53. package/dist/git/worktree/index.d.ts +5 -0
  54. package/dist/git/worktree/index.js +5 -0
  55. package/dist/git/worktree/query.d.ts +42 -0
  56. package/dist/git/worktree/query.js +121 -0
  57. package/dist/git/worktree/slug.d.ts +11 -0
  58. package/dist/git/worktree/slug.js +58 -0
  59. package/dist/git/worktree.d.ts +1 -84
  60. package/dist/git/worktree.js +5 -230
  61. package/dist/index.d.ts +31 -28
  62. package/dist/index.js +27 -25
  63. package/dist/logging/logger.js +6 -6
  64. package/dist/logging/sanitize-messages.d.ts +10 -2
  65. package/dist/logging/sanitize-messages.js +21 -6
  66. package/dist/plugins/installer/checkUpdate.d.ts +4 -1
  67. package/dist/plugins/installer/checkUpdate.js +4 -2
  68. package/dist/plugins/installer/install.js +2 -0
  69. package/dist/plugins/installer/installFromSource.js +9 -1
  70. package/dist/plugins/installer/parseSource.d.ts +4 -1
  71. package/dist/plugins/installer/parseSource.js +28 -10
  72. package/dist/plugins/installer/sourcePath.d.ts +9 -0
  73. package/dist/plugins/installer/sourcePath.js +50 -0
  74. package/dist/plugins/installer/update.d.ts +4 -1
  75. package/dist/plugins/installer/update.js +5 -3
  76. package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
  77. package/dist/plugins/parseMarketplaceInput.js +4 -3
  78. package/dist/plugins/pluginInstaller.js +24 -22
  79. package/dist/preset/index.d.ts +1 -0
  80. package/dist/preset/index.js +16 -9
  81. package/dist/prompt/sections/base.md +1 -1
  82. package/dist/protocol/chat-session-manager.d.ts +2 -0
  83. package/dist/protocol/chat-session-manager.js +38 -2
  84. package/dist/protocol/chat-session.d.ts +3 -0
  85. package/dist/protocol/chat-session.js +1 -0
  86. package/dist/protocol/client.d.ts +9 -4
  87. package/dist/protocol/client.js +18 -1
  88. package/dist/protocol/server.d.ts +30 -0
  89. package/dist/protocol/server.js +246 -46
  90. package/dist/protocol/types.d.ts +47 -0
  91. package/dist/protocol/types.js +6 -0
  92. package/dist/run/FileRunStore.js +10 -1
  93. package/dist/run/Heartbeat.js +12 -0
  94. package/dist/run/RunApprovalBackend.d.ts +3 -0
  95. package/dist/run/RunApprovalBackend.js +41 -6
  96. package/dist/run/RunLock.js +2 -0
  97. package/dist/run/RunManager.d.ts +2 -0
  98. package/dist/run/RunManager.js +64 -24
  99. package/dist/run/ids.d.ts +2 -0
  100. package/dist/run/ids.js +23 -0
  101. package/dist/runtime/background-shell.d.ts +1 -0
  102. package/dist/runtime/background-shell.js +23 -13
  103. package/dist/runtime/spawn-common.js +10 -0
  104. package/dist/services/auto-dream.d.ts +15 -4
  105. package/dist/services/auto-dream.js +20 -20
  106. package/dist/services/dream-consolidation.d.ts +2 -3
  107. package/dist/services/dream-consolidation.js +65 -15
  108. package/dist/services/extract-memories.d.ts +14 -5
  109. package/dist/services/extract-memories.js +20 -3
  110. package/dist/services/global-dream-promotion.d.ts +23 -0
  111. package/dist/services/global-dream-promotion.js +112 -0
  112. package/dist/services/memory-orchestrator.js +347 -34
  113. package/dist/session/memory.d.ts +61 -18
  114. package/dist/session/memory.js +342 -79
  115. package/dist/session/session-manager.d.ts +35 -1
  116. package/dist/session/session-manager.js +190 -3
  117. package/dist/session/transcript.d.ts +1 -1
  118. package/dist/session/transcript.js +17 -5
  119. package/dist/settings/manager.d.ts +1 -0
  120. package/dist/settings/manager.js +87 -37
  121. package/dist/settings/schema-export.d.ts +2 -3
  122. package/dist/settings/schema-export.js +2 -3
  123. package/dist/settings/schema.d.ts +21 -0
  124. package/dist/settings/schema.js +12 -0
  125. package/dist/skills/scanner.d.ts +3 -2
  126. package/dist/skills/scanner.js +12 -9
  127. package/dist/tool-system/builtin/background-jobs.d.ts +10 -1
  128. package/dist/tool-system/builtin/background-jobs.js +12 -1
  129. package/dist/tool-system/builtin/background-work.d.ts +17 -18
  130. package/dist/tool-system/builtin/background-work.js +51 -5
  131. package/dist/tool-system/builtin/config.d.ts +2 -1
  132. package/dist/tool-system/builtin/config.js +16 -11
  133. package/dist/tool-system/builtin/drive-claude-code.d.ts +16 -2
  134. package/dist/tool-system/builtin/drive-claude-code.js +301 -47
  135. package/dist/tool-system/builtin/edit.js +5 -2
  136. package/dist/tool-system/builtin/generate-video.d.ts +1 -0
  137. package/dist/tool-system/builtin/generate-video.js +13 -4
  138. package/dist/tool-system/builtin/index.d.ts +8 -3
  139. package/dist/tool-system/builtin/index.js +37 -21
  140. package/dist/tool-system/builtin/lsp.d.ts +2 -1
  141. package/dist/tool-system/builtin/lsp.js +6 -3
  142. package/dist/tool-system/builtin/memory.js +40 -8
  143. package/dist/tool-system/builtin/notebook-edit.js +5 -2
  144. package/dist/tool-system/builtin/powershell.d.ts +5 -2
  145. package/dist/tool-system/builtin/powershell.js +11 -7
  146. package/dist/tool-system/builtin/read.js +118 -6
  147. package/dist/tool-system/builtin/view-image.d.ts +2 -2
  148. package/dist/tool-system/builtin/view-image.js +109 -19
  149. package/dist/tool-system/builtin/worktree.d.ts +4 -4
  150. package/dist/tool-system/builtin/worktree.js +297 -74
  151. package/dist/tool-system/builtin/write.js +5 -3
  152. package/dist/tool-system/context.d.ts +23 -1
  153. package/dist/tool-system/executor.d.ts +1 -5
  154. package/dist/tool-system/executor.js +94 -115
  155. package/dist/tool-system/mcp-manager.d.ts +18 -5
  156. package/dist/tool-system/mcp-manager.js +140 -74
  157. package/dist/tool-system/path-policy.d.ts +2 -0
  158. package/dist/tool-system/path-policy.js +41 -12
  159. package/dist/tool-system/permission.d.ts +28 -7
  160. package/dist/tool-system/permission.js +130 -49
  161. package/dist/tool-system/registry.js +11 -4
  162. package/dist/tool-system/tool-result-redaction.d.ts +7 -0
  163. package/dist/tool-system/tool-result-redaction.js +48 -0
  164. package/dist/tool-system/workspace-bridge.d.ts +11 -0
  165. package/dist/tool-system/workspace-bridge.js +1 -0
  166. package/dist/types.d.ts +55 -4
  167. package/dist/utils/toolDisplay.js +1 -1
  168. package/package.json +4 -3
@@ -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({ type: "usage_update", promptTokens: ctx });
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?.({ type: "stream_request_start", turnNumber: this.turnCount });
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
- // Context management (async — may trigger LLM summarization)
458
- messages = await this.deps.contextManager.manageAsync(messages);
459
- // manageAsync can itself issue an LLM summarization call lasting several
460
- // seconds; if the signal aborted during it, stop here rather than
461
- // proceeding into the (expensive) main model call. Belt to the loop-top
462
- // brace: this catches an abort that landed *inside* context management.
463
- if (this.config.signal?.aborted) {
464
- this.markStopped();
465
- return { text: finalText, reason: "aborted_streaming", messages };
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 = this.deps.consumePendingCompactInfo?.();
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
- // Streaming tool queue: start concurrency-safe tools during streaming
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
- this.deps.transcript.appendToolResult(result.id, result.toolName, result.result, result.error);
804
- this.config.onStream?.({ type: "tool_result", result });
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?.({ type: "tool_summary", summary });
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
- messages = this.deps.contextManager.manage(messages);
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.config.onStream?.({ type: "turn_complete", reason: "max_turns" });
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: `turn_${this.turnCount}` });
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() {
@@ -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;