@oh-my-pi/pi-coding-agent 4.2.1 → 4.2.3

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 (58) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/docs/sdk.md +5 -5
  3. package/examples/sdk/10-settings.ts +2 -2
  4. package/package.json +5 -5
  5. package/src/capability/fs.ts +90 -0
  6. package/src/capability/index.ts +41 -227
  7. package/src/capability/types.ts +1 -11
  8. package/src/cli/args.ts +4 -0
  9. package/src/core/agent-session.ts +4 -4
  10. package/src/core/agent-storage.ts +50 -0
  11. package/src/core/auth-storage.ts +112 -4
  12. package/src/core/bash-executor.ts +1 -1
  13. package/src/core/custom-tools/loader.ts +2 -2
  14. package/src/core/extensions/loader.ts +2 -2
  15. package/src/core/extensions/types.ts +1 -1
  16. package/src/core/hooks/loader.ts +2 -2
  17. package/src/core/mcp/config.ts +2 -2
  18. package/src/core/model-registry.ts +46 -0
  19. package/src/core/sdk.ts +37 -29
  20. package/src/core/settings-manager.ts +152 -135
  21. package/src/core/skills.ts +72 -51
  22. package/src/core/slash-commands.ts +3 -3
  23. package/src/core/system-prompt.ts +10 -10
  24. package/src/core/tools/edit.ts +7 -4
  25. package/src/core/tools/find.ts +2 -2
  26. package/src/core/tools/index.test.ts +16 -0
  27. package/src/core/tools/index.ts +21 -8
  28. package/src/core/tools/lsp/index.ts +4 -1
  29. package/src/core/tools/ssh.ts +6 -6
  30. package/src/core/tools/task/commands.ts +3 -5
  31. package/src/core/tools/task/executor.ts +88 -3
  32. package/src/core/tools/task/index.ts +4 -0
  33. package/src/core/tools/task/model-resolver.ts +10 -7
  34. package/src/core/tools/task/worker-protocol.ts +48 -2
  35. package/src/core/tools/task/worker.ts +152 -7
  36. package/src/core/tools/write.ts +7 -4
  37. package/src/discovery/agents-md.ts +13 -19
  38. package/src/discovery/builtin.ts +367 -247
  39. package/src/discovery/claude.ts +181 -290
  40. package/src/discovery/cline.ts +30 -10
  41. package/src/discovery/codex.ts +185 -244
  42. package/src/discovery/cursor.ts +106 -121
  43. package/src/discovery/gemini.ts +72 -97
  44. package/src/discovery/github.ts +7 -10
  45. package/src/discovery/helpers.ts +94 -88
  46. package/src/discovery/index.ts +1 -2
  47. package/src/discovery/mcp-json.ts +15 -18
  48. package/src/discovery/ssh.ts +9 -17
  49. package/src/discovery/vscode.ts +10 -5
  50. package/src/discovery/windsurf.ts +52 -86
  51. package/src/main.ts +5 -1
  52. package/src/modes/interactive/components/extensions/extension-dashboard.ts +24 -11
  53. package/src/modes/interactive/components/extensions/state-manager.ts +19 -15
  54. package/src/modes/interactive/controllers/selector-controller.ts +6 -2
  55. package/src/modes/interactive/interactive-mode.ts +19 -15
  56. package/src/prompts/agents/plan.md +107 -30
  57. package/src/utils/shell.ts +2 -2
  58. package/src/prompts/agents/planner.md +0 -112
package/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [4.2.3] - 2026-01-11
6
+
7
+ ### Changed
8
+
9
+ - Changed default for `hidden` option in find tool from `false` to `true`, now including hidden files by default
10
+
11
+ ### Fixed
12
+
13
+ - Fixed serialized auth storage initialization so OAuth refreshes in subagents don't crash
14
+
15
+ ## [4.2.2] - 2026-01-11
16
+ ### Added
17
+
18
+ - Added persistent cache storage for Codex usage data that survives application restarts
19
+ - Added `--no-lsp` to disable LSP tools, formatting, diagnostics, and warmup for a session
20
+
21
+ ### Changed
22
+
23
+ - Changed `SettingsManager.create()` to be async, requiring `await` when creating settings managers
24
+ - Changed `loadSettings()` to be async, requiring `await` when loading settings
25
+ - Changed `discoverSkills()` to be async, requiring `await` when discovering skills
26
+ - Changed `loadSlashCommands()` to be async, requiring `await` when loading slash commands
27
+ - Changed `buildSystemPrompt()` to be async, requiring `await` when building system prompts
28
+ - Changed `loadSkills()` to be async, requiring `await` when loading skills
29
+ - Changed `loadProjectContextFiles()` to be async, requiring `await` when loading context files
30
+ - Changed `getShellConfig()` to be async, requiring `await` when getting shell configuration
31
+ - Changed capability provider `load()` methods to be async-only, removing synchronous `loadSync` API
32
+ - Updated `plan` agent with enhanced structured planning process, parallel exploration via `explore` agent spawning, and improved output format with examples
33
+ - Removed `planner` agent command template, consolidating planning functionality into the `plan` agent
34
+
5
35
  ## [4.2.1] - 2026-01-11
6
36
  ### Added
7
37
 
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.1",
3
+ "version": "4.2.3",
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.1",
43
- "@oh-my-pi/pi-agent-core": "4.2.1",
44
- "@oh-my-pi/pi-git-tool": "4.2.1",
45
- "@oh-my-pi/pi-tui": "4.2.1",
42
+ "@oh-my-pi/pi-ai": "4.2.3",
43
+ "@oh-my-pi/pi-agent-core": "4.2.3",
44
+ "@oh-my-pi/pi-git-tool": "4.2.3",
45
+ "@oh-my-pi/pi-tui": "4.2.3",
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
+ }
@@ -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 { dirname, join, resolve } from "node:path";
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
- * Core loading logic shared by both load() and loadSync().
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
- for (const provider of providers) {
191
- try {
192
- const result = provider.load(ctx);
193
-
194
- if (result instanceof Promise) {
195
- throw new Error(
196
- `Provider "${provider.id}" returned a Promise. Use load() instead of loadSync() for async providers.`,
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
- if (result.items.length > 0) {
205
- contributingProviders.push(provider.id);
206
-
207
- for (const item of result.items) {
208
- const itemWithSource = item as T & { _source: SourceMeta };
209
- if (itemWithSource._source) {
210
- itemWithSource._source.providerName = provider.displayName;
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
- for (let i = 0; i < allItems.length; i++) {
230
- const item = allItems[i];
231
- const key = capability.key(item);
127
+ const result = entry.result;
128
+ if (!result) continue;
232
129
 
233
- if (key === undefined) {
234
- deduped.push(item);
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
- return {
258
- items: deduped,
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
- * Async loading logic shared by load().
267
- */
268
- async function loadImplAsync<T>(
269
- capability: Capability<T>,
270
- providers: Provider<T>[],
271
- ctx: LoadContext,
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 load<T>(capabilityId: string, options: LoadOptions = {}): Promise<CapabilityResult<T>> {
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, fs: createFsHelpers(cwd) };
218
+ const ctx: LoadContext = { cwd, home };
374
219
  const providers = filterProviders(capability, options);
375
220
 
376
- return loadImplAsync(capability, providers, ctx, options);
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
- clearCache();
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 abs = resolve(cwd ?? process.cwd(), path);
580
- statCache.delete(abs);
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(): { stat: number; content: number; dir: number } {
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
  // =============================================================================
@@ -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): LoadResult<T> | Promise<LoadResult<T>>;
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
  }