@oh-my-pi/pi-coding-agent 4.2.1 → 4.2.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.
- package/CHANGELOG.md +20 -0
- package/docs/sdk.md +5 -5
- package/examples/sdk/10-settings.ts +2 -2
- package/package.json +5 -5
- package/src/capability/fs.ts +90 -0
- package/src/capability/index.ts +41 -227
- package/src/capability/types.ts +1 -11
- package/src/cli/args.ts +4 -0
- package/src/core/agent-session.ts +4 -4
- package/src/core/agent-storage.ts +50 -0
- package/src/core/auth-storage.ts +102 -3
- package/src/core/bash-executor.ts +1 -1
- package/src/core/custom-tools/loader.ts +2 -2
- package/src/core/extensions/loader.ts +2 -2
- package/src/core/extensions/types.ts +1 -1
- package/src/core/hooks/loader.ts +2 -2
- package/src/core/mcp/config.ts +2 -2
- package/src/core/model-registry.ts +46 -0
- package/src/core/sdk.ts +37 -29
- package/src/core/settings-manager.ts +152 -135
- package/src/core/skills.ts +72 -51
- package/src/core/slash-commands.ts +3 -3
- package/src/core/system-prompt.ts +10 -10
- package/src/core/tools/edit.ts +7 -4
- package/src/core/tools/index.test.ts +16 -0
- package/src/core/tools/index.ts +21 -8
- package/src/core/tools/lsp/index.ts +4 -1
- package/src/core/tools/ssh.ts +6 -6
- package/src/core/tools/task/commands.ts +3 -5
- package/src/core/tools/task/executor.ts +88 -3
- package/src/core/tools/task/index.ts +4 -0
- package/src/core/tools/task/model-resolver.ts +10 -7
- package/src/core/tools/task/worker-protocol.ts +48 -2
- package/src/core/tools/task/worker.ts +152 -7
- package/src/core/tools/write.ts +7 -4
- package/src/discovery/agents-md.ts +13 -19
- package/src/discovery/builtin.ts +367 -247
- package/src/discovery/claude.ts +181 -290
- package/src/discovery/cline.ts +30 -10
- package/src/discovery/codex.ts +185 -244
- package/src/discovery/cursor.ts +106 -121
- package/src/discovery/gemini.ts +72 -97
- package/src/discovery/github.ts +7 -10
- package/src/discovery/helpers.ts +94 -88
- package/src/discovery/index.ts +1 -2
- package/src/discovery/mcp-json.ts +15 -18
- package/src/discovery/ssh.ts +9 -17
- package/src/discovery/vscode.ts +10 -5
- package/src/discovery/windsurf.ts +52 -86
- package/src/main.ts +5 -1
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +24 -11
- package/src/modes/interactive/components/extensions/state-manager.ts +19 -15
- package/src/modes/interactive/controllers/selector-controller.ts +6 -2
- package/src/modes/interactive/interactive-mode.ts +19 -15
- package/src/prompts/agents/plan.md +107 -30
- package/src/utils/shell.ts +2 -2
- package/src/prompts/agents/planner.md +0 -112
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.2.2] - 2026-01-11
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added persistent cache storage for Codex usage data that survives application restarts
|
|
9
|
+
- Added `--no-lsp` to disable LSP tools, formatting, diagnostics, and warmup for a session
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Changed `SettingsManager.create()` to be async, requiring `await` when creating settings managers
|
|
14
|
+
- Changed `loadSettings()` to be async, requiring `await` when loading settings
|
|
15
|
+
- Changed `discoverSkills()` to be async, requiring `await` when discovering skills
|
|
16
|
+
- Changed `loadSlashCommands()` to be async, requiring `await` when loading slash commands
|
|
17
|
+
- Changed `buildSystemPrompt()` to be async, requiring `await` when building system prompts
|
|
18
|
+
- Changed `loadSkills()` to be async, requiring `await` when loading skills
|
|
19
|
+
- Changed `loadProjectContextFiles()` to be async, requiring `await` when loading context files
|
|
20
|
+
- Changed `getShellConfig()` to be async, requiring `await` when getting shell configuration
|
|
21
|
+
- Changed capability provider `load()` methods to be async-only, removing synchronous `loadSync` API
|
|
22
|
+
- Updated `plan` agent with enhanced structured planning process, parallel exploration via `explore` agent spawning, and improved output format with examples
|
|
23
|
+
- Removed `planner` agent command template, consolidating planning functionality into the `plan` agent
|
|
24
|
+
|
|
5
25
|
## [4.2.1] - 2026-01-11
|
|
6
26
|
### Added
|
|
7
27
|
|
package/docs/sdk.md
CHANGED
|
@@ -682,11 +682,11 @@ import { createAgentSession, SettingsManager, SessionManager } from "@oh-my-pi/p
|
|
|
682
682
|
|
|
683
683
|
// Default: loads from files (global + project merged)
|
|
684
684
|
const { session } = await createAgentSession({
|
|
685
|
-
settingsManager: SettingsManager.create(),
|
|
685
|
+
settingsManager: await SettingsManager.create(),
|
|
686
686
|
});
|
|
687
687
|
|
|
688
688
|
// With overrides
|
|
689
|
-
const settingsManager = SettingsManager.create();
|
|
689
|
+
const settingsManager = await SettingsManager.create();
|
|
690
690
|
settingsManager.applyOverrides({
|
|
691
691
|
compaction: { enabled: false },
|
|
692
692
|
retry: { enabled: true, maxRetries: 5 },
|
|
@@ -701,13 +701,13 @@ const { session } = await createAgentSession({
|
|
|
701
701
|
|
|
702
702
|
// Custom directories
|
|
703
703
|
const { session } = await createAgentSession({
|
|
704
|
-
settingsManager: SettingsManager.create("/custom/cwd", "/custom/agent"),
|
|
704
|
+
settingsManager: await SettingsManager.create("/custom/cwd", "/custom/agent"),
|
|
705
705
|
});
|
|
706
706
|
```
|
|
707
707
|
|
|
708
708
|
**Static factories:**
|
|
709
709
|
|
|
710
|
-
- `SettingsManager.create(cwd?, agentDir?)` - Load from files
|
|
710
|
+
- `SettingsManager.create(cwd?, agentDir?)` - Load from files (async)
|
|
711
711
|
- `SettingsManager.inMemory(settings?)` - No file I/O
|
|
712
712
|
|
|
713
713
|
**Project-specific settings:**
|
|
@@ -765,7 +765,7 @@ const contextFiles = discoverContextFiles(cwd, agentDir);
|
|
|
765
765
|
const commands = discoverSlashCommands(cwd, agentDir);
|
|
766
766
|
|
|
767
767
|
// Settings (global + project merged)
|
|
768
|
-
const settings = loadSettings(cwd, agentDir);
|
|
768
|
+
const settings = await loadSettings(cwd, agentDir);
|
|
769
769
|
|
|
770
770
|
// Build system prompt manually
|
|
771
771
|
const prompt = buildSystemPrompt({
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
import { createAgentSession, loadSettings, SessionManager, SettingsManager } from "@oh-my-pi/pi-coding-agent";
|
|
8
8
|
|
|
9
9
|
// Load current settings (merged global + project)
|
|
10
|
-
const settings = loadSettings();
|
|
10
|
+
const settings = await loadSettings();
|
|
11
11
|
console.log("Current settings:", JSON.stringify(settings, null, 2));
|
|
12
12
|
|
|
13
13
|
// Override specific settings
|
|
14
|
-
const settingsManager = SettingsManager.create();
|
|
14
|
+
const settingsManager = await SettingsManager.create();
|
|
15
15
|
settingsManager.applyOverrides({
|
|
16
16
|
compaction: { enabled: false },
|
|
17
17
|
retry: { enabled: true, maxRetries: 5, baseDelayMs: 1000 },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.2",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"prepublishOnly": "bun run generate-template && bun run clean && bun run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-ai": "4.2.
|
|
43
|
-
"@oh-my-pi/pi-agent-core": "4.2.
|
|
44
|
-
"@oh-my-pi/pi-git-tool": "4.2.
|
|
45
|
-
"@oh-my-pi/pi-tui": "4.2.
|
|
42
|
+
"@oh-my-pi/pi-ai": "4.2.2",
|
|
43
|
+
"@oh-my-pi/pi-agent-core": "4.2.2",
|
|
44
|
+
"@oh-my-pi/pi-git-tool": "4.2.2",
|
|
45
|
+
"@oh-my-pi/pi-tui": "4.2.2",
|
|
46
46
|
"@openai/agents": "^0.3.7",
|
|
47
47
|
"@sinclair/typebox": "^0.34.46",
|
|
48
48
|
"ajv": "^8.17.1",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Dirent } from "node:fs";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
const contentCache = new Map<string, string | null>();
|
|
6
|
+
const dirCache = new Map<string, Dirent[]>();
|
|
7
|
+
|
|
8
|
+
function resolvePath(path: string): string {
|
|
9
|
+
return resolve(path);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function readFile(path: string): Promise<string | null> {
|
|
13
|
+
const abs = resolvePath(path);
|
|
14
|
+
if (contentCache.has(abs)) {
|
|
15
|
+
return contentCache.get(abs) ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const content = await Bun.file(abs).text();
|
|
20
|
+
contentCache.set(abs, content);
|
|
21
|
+
return content;
|
|
22
|
+
} catch {
|
|
23
|
+
contentCache.set(abs, null);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function readDirEntries(path: string): Promise<Dirent[]> {
|
|
29
|
+
const abs = resolvePath(path);
|
|
30
|
+
if (dirCache.has(abs)) {
|
|
31
|
+
return dirCache.get(abs) ?? [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const entries = await readdir(abs, { withFileTypes: true });
|
|
36
|
+
dirCache.set(abs, entries);
|
|
37
|
+
return entries;
|
|
38
|
+
} catch {
|
|
39
|
+
dirCache.set(abs, []);
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function readDir(path: string): Promise<string[]> {
|
|
45
|
+
const entries = await readDirEntries(path);
|
|
46
|
+
return entries.map((entry) => entry.name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function walkUp(
|
|
50
|
+
startDir: string,
|
|
51
|
+
name: string,
|
|
52
|
+
opts: { file?: boolean; dir?: boolean } = {},
|
|
53
|
+
): Promise<string | null> {
|
|
54
|
+
const { file = true, dir = true } = opts;
|
|
55
|
+
let current = resolvePath(startDir);
|
|
56
|
+
|
|
57
|
+
while (true) {
|
|
58
|
+
const entries = await readDirEntries(current);
|
|
59
|
+
const entry = entries.find((e) => e.name === name);
|
|
60
|
+
if (entry) {
|
|
61
|
+
if (file && entry.isFile()) return join(current, name);
|
|
62
|
+
if (dir && entry.isDirectory()) return join(current, name);
|
|
63
|
+
}
|
|
64
|
+
const parent = dirname(current);
|
|
65
|
+
if (parent === current) return null;
|
|
66
|
+
current = parent;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function cacheStats(): { content: number; dir: number } {
|
|
71
|
+
return {
|
|
72
|
+
content: contentCache.size,
|
|
73
|
+
dir: dirCache.size,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function clearCache(): void {
|
|
78
|
+
contentCache.clear();
|
|
79
|
+
dirCache.clear();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function invalidate(path: string): void {
|
|
83
|
+
const abs = resolvePath(path);
|
|
84
|
+
contentCache.delete(abs);
|
|
85
|
+
dirCache.delete(abs);
|
|
86
|
+
const parent = dirname(abs);
|
|
87
|
+
if (parent !== abs) {
|
|
88
|
+
dirCache.delete(parent);
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/capability/index.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* - Loading items for a capability across all providers
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { type Dirent, readdirSync, readFileSync, statSync } from "node:fs";
|
|
11
10
|
import { homedir } from "node:os";
|
|
12
|
-
import {
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { clearCache as clearFsCache, cacheStats as fsCacheStats, invalidate as invalidateFs } from "./fs";
|
|
13
13
|
import type {
|
|
14
14
|
Capability,
|
|
15
15
|
CapabilityInfo,
|
|
@@ -40,87 +40,6 @@ const disabledProviders = new Set<string>();
|
|
|
40
40
|
/** Settings manager for persistence (if set) */
|
|
41
41
|
let settingsManager: { getDisabledProviders(): string[]; setDisabledProviders(ids: string[]): void } | null = null;
|
|
42
42
|
|
|
43
|
-
// =============================================================================
|
|
44
|
-
// Filesystem Cache
|
|
45
|
-
// =============================================================================
|
|
46
|
-
|
|
47
|
-
type StatResult = "file" | "dir" | null;
|
|
48
|
-
|
|
49
|
-
const statCache = new Map<string, StatResult>();
|
|
50
|
-
const contentCache = new Map<string, string | null>();
|
|
51
|
-
const dirCache = new Map<string, Dirent[]>();
|
|
52
|
-
|
|
53
|
-
function clearCache(): void {
|
|
54
|
-
statCache.clear();
|
|
55
|
-
contentCache.clear();
|
|
56
|
-
dirCache.clear();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function createFsHelpers(cwd: string): LoadContext["fs"] {
|
|
60
|
-
return {
|
|
61
|
-
exists(path: string): boolean {
|
|
62
|
-
const abs = resolve(cwd, path);
|
|
63
|
-
if (!statCache.has(abs)) {
|
|
64
|
-
try {
|
|
65
|
-
const stat = statSync(abs);
|
|
66
|
-
statCache.set(abs, stat.isDirectory() ? "dir" : stat.isFile() ? "file" : null);
|
|
67
|
-
} catch {
|
|
68
|
-
statCache.set(abs, null);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return statCache.get(abs) !== null;
|
|
72
|
-
},
|
|
73
|
-
|
|
74
|
-
isDir(path: string): boolean {
|
|
75
|
-
this.exists(path);
|
|
76
|
-
return statCache.get(resolve(cwd, path)) === "dir";
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
isFile(path: string): boolean {
|
|
80
|
-
this.exists(path);
|
|
81
|
-
return statCache.get(resolve(cwd, path)) === "file";
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
readFile(path: string): string | null {
|
|
85
|
-
const abs = resolve(cwd, path);
|
|
86
|
-
if (!contentCache.has(abs)) {
|
|
87
|
-
try {
|
|
88
|
-
contentCache.set(abs, readFileSync(abs, "utf-8"));
|
|
89
|
-
} catch {
|
|
90
|
-
contentCache.set(abs, null);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return contentCache.get(abs) ?? null;
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
readDir(path: string): string[] {
|
|
97
|
-
const abs = resolve(cwd, path);
|
|
98
|
-
if (!this.isDir(path)) return [];
|
|
99
|
-
if (!dirCache.has(abs)) {
|
|
100
|
-
try {
|
|
101
|
-
dirCache.set(abs, readdirSync(abs, { withFileTypes: true }));
|
|
102
|
-
} catch {
|
|
103
|
-
dirCache.set(abs, []);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return (dirCache.get(abs) ?? []).map((e) => e.name);
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
walkUp(name: string, opts: { file?: boolean; dir?: boolean } = {}): string | null {
|
|
110
|
-
const { file = true, dir = true } = opts;
|
|
111
|
-
let current = cwd;
|
|
112
|
-
while (true) {
|
|
113
|
-
const candidate = join(current, name);
|
|
114
|
-
if (file && this.isFile(candidate)) return candidate;
|
|
115
|
-
if (dir && this.isDir(candidate)) return candidate;
|
|
116
|
-
const parent = dirname(current);
|
|
117
|
-
if (parent === current) return null;
|
|
118
|
-
current = parent;
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
43
|
// =============================================================================
|
|
125
44
|
// Registration API
|
|
126
45
|
// =============================================================================
|
|
@@ -175,129 +94,55 @@ export function registerProvider<T>(capabilityId: string, provider: Provider<T>)
|
|
|
175
94
|
// =============================================================================
|
|
176
95
|
|
|
177
96
|
/**
|
|
178
|
-
*
|
|
97
|
+
* Async loading logic shared by loadCapability().
|
|
179
98
|
*/
|
|
180
|
-
function loadImpl<T>(
|
|
99
|
+
async function loadImpl<T>(
|
|
181
100
|
capability: Capability<T>,
|
|
182
101
|
providers: Provider<T>[],
|
|
183
102
|
ctx: LoadContext,
|
|
184
103
|
options: LoadOptions,
|
|
185
|
-
): CapabilityResult<T
|
|
104
|
+
): Promise<CapabilityResult<T>> {
|
|
186
105
|
const allItems: Array<T & { _source: SourceMeta; _shadowed?: boolean }> = [];
|
|
187
106
|
const allWarnings: string[] = [];
|
|
188
107
|
const contributingProviders: string[] = [];
|
|
189
108
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (result.warnings) {
|
|
201
|
-
allWarnings.push(...result.warnings.map((w) => `[${provider.displayName}] ${w}`));
|
|
109
|
+
const results = await Promise.all(
|
|
110
|
+
providers.map(async (provider) => {
|
|
111
|
+
try {
|
|
112
|
+
const result = await provider.load(ctx);
|
|
113
|
+
return { provider, result };
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return { provider, error };
|
|
202
116
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
allItems.push(itemWithSource as T & { _source: SourceMeta; _shadowed?: boolean });
|
|
212
|
-
} else {
|
|
213
|
-
allWarnings.push(`[${provider.displayName}] Item missing _source metadata, skipping`);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
} catch (err) {
|
|
218
|
-
if (err instanceof Error && err.message.includes("returned a Promise")) {
|
|
219
|
-
throw err;
|
|
220
|
-
}
|
|
221
|
-
allWarnings.push(`[${provider.displayName}] Failed to load: ${err}`);
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
for (const entry of results) {
|
|
121
|
+
const { provider } = entry;
|
|
122
|
+
if ("error" in entry) {
|
|
123
|
+
allWarnings.push(`[${provider.displayName}] Failed to load: ${entry.error}`);
|
|
124
|
+
continue;
|
|
222
125
|
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Deduplicate by key (first wins = highest priority)
|
|
226
|
-
const seen = new Map<string, number>();
|
|
227
|
-
const deduped: Array<T & { _source: SourceMeta }> = [];
|
|
228
126
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const key = capability.key(item);
|
|
127
|
+
const result = entry.result;
|
|
128
|
+
if (!result) continue;
|
|
232
129
|
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
} else if (!seen.has(key)) {
|
|
236
|
-
seen.set(key, i);
|
|
237
|
-
deduped.push(item);
|
|
238
|
-
} else {
|
|
239
|
-
item._shadowed = true;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Validate items (only non-shadowed items)
|
|
244
|
-
if (capability.validate && !options.includeInvalid) {
|
|
245
|
-
for (let i = deduped.length - 1; i >= 0; i--) {
|
|
246
|
-
const error = capability.validate(deduped[i]);
|
|
247
|
-
if (error) {
|
|
248
|
-
const source = deduped[i]._source;
|
|
249
|
-
allWarnings.push(
|
|
250
|
-
`[${source?.providerName ?? "unknown"}] Invalid item at ${source?.path ?? "unknown"}: ${error}`,
|
|
251
|
-
);
|
|
252
|
-
deduped.splice(i, 1);
|
|
253
|
-
}
|
|
130
|
+
if (result.warnings) {
|
|
131
|
+
allWarnings.push(...result.warnings.map((w) => `[${provider.displayName}] ${w}`));
|
|
254
132
|
}
|
|
255
|
-
}
|
|
256
133
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
all: allItems,
|
|
260
|
-
warnings: allWarnings,
|
|
261
|
-
providers: contributingProviders,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
134
|
+
if (result.items.length > 0) {
|
|
135
|
+
contributingProviders.push(provider.id);
|
|
264
136
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
options: LoadOptions,
|
|
273
|
-
): Promise<CapabilityResult<T>> {
|
|
274
|
-
const allItems: Array<T & { _source: SourceMeta; _shadowed?: boolean }> = [];
|
|
275
|
-
const allWarnings: string[] = [];
|
|
276
|
-
const contributingProviders: string[] = [];
|
|
277
|
-
|
|
278
|
-
for (const provider of providers) {
|
|
279
|
-
try {
|
|
280
|
-
const result = await provider.load(ctx);
|
|
281
|
-
|
|
282
|
-
if (result.warnings) {
|
|
283
|
-
allWarnings.push(...result.warnings.map((w) => `[${provider.displayName}] ${w}`));
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (result.items.length > 0) {
|
|
287
|
-
contributingProviders.push(provider.id);
|
|
288
|
-
|
|
289
|
-
for (const item of result.items) {
|
|
290
|
-
const itemWithSource = item as T & { _source: SourceMeta };
|
|
291
|
-
if (itemWithSource._source) {
|
|
292
|
-
itemWithSource._source.providerName = provider.displayName;
|
|
293
|
-
allItems.push(itemWithSource as T & { _source: SourceMeta; _shadowed?: boolean });
|
|
294
|
-
} else {
|
|
295
|
-
allWarnings.push(`[${provider.displayName}] Item missing _source metadata, skipping`);
|
|
296
|
-
}
|
|
137
|
+
for (const item of result.items) {
|
|
138
|
+
const itemWithSource = item as T & { _source: SourceMeta };
|
|
139
|
+
if (itemWithSource._source) {
|
|
140
|
+
itemWithSource._source.providerName = provider.displayName;
|
|
141
|
+
allItems.push(itemWithSource as T & { _source: SourceMeta; _shadowed?: boolean });
|
|
142
|
+
} else {
|
|
143
|
+
allWarnings.push(`[${provider.displayName}] Item missing _source metadata, skipping`);
|
|
297
144
|
}
|
|
298
145
|
}
|
|
299
|
-
} catch (err) {
|
|
300
|
-
allWarnings.push(`[${provider.displayName}] Failed to load: ${err}`);
|
|
301
146
|
}
|
|
302
147
|
}
|
|
303
148
|
|
|
@@ -362,7 +207,7 @@ function filterProviders<T>(capability: Capability<T>, options: LoadOptions): Pr
|
|
|
362
207
|
/**
|
|
363
208
|
* Load a capability by ID.
|
|
364
209
|
*/
|
|
365
|
-
export async function
|
|
210
|
+
export async function loadCapability<T>(capabilityId: string, options: LoadOptions = {}): Promise<CapabilityResult<T>> {
|
|
366
211
|
const capability = capabilities.get(capabilityId) as Capability<T> | undefined;
|
|
367
212
|
if (!capability) {
|
|
368
213
|
throw new Error(`Unknown capability: "${capabilityId}"`);
|
|
@@ -370,28 +215,10 @@ export async function load<T>(capabilityId: string, options: LoadOptions = {}):
|
|
|
370
215
|
|
|
371
216
|
const cwd = options.cwd ?? process.cwd();
|
|
372
217
|
const home = homedir();
|
|
373
|
-
const ctx: LoadContext = { cwd, home
|
|
218
|
+
const ctx: LoadContext = { cwd, home };
|
|
374
219
|
const providers = filterProviders(capability, options);
|
|
375
220
|
|
|
376
|
-
return
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Synchronous load (for capabilities where all providers are sync).
|
|
381
|
-
* Throws if any provider returns a Promise.
|
|
382
|
-
*/
|
|
383
|
-
export function loadSync<T>(capabilityId: string, options: LoadOptions = {}): CapabilityResult<T> {
|
|
384
|
-
const capability = capabilities.get(capabilityId) as Capability<T> | undefined;
|
|
385
|
-
if (!capability) {
|
|
386
|
-
throw new Error(`Unknown capability: "${capabilityId}"`);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const cwd = options.cwd ?? process.cwd();
|
|
390
|
-
const home = homedir();
|
|
391
|
-
const ctx: LoadContext = { cwd, home, fs: createFsHelpers(cwd) };
|
|
392
|
-
const providers = filterProviders(capability, options);
|
|
393
|
-
|
|
394
|
-
return loadImpl(capability, providers, ctx, options);
|
|
221
|
+
return await loadImpl(capability, providers, ctx, options);
|
|
395
222
|
}
|
|
396
223
|
|
|
397
224
|
// =============================================================================
|
|
@@ -567,36 +394,23 @@ export function getAllProvidersInfo(): ProviderInfo[] {
|
|
|
567
394
|
* Reset all caches. Call after chdir or filesystem changes.
|
|
568
395
|
*/
|
|
569
396
|
export function reset(): void {
|
|
570
|
-
|
|
397
|
+
clearFsCache();
|
|
571
398
|
}
|
|
572
399
|
|
|
573
400
|
/**
|
|
574
401
|
* Invalidate cache for a specific path.
|
|
575
402
|
* @param path - Absolute or relative path to invalidate
|
|
576
|
-
* @param cwd - Working directory for resolving relative paths (defaults to process.cwd())
|
|
577
403
|
*/
|
|
578
404
|
export function invalidate(path: string, cwd?: string): void {
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
contentCache.delete(abs);
|
|
582
|
-
dirCache.delete(abs);
|
|
583
|
-
// Also invalidate parent for directory listings
|
|
584
|
-
const parent = dirname(abs);
|
|
585
|
-
if (parent !== abs) {
|
|
586
|
-
statCache.delete(parent);
|
|
587
|
-
dirCache.delete(parent);
|
|
588
|
-
}
|
|
405
|
+
const resolved = cwd ? resolve(cwd, path) : path;
|
|
406
|
+
invalidateFs(resolved);
|
|
589
407
|
}
|
|
590
408
|
|
|
591
409
|
/**
|
|
592
410
|
* Get cache stats for diagnostics.
|
|
593
411
|
*/
|
|
594
|
-
export function cacheStats(): {
|
|
595
|
-
return
|
|
596
|
-
stat: statCache.size,
|
|
597
|
-
content: contentCache.size,
|
|
598
|
-
dir: dirCache.size,
|
|
599
|
-
};
|
|
412
|
+
export function cacheStats(): { content: number; dir: number } {
|
|
413
|
+
return fsCacheStats();
|
|
600
414
|
}
|
|
601
415
|
|
|
602
416
|
// =============================================================================
|
package/src/capability/types.ts
CHANGED
|
@@ -14,16 +14,6 @@ export interface LoadContext {
|
|
|
14
14
|
cwd: string;
|
|
15
15
|
/** User home directory */
|
|
16
16
|
home: string;
|
|
17
|
-
/** Filesystem helpers (cached) */
|
|
18
|
-
fs: {
|
|
19
|
-
exists(path: string): boolean;
|
|
20
|
-
isDir(path: string): boolean;
|
|
21
|
-
isFile(path: string): boolean;
|
|
22
|
-
readFile(path: string): string | null;
|
|
23
|
-
readDir(path: string): string[];
|
|
24
|
-
/** Walk up from cwd looking for a file/dir, returns first match */
|
|
25
|
-
walkUp(name: string, opts?: { file?: boolean; dir?: boolean }): string | null;
|
|
26
|
-
};
|
|
27
17
|
}
|
|
28
18
|
|
|
29
19
|
/**
|
|
@@ -61,7 +51,7 @@ export interface Provider<T> {
|
|
|
61
51
|
* Load items for this capability.
|
|
62
52
|
* Returns items in provider's preferred order (usually project before user).
|
|
63
53
|
*/
|
|
64
|
-
load(ctx: LoadContext):
|
|
54
|
+
load(ctx: LoadContext): Promise<LoadResult<T>>;
|
|
65
55
|
}
|
|
66
56
|
|
|
67
57
|
/**
|
package/src/cli/args.ts
CHANGED
|
@@ -31,6 +31,7 @@ export interface Args {
|
|
|
31
31
|
models?: string[];
|
|
32
32
|
tools?: string[];
|
|
33
33
|
noTools?: boolean;
|
|
34
|
+
noLsp?: boolean;
|
|
34
35
|
hooks?: string[];
|
|
35
36
|
extensions?: string[];
|
|
36
37
|
noExtensions?: boolean;
|
|
@@ -100,6 +101,8 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
|
|
|
100
101
|
result.models = args[++i].split(",").map((s) => s.trim());
|
|
101
102
|
} else if (arg === "--no-tools") {
|
|
102
103
|
result.noTools = true;
|
|
104
|
+
} else if (arg === "--no-lsp") {
|
|
105
|
+
result.noLsp = true;
|
|
103
106
|
} else if (arg === "--tools" && i + 1 < args.length) {
|
|
104
107
|
const toolNames = args[++i].split(",").map((s) => s.trim());
|
|
105
108
|
const validTools: string[] = [];
|
|
@@ -196,6 +199,7 @@ ${chalk.bold("Options:")}
|
|
|
196
199
|
--models <patterns> Comma-separated model patterns for Ctrl+P cycling
|
|
197
200
|
Supports globs (anthropic/*, *sonnet*) and fuzzy matching
|
|
198
201
|
--no-tools Disable all built-in tools
|
|
202
|
+
--no-lsp Disable LSP tools, formatting, and diagnostics
|
|
199
203
|
--tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)
|
|
200
204
|
Available: read, bash, edit, write, grep, find, ls
|
|
201
205
|
--thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
|
|
@@ -99,7 +99,7 @@ export interface AgentSessionConfig {
|
|
|
99
99
|
/** Tool registry for LSP and settings */
|
|
100
100
|
toolRegistry?: Map<string, AgentTool>;
|
|
101
101
|
/** System prompt builder that can consider tool availability */
|
|
102
|
-
rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => string
|
|
102
|
+
rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => Promise<string>;
|
|
103
103
|
/** TTSR manager for time-traveling stream rules */
|
|
104
104
|
ttsrManager?: TtsrManager;
|
|
105
105
|
}
|
|
@@ -249,7 +249,7 @@ export class AgentSession {
|
|
|
249
249
|
|
|
250
250
|
// Tool registry and prompt builder for extensions
|
|
251
251
|
private _toolRegistry: Map<string, AgentTool>;
|
|
252
|
-
private _rebuildSystemPrompt: ((toolNames: string[], tools: Map<string, AgentTool>) => string) | undefined;
|
|
252
|
+
private _rebuildSystemPrompt: ((toolNames: string[], tools: Map<string, AgentTool>) => Promise<string>) | undefined;
|
|
253
253
|
private _baseSystemPrompt: string;
|
|
254
254
|
|
|
255
255
|
// TTSR manager for time-traveling stream rules
|
|
@@ -628,7 +628,7 @@ export class AgentSession {
|
|
|
628
628
|
* Also rebuilds the system prompt to reflect the new tool set.
|
|
629
629
|
* Changes take effect on the next agent turn.
|
|
630
630
|
*/
|
|
631
|
-
setActiveToolsByName(toolNames: string[]): void {
|
|
631
|
+
async setActiveToolsByName(toolNames: string[]): Promise<void> {
|
|
632
632
|
const tools: AgentTool[] = [];
|
|
633
633
|
const validToolNames: string[] = [];
|
|
634
634
|
for (const name of toolNames) {
|
|
@@ -642,7 +642,7 @@ export class AgentSession {
|
|
|
642
642
|
|
|
643
643
|
// Rebuild base system prompt with new tool set
|
|
644
644
|
if (this._rebuildSystemPrompt) {
|
|
645
|
-
this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames, this._toolRegistry);
|
|
645
|
+
this._baseSystemPrompt = await this._rebuildSystemPrompt(validToolNames, this._toolRegistry);
|
|
646
646
|
this.agent.setSystemPrompt(this._baseSystemPrompt);
|
|
647
647
|
}
|
|
648
648
|
}
|