@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 +14 -0
- package/package.json +14 -6
- package/src/cursor.ts +1 -1
- package/src/modes/components/model-selector.ts +43 -14
- package/src/modes/components/tool-execution.ts +1 -3
- package/src/modes/interactive-mode.ts +3 -3
- package/src/prompts/system/plan-mode-active.md +4 -0
- package/src/prompts/tools/enter-plan-mode.md +6 -0
- package/src/prompts/tools/find.md +3 -2
- package/src/prompts/tools/grep.md +1 -1
- package/src/session/agent-session.ts +5 -1
- package/src/session/agent-storage.ts +54 -1
- package/src/task/executor.ts +6 -6
- package/src/task/index.ts +1 -3
- package/src/task/worker-protocol.ts +4 -4
- package/src/task/worker.ts +1 -1
- package/src/tools/bash.ts +1 -3
- package/src/tools/enter-plan-mode.ts +11 -6
- package/src/tools/find.ts +74 -150
- package/src/tools/grep.ts +215 -109
- package/src/tools/index.ts +5 -5
- package/src/tools/output-meta.ts +2 -2
- package/src/tools/plan-mode-guard.ts +1 -1
- package/src/tools/python.ts +1 -3
- package/src/tools/read.ts +30 -20
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.
|
|
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.
|
|
79
|
-
"@oh-my-pi/pi-agent-core": "8.4.
|
|
80
|
-
"@oh-my-pi/pi-ai": "8.4.
|
|
81
|
-
"@oh-my-pi/pi-tui": "8.4.
|
|
82
|
-
"@oh-my-pi/pi-utils": "8.4.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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
|
|
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 `
|
|
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 =
|
|
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
|
package/src/task/executor.ts
CHANGED
|
@@ -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("
|
|
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.
|
package/src/task/worker.ts
CHANGED
|
@@ -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,
|
|
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 {
|
|
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(
|
|
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 {
|