@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,58 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.9.2] - 2026-03-05
6
+
7
+ ### Added
8
+
9
+ - Support for Python code execution messages with output display and error handling
10
+ - Support for mode change entries in session exports
11
+ - Support for TTSR injection and session initialization entries in tree filtering
12
+
13
+ ### Changed
14
+
15
+ - Updated label lookup to use `targetId` field instead of `parentId` for label references
16
+ - Changed model change entry display to use `model` field instead of separate `provider` and `modelId` fields
17
+ - Simplified model change rendering by removing OpenAI Codex bridge prompt display
18
+ - Updated searchable text extraction to include Python code from `pythonExecution` messages
19
+
20
+ ### Removed
21
+
22
+ - Removed `codexInjectionInfo` from session data destructuring
23
+ - Removed OpenAI Codex-specific bridge prompt UI from model change entries
24
+
25
+ ### Fixed
26
+
27
+ - Auto-corrected off-by-one range start errors in hashline edits that would duplicate preceding lines
28
+
29
+ ## [13.9.0] - 2026-03-05
30
+ ### Added
31
+
32
+ - Added `read.defaultLimit` setting to configure default number of lines returned by read tool when no limit is specified (default: 300 lines)
33
+ - Added preset options for read default limit (200, 300, 500, 1000, 5000 lines) in settings UI
34
+
35
+ ### Changed
36
+
37
+ - Updated read tool prompt to distinguish between default limit and maximum limit per call
38
+ - Moved `ThinkingLevel` type from `@oh-my-pi/pi-agent-core` to `@oh-my-pi/pi-ai` for centralized thinking level definitions
39
+ - Replaced local thinking level validation with `parseThinkingLevel()` and `ALL_THINKING_LEVELS` from `@oh-my-pi/pi-ai`
40
+ - Updated thinking level option providers to use `THINKING_MODE_DESCRIPTIONS` from `@oh-my-pi/pi-ai` for consistent descriptions
41
+ - Renamed `RoleThinkingMode` type to `ThinkingMode` and changed default value from `'default'` to `'inherit'` for clarity
42
+ - Replaced `formatThinkingEffortLabel()` utility with `formatThinking()` from `@oh-my-pi/pi-ai`
43
+ - Renamed `extractExplicitThinkingLevel()` to `extractExplicitThinkingSelector()` in model resolver
44
+ - Updated thinking level clamping to use `getAvailableThinkingLevel()` from `@oh-my-pi/pi-ai`
45
+
46
+ ### Removed
47
+
48
+ - Removed `thinking-effort-label.ts` utility file (functionality moved to `@oh-my-pi/pi-ai`)
49
+ - Removed local `VALID_THINKING_LEVELS` constant definitions across multiple files
50
+ - Removed `isValidThinkingLevel()` function (replaced by `parseThinkingLevel()` from `@oh-my-pi/pi-ai`)
51
+ - Removed `parseThinkingLevel()` helper from discovery module (now uses centralized version from `@oh-my-pi/pi-ai`)
52
+
53
+ ### Fixed
54
+
55
+ - Fixed provider session state not being cleared when branching or navigating tree history, preventing resource leaks with codex provider sessions
56
+
5
57
  ## [13.8.0] - 2026-03-04
6
58
  ### Added
7
59
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "13.8.0",
4
+ "version": "13.9.2",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -41,12 +41,12 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@mozilla/readability": "^0.6",
44
- "@oh-my-pi/omp-stats": "13.8.0",
45
- "@oh-my-pi/pi-agent-core": "13.8.0",
46
- "@oh-my-pi/pi-ai": "13.8.0",
47
- "@oh-my-pi/pi-natives": "13.8.0",
48
- "@oh-my-pi/pi-tui": "13.8.0",
49
- "@oh-my-pi/pi-utils": "13.8.0",
44
+ "@oh-my-pi/omp-stats": "13.9.2",
45
+ "@oh-my-pi/pi-agent-core": "13.9.2",
46
+ "@oh-my-pi/pi-ai": "13.9.2",
47
+ "@oh-my-pi/pi-natives": "13.9.2",
48
+ "@oh-my-pi/pi-tui": "13.9.2",
49
+ "@oh-my-pi/pi-utils": "13.9.2",
50
50
  "@sinclair/typebox": "^0.34",
51
51
  "@xterm/headless": "^6.0",
52
52
  "ajv": "^8.18",
@@ -20,10 +20,6 @@ export interface RuleFrontmatter {
20
20
  condition?: string | string[];
21
21
  /** New key for TTSR stream scope. */
22
22
  scope?: string | string[];
23
- /** Legacy key accepted for backward compatibility with existing rules. */
24
- ttsr_trigger?: string | string[];
25
- /** Legacy camelCase key accepted for backward compatibility with existing rules. */
26
- ttsrTrigger?: string | string[];
27
23
  [key: string]: unknown;
28
24
  }
29
25
 
@@ -61,7 +61,7 @@ function toFrontmatter(agent: AgentDefinition): Record<string, unknown> {
61
61
  if (agent.tools && agent.tools.length > 0) frontmatter.tools = agent.tools;
62
62
  if (agent.spawns !== undefined) frontmatter.spawns = agent.spawns;
63
63
  if (agent.model && agent.model.length > 0) frontmatter.model = agent.model;
64
- if (agent.thinkingLevel) frontmatter["thinking-level"] = agent.thinkingLevel;
64
+ if (agent.thinkingLevel) frontmatter.thinkingLevel = agent.thinkingLevel;
65
65
  if (agent.output !== undefined) frontmatter.output = agent.output;
66
66
  if (agent.blocking) frontmatter.blocking = true;
67
67
 
package/src/cli/args.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * CLI argument parsing and help display
3
3
  */
4
- import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
4
+ import { getAvailableThinkingLevels, parseThinkingLevel, type ThinkingLevel } from "@oh-my-pi/pi-ai";
5
5
  import { APP_NAME, CONFIG_DIR_NAME, logger } from "@oh-my-pi/pi-utils";
6
6
  import chalk from "chalk";
7
7
  import { BUILTIN_TOOLS } from "../tools";
@@ -48,12 +48,6 @@ export interface Args {
48
48
  unknownFlags: Map<string, boolean | string>;
49
49
  }
50
50
 
51
- const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
52
-
53
- export function isValidThinkingLevel(level: string): level is ThinkingLevel {
54
- return VALID_THINKING_LEVELS.includes(level as ThinkingLevel);
55
- }
56
-
57
51
  export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "boolean" | "string" }>): Args {
58
52
  const result: Args = {
59
53
  messages: [],
@@ -127,13 +121,14 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
127
121
  }
128
122
  result.tools = validTools;
129
123
  } else if (arg === "--thinking" && i + 1 < args.length) {
130
- const level = args[++i];
131
- if (isValidThinkingLevel(level)) {
132
- result.thinking = level;
124
+ const rawThinking = args[++i];
125
+ const thinking = parseThinkingLevel(rawThinking);
126
+ if (thinking !== undefined) {
127
+ result.thinking = thinking;
133
128
  } else {
134
129
  logger.warn("Invalid thinking level passed to --thinking", {
135
- level,
136
- validThinkingLevels: [...VALID_THINKING_LEVELS],
130
+ level: rawThinking,
131
+ validThinkingLevels: getAvailableThinkingLevels(),
137
132
  });
138
133
  }
139
134
  } else if (arg === "--print" || arg === "-p") {
@@ -2,6 +2,7 @@
2
2
  * Root command for the coding agent CLI.
3
3
  */
4
4
 
5
+ import { getAvailableThinkingLevels } from "@oh-my-pi/pi-ai";
5
6
  import { APP_NAME } from "@oh-my-pi/pi-utils";
6
7
  import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
7
8
  import { parseArgs } from "../cli/args";
@@ -85,8 +86,8 @@ export default class Index extends Command {
85
86
  description: "Comma-separated list of tools to enable (default: all)",
86
87
  }),
87
88
  thinking: Flags.string({
88
- description: "Set thinking level: off, minimal, low, medium, high, xhigh",
89
- options: ["off", "minimal", "low", "medium", "high", "xhigh"],
89
+ description: `Set thinking level: ${getAvailableThinkingLevels().join(", ")}`,
90
+ options: getAvailableThinkingLevels(),
90
91
  }),
91
92
  hook: Flags.string({
92
93
  description: "Load a hook/extension file (can be used multiple times)",
@@ -1,10 +1,16 @@
1
1
  /**
2
2
  * Model resolution, scoping, and initial selection
3
3
  */
4
- import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
5
- import { type Api, DEFAULT_MODEL_PER_PROVIDER, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
4
+ import {
5
+ type Api,
6
+ DEFAULT_MODEL_PER_PROVIDER,
7
+ type KnownProvider,
8
+ type Model,
9
+ modelsAreEqual,
10
+ parseThinkingLevel,
11
+ type ThinkingLevel,
12
+ } from "@oh-my-pi/pi-ai";
6
13
  import chalk from "chalk";
7
- import { isValidThinkingLevel } from "../cli/args";
8
14
  import MODEL_PRIO from "../priority.json" with { type: "json" };
9
15
  import { fuzzyMatch } from "../utils/fuzzy";
10
16
  import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
@@ -34,8 +40,9 @@ export function parseModelString(
34
40
  const colonIdx = id.lastIndexOf(":");
35
41
  if (colonIdx !== -1) {
36
42
  const suffix = id.slice(colonIdx + 1);
37
- if (isValidThinkingLevel(suffix)) {
38
- return { provider, id: id.slice(0, colonIdx), thinkingLevel: suffix };
43
+ const thinkingLevel = parseThinkingLevel(suffix);
44
+ if (thinkingLevel) {
45
+ return { provider, id: id.slice(0, colonIdx), thinkingLevel };
39
46
  }
40
47
  }
41
48
  return { provider, id };
@@ -242,7 +249,7 @@ function parseModelPatternWithContext(
242
249
  pattern: string,
243
250
  availableModels: Model<Api>[],
244
251
  context: ModelPreferenceContext,
245
- options?: { allowInvalidThinkingLevelFallback?: boolean },
252
+ options?: { allowInvalidThinkingSelectorFallback?: boolean },
246
253
  ): ParsedModelResult {
247
254
  // Try exact match first
248
255
  const exactMatch = tryMatchModel(pattern, availableModels, context);
@@ -260,7 +267,8 @@ function parseModelPatternWithContext(
260
267
  const prefix = pattern.substring(0, lastColonIndex);
261
268
  const suffix = pattern.substring(lastColonIndex + 1);
262
269
 
263
- if (isValidThinkingLevel(suffix)) {
270
+ const parsedThinkingLevel = parseThinkingLevel(suffix);
271
+ if (parsedThinkingLevel) {
264
272
  // Valid thinking level - recurse on prefix and use this level
265
273
  const result = parseModelPatternWithContext(prefix, availableModels, context, options);
266
274
  if (result.model) {
@@ -268,7 +276,7 @@ function parseModelPatternWithContext(
268
276
  const explicitThinkingLevel = !result.warning;
269
277
  return {
270
278
  model: result.model,
271
- thinkingLevel: explicitThinkingLevel ? suffix : undefined,
279
+ thinkingLevel: explicitThinkingLevel ? parsedThinkingLevel : undefined,
272
280
  warning: result.warning,
273
281
  explicitThinkingLevel,
274
282
  };
@@ -276,7 +284,7 @@ function parseModelPatternWithContext(
276
284
  return result;
277
285
  }
278
286
 
279
- const allowFallback = options?.allowInvalidThinkingLevelFallback ?? true;
287
+ const allowFallback = options?.allowInvalidThinkingSelectorFallback ?? true;
280
288
  if (!allowFallback) {
281
289
  return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
282
290
  }
@@ -298,7 +306,7 @@ export function parseModelPattern(
298
306
  pattern: string,
299
307
  availableModels: Model<Api>[],
300
308
  preferences?: ModelMatchPreferences,
301
- options?: { allowInvalidThinkingLevelFallback?: boolean },
309
+ options?: { allowInvalidThinkingSelectorFallback?: boolean },
302
310
  ): ParsedModelResult {
303
311
  const context = buildPreferenceContext(availableModels, preferences);
304
312
  return parseModelPatternWithContext(pattern, availableModels, context, options);
@@ -331,6 +339,74 @@ export function expandRoleAlias(value: string, settings?: Settings): string {
331
339
  return settings?.getModelRole(role) ?? value;
332
340
  }
333
341
 
342
+ /**
343
+ * Resolve a model role value into a concrete model and thinking metadata.
344
+ */
345
+ export interface ResolvedModelRoleValue {
346
+ model: Model<Api> | undefined;
347
+ thinkingLevel?: ThinkingLevel;
348
+ explicitThinkingLevel: boolean;
349
+ warning: string | undefined;
350
+ }
351
+
352
+ export function resolveModelRoleValue(
353
+ roleValue: string | undefined,
354
+ availableModels: Model<Api>[],
355
+ options?: { settings?: Settings; matchPreferences?: ModelMatchPreferences },
356
+ ): ResolvedModelRoleValue {
357
+ if (!roleValue) {
358
+ return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
359
+ }
360
+
361
+ const normalized = roleValue.trim();
362
+ if (!normalized || normalized === DEFAULT_MODEL_ROLE) {
363
+ return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
364
+ }
365
+
366
+ const lastColonIndex = normalized.lastIndexOf(":");
367
+ const hasThinkingSuffix =
368
+ lastColonIndex > PREFIX_MODEL_ROLE.length && parseThinkingLevel(normalized.slice(lastColonIndex + 1));
369
+ const aliasCandidate = hasThinkingSuffix ? normalized.slice(0, lastColonIndex) : normalized;
370
+ const effectivePattern = expandRoleAlias(aliasCandidate, options?.settings);
371
+ const patternWithSuffix = hasThinkingSuffix
372
+ ? `${effectivePattern}:${normalized.slice(lastColonIndex + 1)}`
373
+ : effectivePattern;
374
+ const { model, thinkingLevel, warning, explicitThinkingLevel } = parseModelPattern(
375
+ patternWithSuffix,
376
+ availableModels,
377
+ options?.matchPreferences,
378
+ );
379
+
380
+ return { model, thinkingLevel, explicitThinkingLevel, warning };
381
+ }
382
+
383
+ export function extractExplicitThinkingSelector(
384
+ value: string | undefined,
385
+ settings?: Settings,
386
+ ): ThinkingLevel | undefined {
387
+ if (!value) return undefined;
388
+ const normalized = value.trim();
389
+ if (!normalized || normalized === DEFAULT_MODEL_ROLE) return undefined;
390
+
391
+ const visited = new Set<string>();
392
+ let current = normalized;
393
+ while (!visited.has(current)) {
394
+ visited.add(current);
395
+ const lastColonIndex = current.lastIndexOf(":");
396
+ const hasThinkingSuffix =
397
+ lastColonIndex > PREFIX_MODEL_ROLE.length && parseThinkingLevel(current.slice(lastColonIndex + 1));
398
+ if (hasThinkingSuffix) {
399
+ return current.slice(lastColonIndex + 1) as ThinkingLevel;
400
+ }
401
+ const expanded = expandRoleAlias(current, settings).trim();
402
+ if (!expanded || expanded === current) break;
403
+ if (expanded === DEFAULT_MODEL_ROLE) return undefined;
404
+ current = expanded;
405
+ }
406
+
407
+ return undefined;
408
+ }
409
+
334
410
  /**
335
411
  * Resolve a model identifier or pattern to a Model instance.
336
412
  */
@@ -341,7 +417,8 @@ export function resolveModelFromString(
341
417
  ): Model<Api> | undefined {
342
418
  const parsed = parseModelString(value);
343
419
  if (parsed) {
344
- return available.find(model => model.provider === parsed.provider && model.id === parsed.id);
420
+ const exact = available.find(model => model.provider === parsed.provider && model.id === parsed.id);
421
+ if (exact) return exact;
345
422
  }
346
423
  return parseModelPattern(value, available, matchPreferences).model;
347
424
  }
@@ -373,25 +450,20 @@ export function resolveModelOverride(
373
450
  modelPatterns: string[],
374
451
  modelRegistry: ModelRegistry,
375
452
  settings?: Settings,
376
- ): { model?: Model<Api>; thinkingLevel?: ThinkingLevel } {
377
- if (modelPatterns.length === 0) return {};
453
+ ): { model?: Model<Api>; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } {
454
+ if (modelPatterns.length === 0) return { explicitThinkingLevel: false };
455
+ const availableModels = modelRegistry.getAvailable();
378
456
  const matchPreferences = { usageOrder: settings?.getStorage()?.getModelUsageOrder() };
379
457
  for (const pattern of modelPatterns) {
380
- const normalized = pattern.trim();
381
- if (!normalized || isDefaultModelAlias(normalized)) {
382
- continue;
383
- }
384
- const effectivePattern = expandRoleAlias(pattern, settings);
385
- const { model, thinkingLevel } = parseModelPattern(
386
- effectivePattern,
387
- modelRegistry.getAvailable(),
458
+ const { model, thinkingLevel, explicitThinkingLevel } = resolveModelRoleValue(pattern, availableModels, {
459
+ settings,
388
460
  matchPreferences,
389
- );
461
+ });
390
462
  if (model) {
391
- return { model, thinkingLevel: thinkingLevel !== "off" ? thinkingLevel : undefined };
463
+ return { model, thinkingLevel, explicitThinkingLevel };
392
464
  }
393
465
  }
394
- return {};
466
+ return { explicitThinkingLevel: false };
395
467
  }
396
468
 
397
469
  /**
@@ -425,8 +497,9 @@ export async function resolveModelScope(
425
497
 
426
498
  if (colonIdx !== -1) {
427
499
  const suffix = pattern.substring(colonIdx + 1);
428
- if (isValidThinkingLevel(suffix)) {
429
- thinkingLevel = suffix;
500
+ const parsedThinkingLevel = parseThinkingLevel(suffix);
501
+ if (parsedThinkingLevel) {
502
+ thinkingLevel = parsedThinkingLevel;
430
503
  explicitThinkingLevel = true;
431
504
  globPattern = pattern.substring(0, colonIdx);
432
505
  }
@@ -553,7 +626,7 @@ export function resolveCliModel(options: {
553
626
 
554
627
  const candidates = provider ? availableModels.filter(model => model.provider === provider) : availableModels;
555
628
  const { model, thinkingLevel, warning } = parseModelPattern(pattern, candidates, preferences, {
556
- allowInvalidThinkingLevelFallback: false,
629
+ allowInvalidThinkingSelectorFallback: false,
557
630
  });
558
631
 
559
632
  if (!model) {
@@ -590,7 +663,7 @@ export async function findInitialModel(options: {
590
663
  isContinuing: boolean;
591
664
  defaultProvider?: string;
592
665
  defaultModelId?: string;
593
- defaultThinkingLevel?: ThinkingLevel;
666
+ defaultThinkingSelector?: ThinkingLevel;
594
667
  modelRegistry: ModelRegistry;
595
668
  }): Promise<InitialModelResult> {
596
669
  const {
@@ -600,7 +673,7 @@ export async function findInitialModel(options: {
600
673
  isContinuing,
601
674
  defaultProvider,
602
675
  defaultModelId,
603
- defaultThinkingLevel,
676
+ defaultThinkingSelector,
604
677
  modelRegistry,
605
678
  } = options;
606
679
 
@@ -620,10 +693,10 @@ export async function findInitialModel(options: {
620
693
  // 2. Use first model from scoped models (skip if continuing/resuming)
621
694
  if (scopedModels.length > 0 && !isContinuing) {
622
695
  const scoped = scopedModels[0];
623
- const scopedThinkingLevel = scoped.thinkingLevel ?? defaultThinkingLevel ?? "off";
696
+ const scopedThinkingSelector = scoped.thinkingLevel ?? defaultThinkingSelector ?? "off";
624
697
  return {
625
698
  model: scoped.model,
626
- thinkingLevel: scopedThinkingLevel,
699
+ thinkingLevel: scopedThinkingSelector,
627
700
  fallbackMessage: undefined,
628
701
  };
629
702
  }
@@ -633,8 +706,8 @@ export async function findInitialModel(options: {
633
706
  const found = modelRegistry.find(defaultProvider, defaultModelId);
634
707
  if (found) {
635
708
  model = found;
636
- if (defaultThinkingLevel) {
637
- thinkingLevel = defaultThinkingLevel;
709
+ if (defaultThinkingSelector) {
710
+ thinkingLevel = defaultThinkingSelector;
638
711
  }
639
712
  return { model, thinkingLevel, fallbackMessage: undefined };
640
713
  }
@@ -1,4 +1,6 @@
1
- /**
1
+ import { getAvailableThinkingLevels } from "@oh-my-pi/pi-ai";
2
+
3
+ /** Unified settings schema - single source of truth for all settings.
2
4
  * Unified settings schema - single source of truth for all settings.
3
5
  *
4
6
  * Each setting is defined once here with:
@@ -190,7 +192,7 @@ export const SETTINGS_SCHEMA = {
190
192
  },
191
193
  defaultThinkingLevel: {
192
194
  type: "enum",
193
- values: ["off", "minimal", "low", "medium", "high", "xhigh"] as const,
195
+ values: getAvailableThinkingLevels(),
194
196
  default: "high",
195
197
  ui: {
196
198
  tab: "agent",
@@ -283,6 +285,16 @@ export const SETTINGS_SCHEMA = {
283
285
  description: "Include line hashes in read output for hashline edit mode (LINE#ID:content)",
284
286
  },
285
287
  },
288
+ "read.defaultLimit": {
289
+ type: "number",
290
+ default: 300,
291
+ ui: {
292
+ tab: "tools",
293
+ label: "Read default limit",
294
+ description: "Default number of lines returned when agent calls read without a limit",
295
+ submenu: true,
296
+ },
297
+ },
286
298
  showHardwareCursor: {
287
299
  type: "boolean",
288
300
  default: true, // will be computed based on platform if undefined
@@ -318,21 +318,6 @@ export class Settings {
318
318
  return result as unknown as GroupTypeMap[G];
319
319
  }
320
320
 
321
- /**
322
- * Get edit model variants (typed accessor for complex nested config).
323
- */
324
- getEditModelVariants(): Record<string, EditMode | null> {
325
- const variants = (this.#merged.edit as { modelVariants?: Record<string, string> })?.modelVariants ?? {};
326
- const result: Record<string, EditMode | null> = {};
327
- for (const pattern in variants) {
328
- const value = normalizeEditMode(variants[pattern]);
329
- if (value) {
330
- result[pattern] = value;
331
- }
332
- }
333
- return result;
334
- }
335
-
336
321
  /**
337
322
  * Get the edit variant for a specific model.
338
323
  * Returns "patch", "replace", "hashline", or null (use global default).
@@ -341,9 +326,8 @@ export class Settings {
341
326
  if (!model) return null;
342
327
  const variants = (this.#merged.edit as { modelVariants?: Record<string, string> })?.modelVariants;
343
328
  if (!variants) return null;
344
- const modelLower = model.toLowerCase();
345
329
  for (const pattern in variants) {
346
- if (modelLower.includes(pattern)) {
330
+ if (model.includes(pattern)) {
347
331
  const value = normalizeEditMode(variants[pattern]);
348
332
  if (value) {
349
333
  return value;
@@ -1,6 +1,7 @@
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-agent-core";
3
+ import type { ThinkingLevel } from "@oh-my-pi/pi-ai";
4
+ import { parseThinkingLevel } from "@oh-my-pi/pi-ai";
4
5
  import { FileType, glob } from "@oh-my-pi/pi-natives";
5
6
  import { CONFIG_DIR_NAME, tryParseJson } from "@oh-my-pi/pi-utils";
6
7
  import { readFile } from "../capability/fs";
@@ -9,8 +10,6 @@ import type { Skill, SkillFrontmatter } from "../capability/skill";
9
10
  import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
10
11
  import { parseFrontmatter } from "../utils/frontmatter";
11
12
 
12
- const VALID_THINKING_LEVELS: readonly string[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
13
-
14
13
  /**
15
14
  * Standard paths for each config source.
16
15
  */
@@ -100,18 +99,6 @@ export function createSourceMeta(provider: string, filePath: string, level: "use
100
99
  };
101
100
  }
102
101
 
103
- /**
104
- * Parse thinking level from frontmatter.
105
- * Supports keys: thinkingLevel, thinking-level, thinking
106
- */
107
- export function parseThinkingLevel(frontmatter: Record<string, unknown>): ThinkingLevel | undefined {
108
- const raw = frontmatter.thinkingLevel ?? frontmatter["thinking-level"] ?? frontmatter.thinking;
109
- if (typeof raw === "string" && VALID_THINKING_LEVELS.includes(raw)) {
110
- return raw as ThinkingLevel;
111
- }
112
- return undefined;
113
- }
114
-
115
102
  export function parseBoolean(value: unknown): boolean | undefined {
116
103
  if (typeof value === "boolean") return value;
117
104
  if (typeof value === "string") {
@@ -247,10 +234,16 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAg
247
234
  }
248
235
 
249
236
  const output = frontmatter.output !== undefined ? frontmatter.output : undefined;
237
+ const rawThinkingLevel =
238
+ typeof frontmatter.thinkingLevel === "string"
239
+ ? frontmatter.thinkingLevel
240
+ : typeof frontmatter.thinking === "string"
241
+ ? frontmatter.thinking
242
+ : undefined;
243
+
244
+ const thinkingLevel = parseThinkingLevel(rawThinkingLevel);
250
245
  const model = parseModelList(frontmatter.model);
251
- const thinkingLevel = parseThinkingLevel(frontmatter);
252
246
  const blocking = parseBoolean(frontmatter.blocking);
253
-
254
247
  return { name, description, tools, spawns, model, output, thinkingLevel, blocking };
255
248
  }
256
249