@oh-my-pi/pi-coding-agent 8.4.2 → 8.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [8.4.5] - 2026-01-26
6
+
7
+ ### Added
8
+ - Model usage tracking to record and retrieve most recently used models
9
+ - Model sorting in selector based on usage history
10
+
11
+ ### Changed
12
+ - Renamed `head_limit` parameter to `limit` in grep and find tools for consistency
13
+ - Added `context` as an alias for the `c` context parameter in grep tool
14
+ - Made hidden files inclusion configurable in find tool via `hidden` parameter (defaults to true)
15
+ - Added support for reading ignore patterns from .gitignore and .ignore files in find tool
16
+
17
+ ### Fixed
18
+ - Respected .gitignore rules when filtering find tool results by glob pattern
5
19
  ## [8.4.2] - 2026-01-25
6
20
 
7
21
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "8.4.2",
3
+ "version": "8.4.5",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "ompConfig": {
@@ -50,6 +50,14 @@
50
50
  "types": "./src/session/*.ts",
51
51
  "import": "./src/session/*.ts"
52
52
  },
53
+ "./internal-urls": {
54
+ "types": "./src/internal-urls/index.ts",
55
+ "import": "./src/internal-urls/index.ts"
56
+ },
57
+ "./internal-urls/*": {
58
+ "types": "./src/internal-urls/*.ts",
59
+ "import": "./src/internal-urls/*.ts"
60
+ },
53
61
  "./vendor/photon/*": {
54
62
  "types": "./src/vendor/photon/*",
55
63
  "import": "./src/vendor/photon/*"
@@ -75,11 +83,11 @@
75
83
  "test": "bun test"
76
84
  },
77
85
  "dependencies": {
78
- "@oh-my-pi/omp-stats": "8.4.2",
79
- "@oh-my-pi/pi-agent-core": "8.4.2",
80
- "@oh-my-pi/pi-ai": "8.4.2",
81
- "@oh-my-pi/pi-tui": "8.4.2",
82
- "@oh-my-pi/pi-utils": "8.4.2",
86
+ "@oh-my-pi/omp-stats": "8.4.5",
87
+ "@oh-my-pi/pi-agent-core": "8.4.5",
88
+ "@oh-my-pi/pi-ai": "8.4.5",
89
+ "@oh-my-pi/pi-tui": "8.4.5",
90
+ "@oh-my-pi/pi-utils": "8.4.5",
83
91
  "@openai/agents": "^0.4.3",
84
92
  "@sinclair/typebox": "^0.34.46",
85
93
  "ajv": "^8.17.1",
package/src/cursor.ts CHANGED
@@ -170,7 +170,7 @@ export class CursorExecHandlers implements ICursorExecHandlers {
170
170
  context: args.context ?? args.contextBefore ?? args.contextAfter ?? undefined,
171
171
  ignore_case: args.caseInsensitive || undefined,
172
172
  type: args.type || undefined,
173
- head_limit: args.headLimit ?? undefined,
173
+ limit: args.headLimit ?? undefined,
174
174
  multiline: args.multiline || undefined,
175
175
  });
176
176
  return toolResultMessage;
@@ -75,7 +75,6 @@ export class ModelSelectorComponent extends Container {
75
75
  private allModels: ModelItem[] = [];
76
76
  private filteredModels: ModelItem[] = [];
77
77
  private selectedIndex: number = 0;
78
- private currentModel?: Model<any>;
79
78
  private defaultModel?: Model<any>;
80
79
  private smolModel?: Model<any>;
81
80
  private slowModel?: Model<any>;
@@ -98,7 +97,7 @@ export class ModelSelectorComponent extends Container {
98
97
 
99
98
  constructor(
100
99
  tui: TUI,
101
- currentModel: Model<any> | undefined,
100
+ _currentModel: Model<any> | undefined,
102
101
  settingsManager: SettingsManager,
103
102
  modelRegistry: ModelRegistry,
104
103
  scopedModels: ReadonlyArray<ScopedModelItem>,
@@ -109,7 +108,6 @@ export class ModelSelectorComponent extends Container {
109
108
  super();
110
109
 
111
110
  this.tui = tui;
112
- this.currentModel = currentModel;
113
111
  this.settingsManager = settingsManager;
114
112
  this.modelRegistry = modelRegistry;
115
113
  this.scopedModels = scopedModels;
@@ -213,6 +211,44 @@ export class ModelSelectorComponent extends Container {
213
211
  }
214
212
  }
215
213
 
214
+ private sortModels(models: ModelItem[]): void {
215
+ // Sort: tagged models (default/smol/slow) first, then MRU, then alphabetical
216
+ const mruOrder = this.settingsManager.getStorage()?.getModelUsageOrder() ?? [];
217
+ const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
218
+
219
+ models.sort((a, b) => {
220
+ const aKey = `${a.provider}/${a.id}`;
221
+ const bKey = `${b.provider}/${b.id}`;
222
+
223
+ // Tagged models first: default (0), smol (1), slow (2), untagged (3)
224
+ const aTag = modelsAreEqual(this.defaultModel, a.model)
225
+ ? 0
226
+ : modelsAreEqual(this.smolModel, a.model)
227
+ ? 1
228
+ : modelsAreEqual(this.slowModel, a.model)
229
+ ? 2
230
+ : 3;
231
+ const bTag = modelsAreEqual(this.defaultModel, b.model)
232
+ ? 0
233
+ : modelsAreEqual(this.smolModel, b.model)
234
+ ? 1
235
+ : modelsAreEqual(this.slowModel, b.model)
236
+ ? 2
237
+ : 3;
238
+ if (aTag !== bTag) return aTag - bTag;
239
+
240
+ // Then MRU order (models in mruIndex come before those not in it)
241
+ const aMru = mruIndex.get(aKey) ?? Number.MAX_SAFE_INTEGER;
242
+ const bMru = mruIndex.get(bKey) ?? Number.MAX_SAFE_INTEGER;
243
+ if (aMru !== bMru) return aMru - bMru;
244
+
245
+ // Finally alphabetical by provider, then id
246
+ const providerCmp = a.provider.localeCompare(b.provider);
247
+ if (providerCmp !== 0) return providerCmp;
248
+ return a.id.localeCompare(b.id);
249
+ });
250
+ }
251
+
216
252
  private async loadModels(): Promise<void> {
217
253
  let models: ModelItem[];
218
254
 
@@ -249,16 +285,7 @@ export class ModelSelectorComponent extends Container {
249
285
  }
250
286
  }
251
287
 
252
- // Sort: current model first, then by provider, then by id
253
- models.sort((a, b) => {
254
- const aIsCurrent = modelsAreEqual(this.currentModel, a.model);
255
- const bIsCurrent = modelsAreEqual(this.currentModel, b.model);
256
- if (aIsCurrent && !bIsCurrent) return -1;
257
- if (!aIsCurrent && bIsCurrent) return 1;
258
- const providerCmp = a.provider.localeCompare(b.provider);
259
- if (providerCmp !== 0) return providerCmp;
260
- return a.id.localeCompare(b.id);
261
- });
288
+ this.sortModels(models);
262
289
 
263
290
  this.allModels = models;
264
291
  this.filteredModels = models;
@@ -315,7 +342,9 @@ export class ModelSelectorComponent extends Container {
315
342
  this.updateTabBar();
316
343
  baseModels = this.allModels;
317
344
  }
318
- this.filteredModels = fuzzyFilter(baseModels, query, ({ id, provider }) => `${id} ${provider}`);
345
+ const fuzzyMatches = fuzzyFilter(baseModels, query, ({ id, provider }) => `${id} ${provider}`);
346
+ this.sortModels(fuzzyMatches);
347
+ this.filteredModels = fuzzyMatches;
319
348
  } else {
320
349
  this.filteredModels = baseModels;
321
350
  }
@@ -558,9 +558,7 @@ export class ToolExecutionComponent extends Container {
558
558
  const context: Record<string, unknown> = {};
559
559
  const normalizeTimeoutSeconds = (value: unknown, maxSeconds: number): number | undefined => {
560
560
  if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
561
- let timeoutSec = value > 1000 ? value / 1000 : value;
562
- timeoutSec = Math.max(1, Math.min(maxSeconds, timeoutSec));
563
- return timeoutSec;
561
+ return Math.max(1, Math.min(maxSeconds, value));
564
562
  };
565
563
 
566
564
  if (this.toolName === "bash" && this.result) {
@@ -5,7 +5,6 @@
5
5
  import * as path from "node:path";
6
6
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
7
7
  import type { AssistantMessage, ImageContent, Message, Model, UsageReport } from "@oh-my-pi/pi-ai";
8
- import { resolvePlanUrlToPath } from "@oh-my-pi/pi-coding-agent/internal-urls";
9
8
  import type { Component, Loader, SlashCommand } from "@oh-my-pi/pi-tui";
10
9
  import {
11
10
  CombinedAutocompleteProvider,
@@ -24,6 +23,7 @@ import type { SettingsManager } from "../config/settings-manager";
24
23
  import type { ExtensionUIContext } from "../extensibility/extensions";
25
24
  import type { CompactOptions } from "../extensibility/extensions/types";
26
25
  import { loadSlashCommands } from "../extensibility/slash-commands";
26
+ import { resolvePlanUrlToPath } from "../internal-urls";
27
27
  import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
28
28
  import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
29
29
  import { HistoryStorage } from "../session/history-storage";
@@ -136,7 +136,7 @@ export class InteractiveMode implements InteractiveModeContext {
136
136
  private planModeHasEntered = false;
137
137
  public readonly lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined =
138
138
  undefined;
139
- public mcpManager?: import("@oh-my-pi/pi-coding-agent/mcp").MCPManager;
139
+ public mcpManager?: import("../mcp").MCPManager;
140
140
  private readonly toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
141
141
 
142
142
  private readonly commandController: CommandController;
@@ -152,7 +152,7 @@ export class InteractiveMode implements InteractiveModeContext {
152
152
  changelogMarkdown: string | undefined = undefined,
153
153
  setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void = () => {},
154
154
  lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined = undefined,
155
- mcpManager?: import("@oh-my-pi/pi-coding-agent/mcp").MCPManager,
155
+ mcpManager?: import("../mcp").MCPManager,
156
156
  ) {
157
157
  this.session = session;
158
158
  this.sessionManager = session.sessionManager;
@@ -19,6 +19,10 @@ Create your plan at `{{planFilePath}}`.
19
19
 
20
20
  The plan file is the ONLY file you may write or edit.
21
21
 
22
+ <important>
23
+ Plan execution runs in a fresh context (session cleared). Make the plan file self-contained: include any requirements, decisions, key findings, and remaining todos needed to continue without prior session history.
24
+ </important>
25
+
22
26
  {{#if reentry}}
23
27
  ## Re-entry
24
28
 
@@ -38,6 +38,12 @@ In plan mode:
38
38
  Requires user approval to enter. Once approved, you enter read-only exploration mode with restricted tool access.
39
39
  </output>
40
40
 
41
+ <parameters>
42
+ Optional parameters:
43
+ - `parallel`: Explore independent threads in parallel before synthesizing.
44
+ - `iterative`: One thread at a time with checkpoints between steps.
45
+ </parameters>
46
+
41
47
  <example name="auth">
42
48
  User: "Add user authentication to the app"
43
49
  → Use plan mode: architectural decisions (session vs JWT, where to store tokens, middleware structure)
@@ -3,12 +3,13 @@
3
3
  Fast file pattern matching that works with any codebase size.
4
4
 
5
5
  <instruction>
6
- - Supports glob patterns like "**/*.js" or "src/**/*.ts"
6
+ - Supports glob patterns like `**/*.js` or `src/**/*.ts`
7
+ - Includes hidden files by default (use `hidden: false` to exclude)
7
8
  - Speculatively perform multiple searches in parallel when potentially useful
8
9
  </instruction>
9
10
 
10
11
  <output>
11
- Matching file paths sorted by modification time (most recent first). Results truncated at 1000 entries or 50KB.
12
+ Matching file paths sorted by modification time (most recent first). Results truncated at 1000 entries or 50KB (configurable via `limit`).
12
13
  </output>
13
14
 
14
15
  <avoid>
@@ -16,7 +16,7 @@ Results depend on `output_mode`:
16
16
  - `count`: Match counts per file
17
17
 
18
18
  In `content` mode, truncated at 100 matches by default (configurable via `limit`).
19
- For `files_with_matches` and `count` modes, use `head_limit` to truncate results.
19
+ For `files_with_matches` and `count` modes, use `limit` to truncate results.
20
20
  </output>
21
21
 
22
22
  <critical>
@@ -27,7 +27,6 @@ import type {
27
27
  UsageReport,
28
28
  } from "@oh-my-pi/pi-ai";
29
29
  import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
30
- import { resolvePlanUrlToPath } from "@oh-my-pi/pi-coding-agent/internal-urls";
31
30
  import { abortableSleep, isEnoent, logger } from "@oh-my-pi/pi-utils";
32
31
  import { YAML } from "bun";
33
32
  import type { Rule } from "../capability/rule";
@@ -61,6 +60,7 @@ import type { CompactOptions, ContextUsage } from "../extensibility/extensions/t
61
60
  import type { HookCommandContext } from "../extensibility/hooks/types";
62
61
  import type { Skill, SkillWarning } from "../extensibility/skills";
63
62
  import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
63
+ import { resolvePlanUrlToPath } from "../internal-urls";
64
64
  import { executePython as executePythonCommand, type PythonResult } from "../ipy/executor";
65
65
  import { theme } from "../modes/theme/theme";
66
66
  import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../patch";
@@ -1676,6 +1676,7 @@ export class AgentSession {
1676
1676
  this.agent.setModel(model);
1677
1677
  this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, role);
1678
1678
  this.settingsManager.setModelRole(role, `${model.provider}/${model.id}`);
1679
+ this.settingsManager.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
1679
1680
 
1680
1681
  // Re-clamp thinking level for new model's capabilities
1681
1682
  this.setThinkingLevel(this.thinkingLevel);
@@ -1694,6 +1695,7 @@ export class AgentSession {
1694
1695
 
1695
1696
  this.agent.setModel(model);
1696
1697
  this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, "temporary");
1698
+ this.settingsManager.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
1697
1699
 
1698
1700
  // Re-clamp thinking level for new model's capabilities
1699
1701
  this.setThinkingLevel(this.thinkingLevel);
@@ -1790,6 +1792,7 @@ export class AgentSession {
1790
1792
  this.agent.setModel(next.model);
1791
1793
  this.sessionManager.appendModelChange(`${next.model.provider}/${next.model.id}`);
1792
1794
  this.settingsManager.setModelRole("default", `${next.model.provider}/${next.model.id}`);
1795
+ this.settingsManager.getStorage()?.recordModelUsage(`${next.model.provider}/${next.model.id}`);
1793
1796
 
1794
1797
  // Apply thinking level (setThinkingLevel clamps to model capabilities)
1795
1798
  this.setThinkingLevel(next.thinkingLevel);
@@ -1817,6 +1820,7 @@ export class AgentSession {
1817
1820
  this.agent.setModel(nextModel);
1818
1821
  this.sessionManager.appendModelChange(`${nextModel.provider}/${nextModel.id}`);
1819
1822
  this.settingsManager.setModelRole("default", `${nextModel.provider}/${nextModel.id}`);
1823
+ this.settingsManager.getStorage()?.recordModelUsage(`${nextModel.provider}/${nextModel.id}`);
1820
1824
 
1821
1825
  // Re-clamp thinking level for new model's capabilities
1822
1826
  this.setThinkingLevel(this.thinkingLevel);
@@ -23,6 +23,12 @@ type AuthRow = {
23
23
  data: string;
24
24
  };
25
25
 
26
+ /** Row shape for model_usage table queries */
27
+ type ModelUsageRow = {
28
+ model_key: string;
29
+ last_used_at: number;
30
+ };
31
+
26
32
  /**
27
33
  * Auth credential with database row ID for updates/deletes.
28
34
  * Wraps AuthCredential with storage metadata.
@@ -34,7 +40,7 @@ export interface StoredAuthCredential {
34
40
  }
35
41
 
36
42
  /** Bump when schema changes require migration */
37
- const SCHEMA_VERSION = 2;
43
+ const SCHEMA_VERSION = 3;
38
44
 
39
45
  /**
40
46
  * Type guard for plain objects.
@@ -126,6 +132,9 @@ export class AgentStorage {
126
132
  private deleteAuthStmt: Statement;
127
133
  private deleteAuthByProviderStmt: Statement;
128
134
  private countAuthStmt: Statement;
135
+ private upsertModelUsageStmt: Statement;
136
+ private listModelUsageStmt: Statement;
137
+ private modelUsageCache: string[] | null = null;
129
138
 
130
139
  private constructor(dbPath: string) {
131
140
  this.ensureDir(dbPath);
@@ -157,6 +166,13 @@ export class AgentStorage {
157
166
  this.deleteAuthStmt = this.db.prepare("DELETE FROM auth_credentials WHERE id = ?");
158
167
  this.deleteAuthByProviderStmt = this.db.prepare("DELETE FROM auth_credentials WHERE provider = ?");
159
168
  this.countAuthStmt = this.db.prepare("SELECT COUNT(*) as count FROM auth_credentials");
169
+
170
+ this.upsertModelUsageStmt = this.db.prepare(
171
+ "INSERT INTO model_usage (model_key, last_used_at) VALUES (?, unixepoch()) ON CONFLICT(model_key) DO UPDATE SET last_used_at = unixepoch()",
172
+ );
173
+ this.listModelUsageStmt = this.db.prepare(
174
+ "SELECT model_key, last_used_at FROM model_usage ORDER BY last_used_at DESC",
175
+ );
160
176
  }
161
177
 
162
178
  /**
@@ -186,6 +202,11 @@ CREATE TABLE IF NOT EXISTS cache (
186
202
  );
187
203
  CREATE INDEX IF NOT EXISTS idx_cache_expires ON cache(expires_at);
188
204
 
205
+ CREATE TABLE IF NOT EXISTS model_usage (
206
+ model_key TEXT PRIMARY KEY,
207
+ last_used_at INTEGER NOT NULL DEFAULT (unixepoch())
208
+ );
209
+
189
210
  CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY);
190
211
  `);
191
212
 
@@ -357,6 +378,38 @@ CREATE TABLE settings (
357
378
  }
358
379
  }
359
380
 
381
+ /**
382
+ * Records model usage, updating the last-used timestamp.
383
+ * @param modelKey - Model key in "provider/modelId" format
384
+ */
385
+ recordModelUsage(modelKey: string): void {
386
+ try {
387
+ this.upsertModelUsageStmt.run(modelKey);
388
+ this.modelUsageCache = null;
389
+ } catch (error) {
390
+ logger.warn("AgentStorage failed to record model usage", { modelKey, error: String(error) });
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Gets model keys ordered by most recently used.
396
+ * Results are cached until recordModelUsage is called.
397
+ * @returns Array of model keys ("provider/modelId") in MRU order
398
+ */
399
+ getModelUsageOrder(): string[] {
400
+ if (this.modelUsageCache) {
401
+ return this.modelUsageCache;
402
+ }
403
+ try {
404
+ const rows = this.listModelUsageStmt.all() as ModelUsageRow[];
405
+ this.modelUsageCache = rows.map(row => row.model_key);
406
+ return this.modelUsageCache;
407
+ } catch (error) {
408
+ logger.warn("AgentStorage failed to get model usage order", { error: String(error) });
409
+ return [];
410
+ }
411
+ }
412
+
360
413
  /**
361
414
  * Checks if any auth credentials exist in storage.
362
415
  * @returns True if at least one credential is stored
@@ -5,18 +5,18 @@
5
5
  */
6
6
  import path from "node:path";
7
7
  import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
- import type { PromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
9
- import type { Skill } from "@oh-my-pi/pi-coding-agent/extensibility/skills";
10
- import { getPreludeDocs } from "@oh-my-pi/pi-coding-agent/ipy/executor";
11
- import { checkPythonKernelAvailability } from "@oh-my-pi/pi-coding-agent/ipy/kernel";
12
- import type { ContextFileEntry, ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
13
8
  import type { ModelRegistry } from "../config/model-registry";
14
9
  import { formatModelString, parseModelPattern } from "../config/model-resolver";
10
+ import type { PromptTemplate } from "../config/prompt-templates";
11
+ import type { Skill } from "../extensibility/skills";
12
+ import { getPreludeDocs } from "../ipy/executor";
13
+ import { checkPythonKernelAvailability } from "../ipy/kernel";
15
14
  import { LspTool } from "../lsp";
16
15
  import type { LspParams } from "../lsp/types";
17
16
  import { callTool } from "../mcp/client";
18
17
  import type { MCPManager } from "../mcp/manager";
19
18
  import type { AuthStorage } from "../session/auth-storage";
19
+ import type { ContextFileEntry, ToolSession } from "../tools";
20
20
  import { PythonTool, type PythonToolParams } from "../tools/python";
21
21
  import type { EventBus } from "../utils/event-bus";
22
22
  import { subprocessToolRegistry } from "./subprocess-tool-registry";
@@ -80,7 +80,7 @@ export interface ExecutorOptions {
80
80
  authStorage?: AuthStorage;
81
81
  modelRegistry?: ModelRegistry;
82
82
  settingsManager?: {
83
- serialize: () => import("@oh-my-pi/pi-coding-agent/config/settings-manager").Settings;
83
+ serialize: () => import("../config/settings-manager").Settings;
84
84
  getPlansDirectory: (cwd?: string) => string;
85
85
  getPythonToolMode?: () => "ipy-only" | "bash-only" | "both";
86
86
  getPythonKernelMode?: () => "session" | "per-call";
package/src/task/index.ts CHANGED
@@ -17,14 +17,12 @@ import * as os from "node:os";
17
17
  import path from "node:path";
18
18
  import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
19
19
  import type { Usage } from "@oh-my-pi/pi-ai";
20
- import planModeSubagentPrompt from "@oh-my-pi/pi-coding-agent/prompts/system/plan-mode-subagent.md" with {
21
- type: "text",
22
- };
23
20
  import { $ } from "bun";
24
21
  import { nanoid } from "nanoid";
25
22
  import type { ToolSession } from "..";
26
23
  import { renderPromptTemplate } from "../config/prompt-templates";
27
24
  import type { Theme } from "../modes/theme/theme";
25
+ import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" with { type: "text" };
28
26
  import taskDescriptionTemplate from "../prompts/tools/task.md" with { type: "text" };
29
27
  import { formatDuration } from "../tools/render-utils";
30
28
  // Import review tools for side effects (registers subagent tool handlers)
@@ -1,11 +1,11 @@
1
1
  import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
- import type { PromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
3
- import type { Skill } from "@oh-my-pi/pi-coding-agent/extensibility/skills";
4
- import type { PreludeHelper } from "@oh-my-pi/pi-coding-agent/ipy/kernel";
5
- import type { ContextFileEntry } from "@oh-my-pi/pi-coding-agent/tools";
6
2
  import type { SerializedModelRegistry } from "../config/model-registry";
3
+ import type { PromptTemplate } from "../config/prompt-templates";
7
4
  import type { Settings } from "../config/settings-manager";
5
+ import type { Skill } from "../extensibility/skills";
6
+ import type { PreludeHelper } from "../ipy/kernel";
8
7
  import type { SerializedAuthStorage } from "../session/auth-storage";
8
+ import type { ContextFileEntry } from "../tools";
9
9
 
10
10
  /**
11
11
  * MCP tool metadata passed from parent to worker for proxy tool creation.
@@ -14,7 +14,6 @@
14
14
  */
15
15
  import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
16
16
  import type { Api, Model } from "@oh-my-pi/pi-ai";
17
- import { setPreludeDocsCache } from "@oh-my-pi/pi-coding-agent/ipy/executor";
18
17
  import { logger, postmortem, untilAborted } from "@oh-my-pi/pi-utils";
19
18
  import type { TSchema } from "@sinclair/typebox";
20
19
  import { ModelRegistry } from "../config/model-registry";
@@ -22,6 +21,7 @@ import { parseModelPattern, parseModelString } from "../config/model-resolver";
22
21
  import { renderPromptTemplate } from "../config/prompt-templates";
23
22
  import { SettingsManager } from "../config/settings-manager";
24
23
  import type { CustomTool } from "../extensibility/custom-tools/types";
24
+ import { setPreludeDocsCache } from "../ipy/executor";
25
25
  import { type LspToolDetails, lspSchema } from "../lsp/types";
26
26
  import lspDescription from "../prompts/tools/lsp.md" with { type: "text" };
27
27
  import { createAgentSession, discoverAuthStorage, discoverModels } from "../sdk";
package/src/tools/bash.ts CHANGED
@@ -85,10 +85,8 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
85
85
  throw new ToolError(`Working directory is not a directory: ${commandCwd}`);
86
86
  }
87
87
 
88
- // Auto-convert milliseconds to seconds if value > 1000 (16+ min is unreasonable)
89
- let timeoutSec = rawTimeout > 1000 ? rawTimeout / 1000 : rawTimeout;
90
88
  // Clamp to reasonable range: 1s - 3600s (1 hour)
91
- timeoutSec = Math.max(1, Math.min(3600, timeoutSec));
89
+ const timeoutSec = Math.max(1, Math.min(3600, rawTimeout));
92
90
  const timeoutMs = timeoutSec * 1000;
93
91
 
94
92
  // Track output for streaming updates (tail only)
@@ -1,15 +1,20 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
3
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
4
- import { resolvePlanUrlToPath } from "@oh-my-pi/pi-coding-agent/internal-urls";
5
- import enterPlanModeDescription from "@oh-my-pi/pi-coding-agent/prompts/tools/enter-plan-mode.md" with { type: "text" };
6
- import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
7
- import { ToolError } from "@oh-my-pi/pi-coding-agent/tools/tool-errors";
3
+ import { StringEnum } from "@oh-my-pi/pi-ai";
8
4
  import { isEnoent } from "@oh-my-pi/pi-utils";
9
5
  import { Type } from "@sinclair/typebox";
6
+ import { renderPromptTemplate } from "../config/prompt-templates";
7
+ import { resolvePlanUrlToPath } from "../internal-urls";
8
+ import enterPlanModeDescription from "../prompts/tools/enter-plan-mode.md" with { type: "text" };
9
+ import type { ToolSession } from ".";
10
+ import { ToolError } from "./tool-errors";
10
11
 
11
12
  const enterPlanModeSchema = Type.Object({
12
- workflow: Type.Optional(Type.Union([Type.Literal("parallel"), Type.Literal("iterative")])),
13
+ workflow: Type.Optional(
14
+ StringEnum(["parallel", "iterative"], {
15
+ description: "Planning workflow to use",
16
+ }),
17
+ ),
13
18
  });
14
19
 
15
20
  export interface EnterPlanModeDetails {