@oh-my-pi/pi-coding-agent 13.8.0 → 13.9.2

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 (35) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/package.json +7 -7
  3. package/src/capability/rule.ts +0 -4
  4. package/src/cli/agents-cli.ts +1 -1
  5. package/src/cli/args.ts +7 -12
  6. package/src/commands/launch.ts +3 -2
  7. package/src/config/model-resolver.ts +106 -33
  8. package/src/config/settings-schema.ts +14 -2
  9. package/src/config/settings.ts +1 -17
  10. package/src/discovery/helpers.ts +10 -17
  11. package/src/export/html/template.generated.ts +1 -1
  12. package/src/export/html/template.js +37 -15
  13. package/src/extensibility/extensions/loader.ts +1 -2
  14. package/src/extensibility/extensions/types.ts +2 -1
  15. package/src/main.ts +20 -13
  16. package/src/modes/components/agent-dashboard.ts +12 -13
  17. package/src/modes/components/model-selector.ts +157 -59
  18. package/src/modes/components/read-tool-group.ts +36 -2
  19. package/src/modes/components/settings-defs.ts +11 -8
  20. package/src/modes/components/settings-selector.ts +1 -1
  21. package/src/modes/components/thinking-selector.ts +3 -15
  22. package/src/modes/controllers/selector-controller.ts +21 -7
  23. package/src/modes/rpc/rpc-client.ts +2 -2
  24. package/src/modes/rpc/rpc-types.ts +2 -2
  25. package/src/modes/theme/theme.ts +2 -1
  26. package/src/patch/hashline.ts +26 -3
  27. package/src/patch/index.ts +14 -16
  28. package/src/prompts/tools/read.md +2 -2
  29. package/src/sdk.ts +21 -29
  30. package/src/session/agent-session.ts +44 -37
  31. package/src/task/executor.ts +10 -8
  32. package/src/task/types.ts +1 -2
  33. package/src/tools/read.ts +88 -264
  34. package/src/utils/frontmatter.ts +25 -4
  35. package/src/web/scrapers/choosealicense.ts +1 -1
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
+ import type { ThinkingLevel } from "@oh-my-pi/pi-ai";
3
4
  import {
4
5
  detectMacOSAppearance,
5
6
  type HighlightColors as NativeHighlightColors,
@@ -1219,7 +1220,7 @@ export class Theme {
1219
1220
  return this.mode;
1220
1221
  }
1221
1222
 
1222
- getThinkingBorderColor(level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"): (str: string) => string {
1223
+ getThinkingBorderColor(level: ThinkingLevel): (str: string) => string {
1223
1224
  // Map thinking levels to dedicated theme colors
1224
1225
  switch (level) {
1225
1226
  case "off":
@@ -458,6 +458,18 @@ function maybeWarnSuspiciousUnicodeEscapePlaceholder(edits: HashlineEdit[], warn
458
458
  // Edit Application
459
459
  // ═══════════════════════════════════════════════════════════════════════════
460
460
 
461
+ const MIN_AUTOCORRECT_LENGTH = 2;
462
+
463
+ function shouldAutocorrect(line: string, otherLine: string): boolean {
464
+ if (!line || line !== otherLine) return false;
465
+ line = line.trim();
466
+ if (line.length < MIN_AUTOCORRECT_LENGTH) {
467
+ // if brace, we allow
468
+ return line.endsWith("}") || line.endsWith(")");
469
+ }
470
+ return true;
471
+ }
472
+
461
473
  /**
462
474
  * Apply an array of hashline edits to file content.
463
475
  *
@@ -630,9 +642,7 @@ export function applyHashlineEdits(
630
642
  const trailingReplacementLine = newLines[newLines.length - 1]?.trimEnd();
631
643
  const nextSurvivingLine = fileLines[edit.end.line]?.trimEnd();
632
644
  if (
633
- trailingReplacementLine &&
634
- nextSurvivingLine &&
635
- trailingReplacementLine === nextSurvivingLine &&
645
+ shouldAutocorrect(trailingReplacementLine, nextSurvivingLine) &&
636
646
  // Safety: only correct when end-line content differs from the duplicate.
637
647
  // If end already points to the boundary, matching next line is coincidence.
638
648
  fileLines[edit.end.line - 1]?.trimEnd() !== trailingReplacementLine
@@ -642,6 +652,19 @@ export function applyHashlineEdits(
642
652
  `Auto-corrected range replace ${edit.pos.line}#${edit.pos.hash}-${edit.end.line}#${edit.end.hash}: removed trailing replacement line "${trailingReplacementLine}" that duplicated next surviving line`,
643
653
  );
644
654
  }
655
+ const leadingReplacementLine = newLines[0]?.trimEnd();
656
+ const prevSurvivingLine = fileLines[edit.pos.line - 2]?.trimEnd();
657
+ if (
658
+ shouldAutocorrect(leadingReplacementLine, prevSurvivingLine) &&
659
+ // Safety: only correct when pos-line content differs from the duplicate.
660
+ // If pos already points to the boundary, matching prev line is coincidence.
661
+ fileLines[edit.pos.line - 1]?.trimEnd() !== leadingReplacementLine
662
+ ) {
663
+ newLines.shift();
664
+ warnings.push(
665
+ `Auto-corrected range replace ${edit.pos.line}#${edit.pos.hash}-${edit.end.line}#${edit.end.hash}: removed leading replacement line "${leadingReplacementLine}" that duplicated preceding surviving line`,
666
+ );
667
+ }
645
668
  fileLines.splice(edit.pos.line - 1, count, ...newLines);
646
669
  trackFirstChanged(edit.pos.line);
647
670
  }
@@ -322,18 +322,10 @@ export type EditMode = "replace" | "patch" | "hashline";
322
322
 
323
323
  export const DEFAULT_EDIT_MODE: EditMode = "hashline";
324
324
 
325
- export function normalizeEditMode(mode?: string | null): EditMode | null {
326
- switch (mode) {
327
- case "replace":
328
- return "replace";
329
- case "patch":
330
- return "patch";
331
- case "hashline":
332
- return "hashline";
333
- default:
334
- return null;
335
- }
336
- }
325
+ const EDIT_ID: Record<string, EditMode> = Object.fromEntries(
326
+ ["replace", "patch", "hashline"].map(mode => [mode, mode as EditMode]),
327
+ );
328
+ export const normalizeEditMode = (mode?: string | null): EditMode | undefined => EDIT_ID[mode ?? ""];
337
329
 
338
330
  /**
339
331
  * Edit tool implementation.
@@ -408,11 +400,17 @@ export class EditTool implements AgentTool<TInput> {
408
400
  */
409
401
  get mode(): EditMode {
410
402
  if (this.#editMode) return this.#editMode;
403
+ // 1. Check if edit mode is explicitly set for this model
411
404
  const activeModel = this.session.getActiveModelString?.();
412
- const editVariant =
413
- this.session.settings.getEditVariantForModel(activeModel) ??
414
- normalizeEditMode(this.session.settings.get("edit.mode"));
415
- return editVariant ?? DEFAULT_EDIT_MODE;
405
+ const modelVariant = this.session.settings.getEditVariantForModel(activeModel);
406
+ if (modelVariant) return modelVariant;
407
+ // 2. Check if model contains "-spark" substring (default to replace mode)
408
+ if (activeModel?.includes("-spark")) return "replace";
409
+ // 3. Check if edit mode is explicitly set in session settings
410
+ const settingsMode = normalizeEditMode(this.session.settings.get("edit.mode"));
411
+ if (settingsMode) return settingsMode;
412
+ // 4. Default to DEFAULT_EDIT_MODE
413
+ return DEFAULT_EDIT_MODE;
416
414
  }
417
415
 
418
416
  /**
@@ -1,8 +1,8 @@
1
1
  Reads files from local filesystem or internal URLs.
2
2
 
3
3
  <instruction>
4
- - Reads up to {{DEFAULT_MAX_LINES}} lines default
5
- - Use `offset` and `limit` for large files
4
+ - Reads up to {{DEFAULT_LIMIT}} lines default
5
+ - Use `offset` and `limit` for large files; max {{DEFAULT_MAX_LINES}} lines per call
6
6
  {{#if IS_HASHLINE_MODE}}
7
7
  - Text output is CID prefixed: `LINE#ID:content`
8
8
  {{else}}
package/src/sdk.ts CHANGED
@@ -1,12 +1,6 @@
1
- import {
2
- Agent,
3
- type AgentEvent,
4
- type AgentMessage,
5
- type AgentTool,
6
- INTENT_FIELD,
7
- type ThinkingLevel,
8
- } from "@oh-my-pi/pi-agent-core";
9
- import { type Message, type Model, supportsXhigh } from "@oh-my-pi/pi-ai";
1
+ import { Agent, type AgentEvent, type AgentMessage, type AgentTool, INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
2
+ import { type Message, type Model, supportsXhigh, type ThinkingLevel } from "@oh-my-pi/pi-ai";
3
+
10
4
  import { prewarmOpenAICodexResponses } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
11
5
  import type { Component } from "@oh-my-pi/pi-tui";
12
6
  import { $env, getAgentDbPath, getAgentDir, getProjectDir, logger, postmortem } from "@oh-my-pi/pi-utils";
@@ -15,7 +9,7 @@ import { AsyncJobManager } from "./async";
15
9
  import { loadCapability } from "./capability";
16
10
  import { type Rule, ruleCapability } from "./capability/rule";
17
11
  import { ModelRegistry } from "./config/model-registry";
18
- import { formatModelString, parseModelPattern, parseModelString } from "./config/model-resolver";
12
+ import { formatModelString, parseModelPattern, parseModelString, resolveModelRoleValue } from "./config/model-resolver";
19
13
  import {
20
14
  loadPromptTemplates as loadPromptTemplatesInternal,
21
15
  type PromptTemplate,
@@ -651,6 +645,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
651
645
  const hasThinkingEntry = sessionManager.getBranch().some(entry => entry.type === "thinking_level_change");
652
646
 
653
647
  const hasExplicitModel = options.model !== undefined || options.modelPattern !== undefined;
648
+ const modelMatchPreferences = {
649
+ usageOrder: settings.getStorage()?.getModelUsageOrder(),
650
+ };
651
+ const defaultRoleSpec = resolveModelRoleValue(settings.getModelRole("default"), modelRegistry.getAvailable(), {
652
+ settings,
653
+ matchPreferences: modelMatchPreferences,
654
+ });
654
655
  let model = options.model;
655
656
  let modelFallbackMessage: string | undefined;
656
657
  // If session has data, try to restore model from it.
@@ -671,20 +672,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
671
672
 
672
673
  // If still no model, try settings default.
673
674
  // Skip settings fallback when an explicit model was requested.
674
- if (!hasExplicitModel && !model) {
675
- const settingsDefaultModel = settings.getModelRole("default");
676
- if (settingsDefaultModel) {
677
- const parsedModel = parseModelString(settingsDefaultModel);
678
- if (parsedModel) {
679
- const settingsModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
680
- if (settingsModel && (await hasModelApiKey(settingsModel))) {
681
- model = settingsModel;
682
- // Apply thinking level from config role suffix if not already set
683
- if (options.thinkingLevel === undefined && parsedModel.thinkingLevel) {
684
- options.thinkingLevel = parsedModel.thinkingLevel;
685
- }
686
- }
687
- }
675
+ if (!hasExplicitModel && !model && defaultRoleSpec.model) {
676
+ if (await hasModelApiKey(defaultRoleSpec.model)) {
677
+ model = defaultRoleSpec.model;
688
678
  }
689
679
  }
690
680
 
@@ -704,11 +694,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
704
694
 
705
695
  let thinkingLevel = options.thinkingLevel;
706
696
 
707
- // If session has data, restore thinking level from it
708
- if (thinkingLevel === undefined && hasExistingSession) {
709
- thinkingLevel = hasThinkingEntry
710
- ? (existingSession.thinkingLevel as ThinkingLevel)
711
- : ((settings.get("defaultThinkingLevel") ?? "off") as ThinkingLevel);
697
+ // If session has data and includes a thinking entry, restore it
698
+ if (thinkingLevel === undefined && hasExistingSession && hasThinkingEntry) {
699
+ thinkingLevel = existingSession.thinkingLevel as ThinkingLevel;
700
+ }
701
+
702
+ if (thinkingLevel === undefined && !hasExplicitModel && !hasThinkingEntry && defaultRoleSpec.explicitThinkingLevel) {
703
+ thinkingLevel = defaultRoleSpec.thinkingLevel;
712
704
  }
713
705
 
714
706
  // Fall back to settings default
@@ -24,7 +24,6 @@ import {
24
24
  type AgentState,
25
25
  type AgentTool,
26
26
  INTENT_FIELD,
27
- type ThinkingLevel,
28
27
  } from "@oh-my-pi/pi-agent-core";
29
28
  import type {
30
29
  AssistantMessage,
@@ -33,6 +32,7 @@ import type {
33
32
  Model,
34
33
  ProviderSessionState,
35
34
  TextContent,
35
+ ThinkingLevel,
36
36
  ToolCall,
37
37
  ToolChoice,
38
38
  Usage,
@@ -40,6 +40,7 @@ import type {
40
40
  } from "@oh-my-pi/pi-ai";
41
41
  import {
42
42
  calculateRateLimitBackoffMs,
43
+ getAvailableThinkingLevels,
43
44
  isContextOverflow,
44
45
  modelsAreEqual,
45
46
  parseRateLimitReason,
@@ -49,7 +50,7 @@ import { abortableSleep, getAgentDbPath, isEnoent, logger } from "@oh-my-pi/pi-u
49
50
  import type { AsyncJob, AsyncJobManager } from "../async";
50
51
  import type { Rule } from "../capability/rule";
51
52
  import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "../config/model-registry";
52
- import { expandRoleAlias, parseModelString } from "../config/model-resolver";
53
+ import { extractExplicitThinkingSelector, parseModelString, resolveModelRoleValue } from "../config/model-resolver";
53
54
  import { expandPromptTemplate, type PromptTemplate, renderPromptTemplate } from "../config/prompt-templates";
54
55
  import type { Settings, SkillsSettings } from "../config/settings";
55
56
  import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
@@ -252,10 +253,6 @@ export interface HandoffResult {
252
253
  // ============================================================================
253
254
 
254
255
  /** Standard thinking levels */
255
- const THINKING_LEVELS: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high"];
256
-
257
- /** Thinking levels including xhigh (for supported models) */
258
- const THINKING_LEVELS_WITH_XHIGH: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
259
256
 
260
257
  const noOpUIContext: ExtensionUIContext = {
261
258
  select: async (_title, _options, _dialogOptions) => undefined,
@@ -295,7 +292,6 @@ export class AgentSession {
295
292
  readonly settings: Settings;
296
293
 
297
294
  #asyncJobManager: AsyncJobManager | undefined = undefined;
298
-
299
295
  #scopedModels: Array<{ model: Model; thinkingLevel: ThinkingLevel }>;
300
296
  #promptTemplates: PromptTemplate[];
301
297
  #slashCommands: FileSlashCommand[];
@@ -2630,7 +2626,7 @@ export class AgentSession {
2630
2626
 
2631
2627
  this.#setModelWithProviderSessionReset(model);
2632
2628
  this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, role);
2633
- this.settings.setModelRole(role, `${model.provider}/${model.id}`);
2629
+ this.settings.setModelRole(role, this.#formatRoleModelValue(role, model));
2634
2630
  this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
2635
2631
 
2636
2632
  // Re-clamp thinking level for new model's capabilities without persisting settings
@@ -2684,7 +2680,13 @@ export class AgentSession {
2684
2680
 
2685
2681
  const currentModel = this.model;
2686
2682
  if (!currentModel) return undefined;
2687
- const roleModels: Array<{ role: ModelRole; model: Model; thinkingLevel?: ThinkingLevel }> = [];
2683
+ const matchPreferences = { usageOrder: this.settings.getStorage()?.getModelUsageOrder() };
2684
+ const roleModels: Array<{
2685
+ role: ModelRole;
2686
+ model: Model;
2687
+ thinkingLevel?: ThinkingLevel;
2688
+ explicitThinkingLevel: boolean;
2689
+ }> = [];
2688
2690
 
2689
2691
  for (const role of roleOrder) {
2690
2692
  const roleModelStr =
@@ -2693,18 +2695,18 @@ export class AgentSession {
2693
2695
  : this.settings.getModelRole(role);
2694
2696
  if (!roleModelStr) continue;
2695
2697
 
2696
- const expandedRoleModelStr = expandRoleAlias(roleModelStr, this.settings);
2697
- const parsed = parseModelString(expandedRoleModelStr);
2698
- let match: Model | undefined;
2699
- if (parsed) {
2700
- match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
2701
- }
2702
- if (!match) {
2703
- match = availableModels.find(m => m.id.toLowerCase() === expandedRoleModelStr.toLowerCase());
2704
- }
2705
- if (!match) continue;
2698
+ const resolved = resolveModelRoleValue(roleModelStr, availableModels, {
2699
+ settings: this.settings,
2700
+ matchPreferences,
2701
+ });
2702
+ if (!resolved.model) continue;
2706
2703
 
2707
- roleModels.push({ role, model: match, thinkingLevel: parsed?.thinkingLevel });
2704
+ roleModels.push({
2705
+ role,
2706
+ model: resolved.model,
2707
+ thinkingLevel: resolved.thinkingLevel,
2708
+ explicitThinkingLevel: resolved.explicitThinkingLevel,
2709
+ });
2708
2710
  }
2709
2711
 
2710
2712
  if (roleModels.length <= 1) return undefined;
@@ -2724,12 +2726,8 @@ export class AgentSession {
2724
2726
  await this.setModel(next.model, next.role);
2725
2727
  }
2726
2728
 
2727
- // Apply per-role thinking level from config suffix and preserve it for round-tripping
2728
- if (next.thinkingLevel) {
2729
+ if (next.explicitThinkingLevel && next.thinkingLevel !== undefined) {
2729
2730
  this.setThinkingLevel(next.thinkingLevel);
2730
- if (!options?.temporary) {
2731
- this.settings.setModelRole(next.role, `${next.model.provider}/${next.model.id}:${next.thinkingLevel}`);
2732
- }
2733
2731
  }
2734
2732
 
2735
2733
  return { model: next.model, thinkingLevel: this.thinkingLevel, role: next.role };
@@ -2772,7 +2770,7 @@ export class AgentSession {
2772
2770
  // Apply model
2773
2771
  this.#setModelWithProviderSessionReset(next.model);
2774
2772
  this.sessionManager.appendModelChange(`${next.model.provider}/${next.model.id}`);
2775
- this.settings.setModelRole("default", `${next.model.provider}/${next.model.id}`);
2773
+ this.settings.setModelRole("default", this.#formatRoleModelValue("default", next.model));
2776
2774
  this.settings.getStorage()?.recordModelUsage(`${next.model.provider}/${next.model.id}`);
2777
2775
 
2778
2776
  // Apply thinking level (setThinkingLevel clamps to model capabilities)
@@ -2800,7 +2798,7 @@ export class AgentSession {
2800
2798
 
2801
2799
  this.#setModelWithProviderSessionReset(nextModel);
2802
2800
  this.sessionManager.appendModelChange(`${nextModel.provider}/${nextModel.id}`);
2803
- this.settings.setModelRole("default", `${nextModel.provider}/${nextModel.id}`);
2801
+ this.settings.setModelRole("default", this.#formatRoleModelValue("default", nextModel));
2804
2802
  this.settings.getStorage()?.recordModelUsage(`${nextModel.provider}/${nextModel.id}`);
2805
2803
 
2806
2804
  // Re-clamp thinking level for new model's capabilities without persisting settings
@@ -2862,9 +2860,9 @@ export class AgentSession {
2862
2860
  * Get available thinking levels for current model.
2863
2861
  * The provider will clamp to what the specific model supports internally.
2864
2862
  */
2865
- getAvailableThinkingLevels(): ThinkingLevel[] {
2863
+ getAvailableThinkingLevels(): ReadonlyArray<ThinkingLevel> {
2866
2864
  if (!this.supportsThinking()) return ["off"];
2867
- return this.supportsXhighThinking() ? THINKING_LEVELS_WITH_XHIGH : THINKING_LEVELS;
2865
+ return getAvailableThinkingLevels(this.supportsXhighThinking());
2868
2866
  }
2869
2867
 
2870
2868
  /**
@@ -2881,8 +2879,8 @@ export class AgentSession {
2881
2879
  return !!this.model?.reasoning;
2882
2880
  }
2883
2881
 
2884
- #clampThinkingLevel(level: ThinkingLevel, availableLevels: ThinkingLevel[]): ThinkingLevel {
2885
- const ordered = THINKING_LEVELS_WITH_XHIGH;
2882
+ #clampThinkingLevel(level: ThinkingLevel, availableLevels: ReadonlyArray<ThinkingLevel>): ThinkingLevel {
2883
+ const ordered = getAvailableThinkingLevels(true);
2886
2884
  const available = new Set(availableLevels);
2887
2885
  const requestedIndex = ordered.indexOf(level);
2888
2886
  if (requestedIndex === -1) {
@@ -3585,6 +3583,15 @@ Be thorough - include exact file paths, function names, error messages, and tech
3585
3583
  return `${model.provider}/${model.id}`;
3586
3584
  }
3587
3585
 
3586
+ #formatRoleModelValue(role: ModelRole, model: Model): string {
3587
+ const modelKey = `${model.provider}/${model.id}`;
3588
+ const existingRoleValue = this.settings.getModelRole(role);
3589
+ if (!existingRoleValue) return modelKey;
3590
+
3591
+ const thinkingLevel = extractExplicitThinkingSelector(existingRoleValue, this.settings);
3592
+ if (thinkingLevel === undefined) return modelKey;
3593
+ return `${modelKey}:${thinkingLevel}`;
3594
+ }
3588
3595
  #resolveContextPromotionConfiguredTarget(currentModel: Model, availableModels: Model[]): Model | undefined {
3589
3596
  const configuredTarget = currentModel.contextPromotionTarget?.trim();
3590
3597
  if (!configuredTarget) return undefined;
@@ -3607,12 +3614,10 @@ Be thorough - include exact file paths, function names, error messages, and tech
3607
3614
 
3608
3615
  if (!roleModelStr) return undefined;
3609
3616
 
3610
- const parsed = parseModelString(roleModelStr);
3611
- if (parsed) {
3612
- return availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
3613
- }
3614
- const roleLower = roleModelStr.toLowerCase();
3615
- return availableModels.find(m => m.id.toLowerCase() === roleLower);
3617
+ return resolveModelRoleValue(roleModelStr, availableModels, {
3618
+ settings: this.settings,
3619
+ matchPreferences: { usageOrder: this.settings.getStorage()?.getModelUsageOrder() },
3620
+ }).model;
3616
3621
  }
3617
3622
 
3618
3623
  #getCompactionModelCandidates(availableModels: Model[]): Model[] {
@@ -4563,6 +4568,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
4563
4568
 
4564
4569
  if (!skipConversationRestore) {
4565
4570
  this.agent.replaceMessages(sessionContext.messages);
4571
+ this.#closeCodexProviderSessionsForHistoryRewrite();
4566
4572
  }
4567
4573
 
4568
4574
  return { selectedText, cancelled: false };
@@ -4717,6 +4723,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
4717
4723
  const sessionContext = this.sessionManager.buildSessionContext();
4718
4724
  this.agent.replaceMessages(sessionContext.messages);
4719
4725
  this.#syncTodoPhasesFromBranch();
4726
+ this.#closeCodexProviderSessionsForHistoryRewrite();
4720
4727
 
4721
4728
  // Emit session_tree event
4722
4729
  if (this.#extensionRunner) {
@@ -4,8 +4,8 @@
4
4
  * Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
5
5
  */
6
6
  import path from "node:path";
7
- import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
- import type { Api, Model, ToolChoice } from "@oh-my-pi/pi-ai";
7
+ import type { AgentEvent } from "@oh-my-pi/pi-agent-core";
8
+ import type { Api, Model, ThinkingLevel, ToolChoice } from "@oh-my-pi/pi-ai";
9
9
  import { logger, untilAborted } from "@oh-my-pi/pi-utils";
10
10
  import type { TSchema } from "@sinclair/typebox";
11
11
  import Ajv, { type ValidateFunction } from "ajv";
@@ -938,12 +938,14 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
938
938
  await modelRegistry.refresh();
939
939
  checkAbort();
940
940
 
941
- const { model, thinkingLevel: resolvedThinkingLevel } = resolveModelOverride(
942
- modelPatterns,
943
- modelRegistry,
944
- settings,
945
- );
946
- const effectiveThinkingLevel = thinkingLevel ?? resolvedThinkingLevel;
941
+ const {
942
+ model,
943
+ thinkingLevel: resolvedThinkingLevel,
944
+ explicitThinkingLevel,
945
+ } = resolveModelOverride(modelPatterns, modelRegistry, settings);
946
+ const effectiveThinkingLevel = explicitThinkingLevel
947
+ ? resolvedThinkingLevel
948
+ : (thinkingLevel ?? resolvedThinkingLevel);
947
949
 
948
950
  const sessionManager = sessionFile
949
951
  ? await SessionManager.open(sessionFile)
package/src/task/types.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
- import type { Usage } from "@oh-my-pi/pi-ai";
1
+ import type { ThinkingLevel, Usage } from "@oh-my-pi/pi-ai";
3
2
  import { $env } from "@oh-my-pi/pi-utils";
4
3
  import { type Static, Type } from "@sinclair/typebox";
5
4
  import type { NestedRepoPatch } from "./worktree";