@oh-my-pi/pi-coding-agent 15.10.11 → 15.10.12

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 (121) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/cli.js +5349 -5328
  3. package/dist/types/cli/args.d.ts +1 -0
  4. package/dist/types/cli-commands.d.ts +12 -0
  5. package/dist/types/commands/launch.d.ts +4 -0
  6. package/dist/types/config/api-key-resolver.d.ts +3 -0
  7. package/dist/types/config/model-registry.d.ts +1 -0
  8. package/dist/types/config/model-resolver.d.ts +18 -0
  9. package/dist/types/config/settings-schema.d.ts +29 -1
  10. package/dist/types/config/settings.d.ts +7 -0
  11. package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
  12. package/dist/types/eval/py/executor.d.ts +5 -0
  13. package/dist/types/eval/py/kernel.d.ts +6 -1
  14. package/dist/types/eval/py/runtime.d.ts +9 -0
  15. package/dist/types/exec/bash-executor.d.ts +2 -0
  16. package/dist/types/extensibility/extensions/runner.d.ts +3 -2
  17. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  18. package/dist/types/memory-backend/index.d.ts +1 -0
  19. package/dist/types/memory-backend/runtime.d.ts +4 -0
  20. package/dist/types/memory-backend/types.d.ts +66 -1
  21. package/dist/types/modes/index.d.ts +3 -3
  22. package/dist/types/modes/interactive-mode.d.ts +7 -2
  23. package/dist/types/modes/oauth-manual-input.d.ts +7 -0
  24. package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
  25. package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
  26. package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
  27. package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
  28. package/dist/types/modes/setup-wizard/index.d.ts +5 -1
  29. package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
  30. package/dist/types/modes/types.d.ts +2 -0
  31. package/dist/types/secrets/index.d.ts +1 -1
  32. package/dist/types/secrets/obfuscator.d.ts +8 -2
  33. package/dist/types/session/agent-session.d.ts +14 -2
  34. package/dist/types/session/streaming-output.d.ts +23 -0
  35. package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
  36. package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
  37. package/dist/types/slash-commands/types.d.ts +1 -1
  38. package/dist/types/system-prompt.d.ts +2 -0
  39. package/dist/types/task/executor.d.ts +1 -0
  40. package/dist/types/task/index.d.ts +2 -2
  41. package/dist/types/task/types.d.ts +8 -0
  42. package/dist/types/thinking.d.ts +4 -0
  43. package/dist/types/tiny/title-client.d.ts +11 -0
  44. package/dist/types/tiny/title-protocol.d.ts +1 -0
  45. package/dist/types/tools/index.d.ts +6 -0
  46. package/dist/types/utils/git.d.ts +15 -2
  47. package/dist/types/utils/title-generator.d.ts +3 -2
  48. package/package.json +10 -10
  49. package/src/auto-thinking/classifier.ts +1 -0
  50. package/src/cli/args.ts +3 -0
  51. package/src/cli-commands.ts +29 -0
  52. package/src/cli.ts +8 -9
  53. package/src/commands/launch.ts +4 -0
  54. package/src/commit/model-selection.ts +3 -2
  55. package/src/config/api-key-resolver.ts +8 -6
  56. package/src/config/model-registry.ts +97 -30
  57. package/src/config/model-resolver.ts +60 -0
  58. package/src/config/settings-schema.ts +43 -15
  59. package/src/config/settings.ts +61 -3
  60. package/src/edit/hashline/execute.ts +39 -2
  61. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  62. package/src/eval/completion-bridge.ts +1 -0
  63. package/src/eval/py/executor.ts +29 -7
  64. package/src/eval/py/index.ts +6 -1
  65. package/src/eval/py/kernel.ts +31 -11
  66. package/src/eval/py/runtime.ts +37 -0
  67. package/src/exec/bash-executor.ts +82 -3
  68. package/src/extensibility/extensions/get-commands-handler.ts +2 -1
  69. package/src/extensibility/extensions/runner.ts +6 -1
  70. package/src/extensibility/extensions/types.ts +3 -0
  71. package/src/hindsight/bank.ts +17 -2
  72. package/src/internal-urls/docs-index.generated.ts +3 -3
  73. package/src/main.ts +18 -6
  74. package/src/memories/index.ts +2 -0
  75. package/src/memory-backend/index.ts +1 -0
  76. package/src/memory-backend/local-backend.ts +9 -0
  77. package/src/memory-backend/off-backend.ts +9 -0
  78. package/src/memory-backend/runtime.ts +66 -0
  79. package/src/memory-backend/types.ts +81 -1
  80. package/src/mnemopi/backend.ts +151 -4
  81. package/src/modes/acp/acp-agent.ts +119 -11
  82. package/src/modes/components/assistant-message.ts +19 -21
  83. package/src/modes/components/footer.ts +3 -1
  84. package/src/modes/components/status-line/component.ts +118 -34
  85. package/src/modes/controllers/command-controller.ts +1 -1
  86. package/src/modes/controllers/input-controller.ts +1 -0
  87. package/src/modes/controllers/mcp-command-controller.ts +38 -3
  88. package/src/modes/index.ts +3 -21
  89. package/src/modes/interactive-mode.ts +39 -9
  90. package/src/modes/oauth-manual-input.ts +30 -3
  91. package/src/modes/rpc/rpc-client.ts +154 -3
  92. package/src/modes/rpc/rpc-mode.ts +97 -12
  93. package/src/modes/rpc/rpc-subagents.ts +265 -0
  94. package/src/modes/rpc/rpc-types.ts +81 -1
  95. package/src/modes/setup-wizard/index.ts +12 -2
  96. package/src/modes/setup-wizard/lazy.ts +16 -0
  97. package/src/modes/types.ts +2 -0
  98. package/src/sdk.ts +8 -1
  99. package/src/secrets/index.ts +8 -1
  100. package/src/secrets/obfuscator.ts +39 -18
  101. package/src/session/agent-session.ts +179 -54
  102. package/src/session/streaming-output.ts +166 -10
  103. package/src/slash-commands/acp-builtins.ts +24 -0
  104. package/src/slash-commands/builtin-registry.ts +20 -0
  105. package/src/slash-commands/types.ts +1 -1
  106. package/src/system-prompt.ts +14 -0
  107. package/src/task/executor.ts +13 -12
  108. package/src/task/index.ts +9 -8
  109. package/src/task/render.ts +18 -3
  110. package/src/task/types.ts +9 -0
  111. package/src/thinking.ts +7 -0
  112. package/src/tiny/title-client.ts +34 -5
  113. package/src/tiny/title-protocol.ts +1 -1
  114. package/src/tiny/worker.ts +6 -4
  115. package/src/tools/bash.ts +46 -5
  116. package/src/tools/image-gen.ts +11 -4
  117. package/src/tools/index.ts +13 -1
  118. package/src/tools/inspect-image.ts +1 -0
  119. package/src/utils/commit-message-generator.ts +1 -0
  120. package/src/utils/git.ts +267 -13
  121. package/src/utils/title-generator.ts +24 -5
@@ -108,6 +108,7 @@ import { shouldEnableAppendOnlyContext } from "../config/append-only-context-mod
108
108
  import type { ModelRegistry } from "../config/model-registry";
109
109
  import {
110
110
  extractExplicitThinkingSelector,
111
+ filterAvailableModelsByEnabledPatterns,
111
112
  formatModelSelectorValue,
112
113
  formatModelString,
113
114
  getModelMatchPreferences,
@@ -184,7 +185,12 @@ import planModeToolDecisionReminderPrompt from "../prompts/system/plan-mode-tool
184
185
  import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
185
186
  import ttsrToolReminderTemplate from "../prompts/system/ttsr-tool-reminder.md" with { type: "text" };
186
187
  import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
187
- import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
188
+ import {
189
+ deobfuscateSessionContext,
190
+ obfuscateProviderContext,
191
+ obfuscateProviderTools,
192
+ type SecretObfuscator,
193
+ } from "../secrets/obfuscator";
188
194
  import { invalidateHostMetadata } from "../ssh/connection-manager";
189
195
  import {
190
196
  AUTO_THINKING,
@@ -192,6 +198,7 @@ import {
192
198
  clampAutoThinkingEffort,
193
199
  resolveProvisionalAutoLevel,
194
200
  resolveThinkingLevelForModel,
201
+ shouldDisableReasoning,
195
202
  toReasoningEffort,
196
203
  } from "../thinking";
197
204
  import { shutdownTinyTitleClient } from "../tiny/title-client";
@@ -324,6 +331,8 @@ export interface AgentSessionConfig {
324
331
  agent: Agent;
325
332
  sessionManager: SessionManager;
326
333
  settings: Settings;
334
+ /** Whether the caller explicitly requested yolo/auto-approve behavior for this session. */
335
+ autoApprove?: boolean;
327
336
  /** Models to cycle through with Ctrl+P (from --models flag) */
328
337
  scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
329
338
  /** Initial session thinking selector. */
@@ -839,6 +848,7 @@ export class AgentSession {
839
848
  readonly settings: Settings;
840
849
  readonly yieldQueue: YieldQueue;
841
850
  fileSnapshotStore?: InMemorySnapshotStore;
851
+ #autoApprove: boolean;
842
852
 
843
853
  #powerAssertion: MacOSPowerAssertion | undefined;
844
854
 
@@ -1118,6 +1128,7 @@ export class AgentSession {
1118
1128
  this.agent = config.agent;
1119
1129
  this.sessionManager = config.sessionManager;
1120
1130
  this.settings = config.settings;
1131
+ this.#autoApprove = config.autoApprove === true;
1121
1132
  // Power assertions are taken per turn (see #beginInFlight); nothing acquired here.
1122
1133
  this.#evalKernelOwnerId = config.evalKernelOwnerId ?? `agent-session:${Snowflake.next()}`;
1123
1134
  this.#parentEvalSessionId = config.parentEvalSessionId;
@@ -1133,6 +1144,7 @@ export class AgentSession {
1133
1144
  } else {
1134
1145
  this.#thinkingLevel = config.thinkingLevel;
1135
1146
  }
1147
+ this.#applyThinkingLevelToAgent(this.#thinkingLevel);
1136
1148
  this.#promptTemplates = config.promptTemplates ?? [];
1137
1149
  this.#slashCommands = config.slashCommands ?? [];
1138
1150
  this.#extensionRunner = config.extensionRunner;
@@ -3529,12 +3541,26 @@ export class AgentSession {
3529
3541
  * Wrap a tool with a permission-gate proxy when an ACP client is connected.
3530
3542
  * Only wraps tools whose name is in PERMISSION_REQUIRED_TOOLS and only when
3531
3543
  * the bridge exposes `requestPermission`. No-ops for all other cases.
3544
+ *
3545
+ * When the user has explicitly opted into `yolo` / auto-approve behavior (via
3546
+ * the SDK/CLI `autoApprove` flag or a configured `tools.approvalMode: yolo`),
3547
+ * skips the gate unless the per-tool policy explicitly requires a prompt or
3548
+ * deny. The schema default is also `yolo`, so an explicit configuration or
3549
+ * explicit session flag is required: default-config ACP sessions keep the
3550
+ * client-side permission gate.
3532
3551
  */
3533
3552
  #wrapToolForAcpPermission<T extends AgentTool>(tool: T): T {
3534
3553
  const bridge = this.#clientBridge;
3535
3554
  // Match the capability+method gating pattern used by read/write/bash.
3536
3555
  if (!bridge?.capabilities.requestPermission || !bridge.requestPermission) return tool;
3537
3556
  if (!PERMISSION_REQUIRED_TOOLS.has(tool.name)) return tool;
3557
+ // Skip the gate only on explicit yolo opt-in; honour per-tool policies
3558
+ // that require a prompt or deny (matching the normal approval wrapper).
3559
+ if (this.#isExplicitAutoApproveMode()) {
3560
+ const userPolicies = (this.settings.get("tools.approval") ?? {}) as Record<string, unknown>;
3561
+ const toolPolicy = userPolicies[tool.name];
3562
+ if (!toolPolicy || toolPolicy === "allow") return tool;
3563
+ }
3538
3564
  return new Proxy(tool, {
3539
3565
  get: (target, prop) => {
3540
3566
  if (prop !== "execute") return Reflect.get(target, prop, target);
@@ -3622,6 +3648,13 @@ export class AgentSession {
3622
3648
  }) as T;
3623
3649
  }
3624
3650
 
3651
+ #isExplicitAutoApproveMode(): boolean {
3652
+ return (
3653
+ this.#autoApprove ||
3654
+ (this.settings.isConfigured("tools.approvalMode") && this.settings.get("tools.approvalMode") === "yolo")
3655
+ );
3656
+ }
3657
+
3625
3658
  async #applyActiveToolsByName(
3626
3659
  toolNames: string[],
3627
3660
  options?: { persistMCPSelection?: boolean; previousSelectedMCPToolNames?: string[] },
@@ -3999,6 +4032,47 @@ export class AgentSession {
3999
4032
  return deobfuscateSessionContext(this.sessionManager.buildSessionContext(), this.#obfuscator);
4000
4033
  }
4001
4034
 
4035
+ #obfuscateForProvider<T>(value: T): T {
4036
+ if (!this.#obfuscator?.hasSecrets()) return value;
4037
+ return this.#obfuscator.obfuscateObject(value);
4038
+ }
4039
+
4040
+ #obfuscateTextForProvider(text: string | undefined): string | undefined {
4041
+ if (!text || !this.#obfuscator?.hasSecrets()) return text;
4042
+ return this.#obfuscator.obfuscate(text);
4043
+ }
4044
+
4045
+ #obfuscatePreparationForProvider(preparation: CompactionPreparation): CompactionPreparation {
4046
+ if (!this.#obfuscator?.hasSecrets()) return preparation;
4047
+ if (!preparation.previousSummary && !preparation.previousPreserveData) return preparation;
4048
+ return {
4049
+ ...preparation,
4050
+ previousSummary: preparation.previousSummary
4051
+ ? this.#obfuscator.obfuscate(preparation.previousSummary)
4052
+ : preparation.previousSummary,
4053
+ previousPreserveData: preparation.previousPreserveData
4054
+ ? this.#obfuscator.obfuscateObject(preparation.previousPreserveData)
4055
+ : preparation.previousPreserveData,
4056
+ };
4057
+ }
4058
+
4059
+ #deobfuscateFromProvider(text: string): string {
4060
+ if (!this.#obfuscator?.hasSecrets()) return text;
4061
+ return this.#obfuscator.deobfuscate(text);
4062
+ }
4063
+
4064
+ #deobfuscatedProviderTextReadyForDelta(text: string): string {
4065
+ const deobfuscated = this.#deobfuscateFromProvider(text);
4066
+ if (!this.#obfuscator?.hasSecrets()) return deobfuscated;
4067
+ const pendingPlaceholderStart = deobfuscated.match(/#[A-Z0-9]{0,4}$/);
4068
+ if (pendingPlaceholderStart?.index === undefined) return deobfuscated;
4069
+ return deobfuscated.slice(0, pendingPlaceholderStart.index);
4070
+ }
4071
+
4072
+ #convertToLlmForSideRequest(messages: AgentMessage[]): Message[] {
4073
+ return this.#obfuscateForProvider(convertToLlm(messages));
4074
+ }
4075
+
4002
4076
  /** Convert session messages using the same pre-LLM pipeline as the active session. */
4003
4077
  async convertMessagesToLlm(messages: AgentMessage[], signal?: AbortSignal): Promise<Message[]> {
4004
4078
  const transformedMessages = await this.#transformContext(messages, signal);
@@ -4398,21 +4472,28 @@ export class AgentSession {
4398
4472
  * @throws Error if streaming and no streamingBehavior specified
4399
4473
  * @throws Error if no model selected or no API key available (when not streaming)
4400
4474
  */
4401
- async prompt(text: string, options?: PromptOptions): Promise<void> {
4475
+ /**
4476
+ * Returns `false` when the command was fully handled locally (extension or
4477
+ * custom-TS command consumed without calling the LLM). Returns `true` when
4478
+ * the prompt was forwarded to the agent — either directly or queued as a
4479
+ * steer/follow-up. Callers that render a UI or manage turn lifecycle (e.g.
4480
+ * the ACP agent) use this to know whether to expect an `agent_end` event.
4481
+ */
4482
+ async prompt(text: string, options?: PromptOptions): Promise<boolean> {
4402
4483
  const expandPromptTemplates = options?.expandPromptTemplates ?? true;
4403
4484
 
4404
4485
  // Handle extension commands first (execute immediately, even during streaming)
4405
4486
  if (expandPromptTemplates && text.startsWith("/")) {
4406
4487
  const handled = await this.#tryExecuteExtensionCommand(text);
4407
4488
  if (handled) {
4408
- return;
4489
+ return false;
4409
4490
  }
4410
4491
 
4411
4492
  // Try custom commands (TypeScript slash commands)
4412
4493
  const customResult = await this.#tryExecuteCustomCommand(text);
4413
4494
  if (customResult !== null) {
4414
4495
  if (customResult === "") {
4415
- return;
4496
+ return false;
4416
4497
  }
4417
4498
  text = customResult;
4418
4499
  }
@@ -4446,7 +4527,7 @@ export class AgentSession {
4446
4527
  for (const notice of keywordNotices) {
4447
4528
  await this.sendCustomMessage(notice, { deliverAs: options.streamingBehavior });
4448
4529
  }
4449
- return;
4530
+ return true;
4450
4531
  }
4451
4532
 
4452
4533
  // Skip eager todo prelude when the user has already queued a directive
@@ -4486,6 +4567,7 @@ export class AgentSession {
4486
4567
  if (!options?.synthetic) {
4487
4568
  await this.#enforcePlanModeToolDecision();
4488
4569
  }
4570
+ return true;
4489
4571
  }
4490
4572
 
4491
4573
  async promptCustomMessage<T = unknown>(
@@ -5694,16 +5776,25 @@ export class AgentSession {
5694
5776
  }
5695
5777
 
5696
5778
  /**
5697
- * Get all available models with valid API keys.
5779
+ * Get all available models with valid API keys, filtered by `enabledModels` when configured.
5780
+ * See {@link filterAvailableModelsByEnabledPatterns} for supported pattern forms and limitations.
5698
5781
  */
5699
5782
  getAvailableModels(): Model[] {
5700
- return this.#modelRegistry.getAvailable();
5783
+ const all = this.#modelRegistry.getAvailable();
5784
+ const patterns = this.settings.get("enabledModels");
5785
+ if (!patterns || patterns.length === 0) return all;
5786
+ return filterAvailableModelsByEnabledPatterns(all, patterns, this.#modelRegistry);
5701
5787
  }
5702
5788
 
5703
5789
  // =========================================================================
5704
5790
  // Thinking Level Management
5705
5791
  // =========================================================================
5706
5792
 
5793
+ #applyThinkingLevelToAgent(level: ThinkingLevel | undefined): void {
5794
+ this.agent.setThinkingLevel(toReasoningEffort(level));
5795
+ this.agent.setDisableReasoning(shouldDisableReasoning(level));
5796
+ }
5797
+
5707
5798
  /**
5708
5799
  * Set the thinking level. `auto` enables per-turn classification; the selector
5709
5800
  * itself is never written to the session log, but resolved concrete levels are
@@ -5717,7 +5808,7 @@ export class AgentSession {
5717
5808
  this.#autoThinking = true;
5718
5809
  this.#autoResolvedLevel = undefined;
5719
5810
  this.#thinkingLevel = provisional;
5720
- this.agent.setThinkingLevel(toReasoningEffort(provisional));
5811
+ this.#applyThinkingLevelToAgent(provisional);
5721
5812
  if (persist) {
5722
5813
  this.settings.set("defaultThinkingLevel", AUTO_THINKING);
5723
5814
  }
@@ -5733,7 +5824,7 @@ export class AgentSession {
5733
5824
  const isChanging = effectiveLevel !== this.#thinkingLevel;
5734
5825
 
5735
5826
  this.#thinkingLevel = effectiveLevel;
5736
- this.agent.setThinkingLevel(toReasoningEffort(effectiveLevel));
5827
+ this.#applyThinkingLevelToAgent(effectiveLevel);
5737
5828
 
5738
5829
  if (isChanging) {
5739
5830
  this.sessionManager.appendThinkingLevelChange(effectiveLevel);
@@ -5823,7 +5914,7 @@ export class AgentSession {
5823
5914
  const shouldPersistResolution = this.#autoResolvedLevel !== effort;
5824
5915
  this.#autoResolvedLevel = effort;
5825
5916
  this.#thinkingLevel = effort;
5826
- this.agent.setThinkingLevel(toReasoningEffort(effort));
5917
+ this.#applyThinkingLevelToAgent(effort);
5827
5918
  if (shouldPersistResolution) {
5828
5919
  this.sessionManager.appendThinkingLevelChange(effort);
5829
5920
  }
@@ -6177,10 +6268,10 @@ export class AgentSession {
6177
6268
  customInstructions,
6178
6269
  compactionAbortController.signal,
6179
6270
  {
6180
- promptOverride: compactionPrep.hookPrompt,
6181
- extraContext: compactionPrep.hookContext,
6182
- remoteInstructions: this.#baseSystemPrompt.join("\n\n"),
6183
- convertToLlm,
6271
+ promptOverride: this.#obfuscateTextForProvider(compactionPrep.hookPrompt),
6272
+ extraContext: this.#obfuscateForProvider(compactionPrep.hookContext),
6273
+ remoteInstructions: this.#obfuscateForProvider(this.#baseSystemPrompt.join("\n\n")),
6274
+ convertToLlm: messages => this.#convertToLlmForSideRequest(messages),
6184
6275
  },
6185
6276
  );
6186
6277
  summary = result.summary;
@@ -6363,15 +6454,15 @@ export class AgentSession {
6363
6454
  throw new Error(`No API key for ${model.provider}`);
6364
6455
  }
6365
6456
 
6366
- const handoffText = await generateHandoff(
6457
+ const rawHandoffText = await generateHandoff(
6367
6458
  this.agent.state.messages,
6368
6459
  model,
6369
6460
  apiKey,
6370
6461
  {
6371
- systemPrompt: this.#baseSystemPrompt,
6372
- tools: this.agent.state.tools,
6373
- customInstructions,
6374
- convertToLlm,
6462
+ systemPrompt: this.#obfuscateForProvider(this.#baseSystemPrompt),
6463
+ tools: obfuscateProviderTools(this.#obfuscator, this.agent.state.tools),
6464
+ customInstructions: this.#obfuscateTextForProvider(customInstructions),
6465
+ convertToLlm: messages => this.#convertToLlmForSideRequest(messages),
6375
6466
  initiatorOverride: "agent",
6376
6467
  metadata: this.agent.metadataForProvider(model.provider),
6377
6468
  telemetry: resolveTelemetry(this.agent.telemetry, this.sessionId),
@@ -6383,6 +6474,7 @@ export class AgentSession {
6383
6474
  },
6384
6475
  handoffSignal,
6385
6476
  );
6477
+ const handoffText = this.#deobfuscateFromProvider(rawHandoffText);
6386
6478
 
6387
6479
  if (handoffSignal.aborted) {
6388
6480
  throw new Error("Handoff cancelled");
@@ -7344,17 +7436,24 @@ export class AgentSession {
7344
7436
  if (!apiKey) continue;
7345
7437
 
7346
7438
  try {
7347
- return await compact(preparation, candidate, apiKey, customInstructions, signal, {
7348
- ...options,
7349
- metadata: this.agent.metadataForProvider(candidate.provider),
7350
- convertToLlm,
7351
- telemetry,
7352
- // Honor the user's /model thinking selection (incl. `off`) on
7353
- // the manual `/compact` path. Clamped per-model inside compact()
7354
- // via resolveCompactionEffort so unsupported-effort models
7355
- // (xai-oauth/grok-build) don't trip requireSupportedEffort.
7356
- thinkingLevel: this.thinkingLevel,
7357
- });
7439
+ return await compact(
7440
+ this.#obfuscatePreparationForProvider(preparation),
7441
+ candidate,
7442
+ apiKey,
7443
+ this.#obfuscateTextForProvider(customInstructions),
7444
+ signal,
7445
+ {
7446
+ ...options,
7447
+ metadata: this.agent.metadataForProvider(candidate.provider),
7448
+ convertToLlm: messages => this.#convertToLlmForSideRequest(messages),
7449
+ telemetry,
7450
+ // Honor the user's /model thinking selection (incl. `off`) on
7451
+ // the manual `/compact` path. Clamped per-model inside compact()
7452
+ // via resolveCompactionEffort so unsupported-effort models
7453
+ // (xai-oauth/grok-build) don't trip requireSupportedEffort.
7454
+ thinkingLevel: this.thinkingLevel,
7455
+ },
7456
+ );
7358
7457
  } catch (error) {
7359
7458
  if (!this.#isCompactionAuthFailure(error)) {
7360
7459
  throw error;
@@ -7634,20 +7733,27 @@ export class AgentSession {
7634
7733
  let attempt = 0;
7635
7734
  while (true) {
7636
7735
  try {
7637
- compactResult = await compact(preparation, candidate, apiKey, undefined, autoCompactionSignal, {
7638
- promptOverride: compactionPrep.hookPrompt,
7639
- extraContext: compactionPrep.hookContext,
7640
- remoteInstructions: this.#baseSystemPrompt.join("\n\n"),
7641
- metadata: this.agent.metadataForProvider(candidate.provider),
7642
- initiatorOverride: "agent",
7643
- convertToLlm,
7644
- telemetry,
7645
- // Honor the user's /model thinking selection on the
7646
- // auto-compaction path — the most-fired compaction
7647
- // site. Clamped per-model inside compact() via
7648
- // resolveCompactionEffort.
7649
- thinkingLevel: this.thinkingLevel,
7650
- });
7736
+ compactResult = await compact(
7737
+ this.#obfuscatePreparationForProvider(preparation),
7738
+ candidate,
7739
+ apiKey,
7740
+ undefined,
7741
+ autoCompactionSignal,
7742
+ {
7743
+ promptOverride: this.#obfuscateTextForProvider(compactionPrep.hookPrompt),
7744
+ extraContext: this.#obfuscateForProvider(compactionPrep.hookContext),
7745
+ remoteInstructions: this.#obfuscateForProvider(this.#baseSystemPrompt.join("\n\n")),
7746
+ metadata: this.agent.metadataForProvider(candidate.provider),
7747
+ initiatorOverride: "agent",
7748
+ convertToLlm: messages => this.#convertToLlmForSideRequest(messages),
7749
+ telemetry,
7750
+ // Honor the user's /model thinking selection on the
7751
+ // auto-compaction path — the most-fired compaction
7752
+ // site. Clamped per-model inside compact() via
7753
+ // resolveCompactionEffort.
7754
+ thinkingLevel: this.thinkingLevel,
7755
+ },
7756
+ );
7651
7757
  break;
7652
7758
  } catch (error) {
7653
7759
  if (autoCompactionSignal.aborted) {
@@ -8337,6 +8443,7 @@ export class AgentSession {
8337
8443
  {
8338
8444
  retryAfterMs,
8339
8445
  baseUrl: this.model.baseUrl,
8446
+ modelId: this.model.id,
8340
8447
  },
8341
8448
  );
8342
8449
  if (outcome.switched) {
@@ -8533,11 +8640,12 @@ export class AgentSession {
8533
8640
  * @param command The bash command to execute
8534
8641
  * @param onChunk Optional streaming callback for output
8535
8642
  * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)
8643
+ * @param options.useUserShell If true, allow caller to request configured user-shell routing
8536
8644
  */
8537
8645
  async executeBash(
8538
8646
  command: string,
8539
8647
  onChunk?: (chunk: string) => void,
8540
- options?: { excludeFromContext?: boolean },
8648
+ options?: { excludeFromContext?: boolean; useUserShell?: boolean },
8541
8649
  ): Promise<BashResult> {
8542
8650
  const excludeFromContext = options?.excludeFromContext === true;
8543
8651
  const cwd = this.sessionManager.getCwd();
@@ -8565,6 +8673,7 @@ export class AgentSession {
8565
8673
  sessionKey: this.sessionId,
8566
8674
  timeout: clampTimeout("bash") * 1000,
8567
8675
  onMinimizedSave: originalText => this.#saveBashOriginalArtifact(originalText),
8676
+ useUserShell: options?.useUserShell,
8568
8677
  });
8569
8678
 
8570
8679
  this.recordBashResult(command, result, options);
@@ -8690,6 +8799,7 @@ export class AgentSession {
8690
8799
  sessionId: namespacePythonSessionId(sessionId),
8691
8800
  kernelOwnerId: this.#evalKernelOwnerId,
8692
8801
  kernelMode: this.settings.get("python.kernelMode"),
8802
+ interpreter: this.settings.get("python.interpreter")?.trim() || undefined,
8693
8803
  onChunk,
8694
8804
  signal: abortController.signal,
8695
8805
  });
@@ -8982,6 +9092,7 @@ export class AgentSession {
8982
9092
  promptCacheKey: cacheSessionId,
8983
9093
  preferWebsockets: false,
8984
9094
  reasoning: toReasoningEffort(this.thinkingLevel),
9095
+ disableReasoning: shouldDisableReasoning(this.thinkingLevel),
8985
9096
  hideThinkingSummary: this.agent.hideThinkingSummary,
8986
9097
  serviceTier: this.serviceTier,
8987
9098
  signal: args.signal,
@@ -8990,17 +9101,27 @@ export class AgentSession {
8990
9101
  model.provider,
8991
9102
  );
8992
9103
 
8993
- let replyText = "";
9104
+ let providerReplyText = "";
9105
+ let emittedReplyText = "";
8994
9106
  let assistantMessage: AssistantMessage | undefined;
8995
- const stream = streamSimple(model, context, options);
9107
+ const stream = streamSimple(model, obfuscateProviderContext(this.#obfuscator, context), options);
8996
9108
  for await (const event of stream) {
8997
9109
  if (event.type === "text_delta") {
8998
- replyText += event.delta;
8999
- if (args.onTextDelta) args.onTextDelta(event.delta);
9110
+ providerReplyText += event.delta;
9111
+ if (args.onTextDelta) {
9112
+ const readyText = this.#deobfuscatedProviderTextReadyForDelta(providerReplyText);
9113
+ if (readyText.length > emittedReplyText.length) {
9114
+ const delta = readyText.slice(emittedReplyText.length);
9115
+ emittedReplyText = readyText;
9116
+ args.onTextDelta(delta);
9117
+ }
9118
+ }
9000
9119
  continue;
9001
9120
  }
9002
9121
  if (event.type === "done") {
9003
- assistantMessage = event.message;
9122
+ assistantMessage = this.#obfuscator?.hasSecrets()
9123
+ ? { ...event.message, content: this.#obfuscator.deobfuscateObject(event.message.content) }
9124
+ : event.message;
9004
9125
  break;
9005
9126
  }
9006
9127
  if (event.type === "error") {
@@ -9011,6 +9132,10 @@ export class AgentSession {
9011
9132
  if (!assistantMessage) {
9012
9133
  throw new Error("Ephemeral turn ended without a final message");
9013
9134
  }
9135
+ const replyText = this.#deobfuscateFromProvider(providerReplyText);
9136
+ if (args.onTextDelta && replyText.length > emittedReplyText.length) {
9137
+ args.onTextDelta(replyText.slice(emittedReplyText.length));
9138
+ }
9014
9139
  return {
9015
9140
  replyText: args.dedupeReply === false ? replyText.trim() : dedupeIrcReply(replyText.trim()),
9016
9141
  assistantMessage,
@@ -9270,7 +9395,7 @@ export class AgentSession {
9270
9395
  this.#autoResolvedLevel = undefined;
9271
9396
  this.#thinkingLevel = resolveThinkingLevelForModel(this.model, restoredThinkingLevel);
9272
9397
  }
9273
- this.agent.setThinkingLevel(toReasoningEffort(this.#thinkingLevel));
9398
+ this.#applyThinkingLevelToAgent(this.#thinkingLevel);
9274
9399
  this.agent.serviceTier = hasServiceTierEntry
9275
9400
  ? sessionContext.serviceTier
9276
9401
  : configuredServiceTier === "none"
@@ -9327,7 +9452,7 @@ export class AgentSession {
9327
9452
  this.#thinkingLevel = previousThinkingLevel;
9328
9453
  this.#autoThinking = previousAutoThinking;
9329
9454
  this.#autoResolvedLevel = previousAutoResolvedLevel;
9330
- this.agent.setThinkingLevel(toReasoningEffort(previousThinkingLevel));
9455
+ this.#applyThinkingLevelToAgent(previousThinkingLevel);
9331
9456
  this.agent.serviceTier = previousServiceTier;
9332
9457
  this.#syncTodoPhasesFromBranch();
9333
9458
  this.#reconnectToAgent();
@@ -9511,10 +9636,10 @@ export class AgentSession {
9511
9636
  model,
9512
9637
  apiKey,
9513
9638
  signal: this.#branchSummaryAbortController.signal,
9514
- customInstructions: options.customInstructions,
9639
+ customInstructions: this.#obfuscateTextForProvider(options.customInstructions),
9515
9640
  reserveTokens: branchSummarySettings.reserveTokens,
9516
9641
  metadata: this.agent.metadataForProvider(model.provider),
9517
- convertToLlm,
9642
+ convertToLlm: messages => this.#convertToLlmForSideRequest(messages),
9518
9643
  telemetry: resolveTelemetry(this.agent.telemetry, this.sessionId),
9519
9644
  });
9520
9645
  this.#branchSummaryAbortController = undefined;