@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
|
@@ -118,6 +118,9 @@ export class AgentStorage {
|
|
|
118
118
|
private listSettingsStmt: Statement;
|
|
119
119
|
private insertSettingStmt: Statement;
|
|
120
120
|
private deleteSettingsStmt: Statement;
|
|
121
|
+
private getCacheStmt: Statement;
|
|
122
|
+
private upsertCacheStmt: Statement;
|
|
123
|
+
private deleteExpiredCacheStmt: Statement;
|
|
121
124
|
private listAuthStmt: Statement;
|
|
122
125
|
private listAuthByProviderStmt: Statement;
|
|
123
126
|
private insertAuthStmt: Statement;
|
|
@@ -139,6 +142,12 @@ export class AgentStorage {
|
|
|
139
142
|
);
|
|
140
143
|
this.deleteSettingsStmt = this.db.prepare("DELETE FROM settings");
|
|
141
144
|
|
|
145
|
+
this.getCacheStmt = this.db.prepare("SELECT value FROM cache WHERE key = ? AND expires_at > unixepoch()");
|
|
146
|
+
this.upsertCacheStmt = this.db.prepare(
|
|
147
|
+
"INSERT INTO cache (key, value, expires_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at",
|
|
148
|
+
);
|
|
149
|
+
this.deleteExpiredCacheStmt = this.db.prepare("DELETE FROM cache WHERE expires_at <= unixepoch()");
|
|
150
|
+
|
|
142
151
|
this.listAuthStmt = this.db.prepare(
|
|
143
152
|
"SELECT id, provider, credential_type, data FROM auth_credentials ORDER BY id ASC",
|
|
144
153
|
);
|
|
@@ -175,6 +184,13 @@ CREATE TABLE IF NOT EXISTS auth_credentials (
|
|
|
175
184
|
);
|
|
176
185
|
CREATE INDEX IF NOT EXISTS idx_auth_provider ON auth_credentials(provider);
|
|
177
186
|
|
|
187
|
+
CREATE TABLE IF NOT EXISTS cache (
|
|
188
|
+
key TEXT PRIMARY KEY,
|
|
189
|
+
value TEXT NOT NULL,
|
|
190
|
+
expires_at INTEGER NOT NULL
|
|
191
|
+
);
|
|
192
|
+
CREATE INDEX IF NOT EXISTS idx_cache_expires ON cache(expires_at);
|
|
193
|
+
|
|
178
194
|
CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY);
|
|
179
195
|
`);
|
|
180
196
|
|
|
@@ -302,6 +318,40 @@ CREATE TABLE settings (
|
|
|
302
318
|
}
|
|
303
319
|
}
|
|
304
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Gets a cached value by key. Returns null if not found or expired.
|
|
323
|
+
*/
|
|
324
|
+
getCache(key: string): string | null {
|
|
325
|
+
try {
|
|
326
|
+
const row = this.getCacheStmt.get(key) as { value?: string } | undefined;
|
|
327
|
+
return row?.value ?? null;
|
|
328
|
+
} catch {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Sets a cached value with expiry time (unix seconds).
|
|
335
|
+
*/
|
|
336
|
+
setCache(key: string, value: string, expiresAtSec: number): void {
|
|
337
|
+
try {
|
|
338
|
+
this.upsertCacheStmt.run(key, value, expiresAtSec);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
logger.warn("AgentStorage failed to set cache", { key, error: String(error) });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Deletes expired cache entries. Call periodically for cleanup.
|
|
346
|
+
*/
|
|
347
|
+
cleanExpiredCache(): void {
|
|
348
|
+
try {
|
|
349
|
+
this.deleteExpiredCacheStmt.run();
|
|
350
|
+
} catch {
|
|
351
|
+
// Ignore cleanup errors
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
305
355
|
/**
|
|
306
356
|
* Checks if any auth credentials exist in storage.
|
|
307
357
|
* @returns True if at least one credential is stored
|
package/src/core/auth-storage.ts
CHANGED
|
@@ -35,6 +35,22 @@ export type AuthCredentialEntry = AuthCredential | AuthCredential[];
|
|
|
35
35
|
|
|
36
36
|
export type AuthStorageData = Record<string, AuthCredentialEntry>;
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Serialized representation of AuthStorage for passing to subagent workers.
|
|
40
|
+
* Contains only the essential credential data, not runtime state.
|
|
41
|
+
*/
|
|
42
|
+
export interface SerializedAuthStorage {
|
|
43
|
+
credentials: Record<
|
|
44
|
+
string,
|
|
45
|
+
Array<{
|
|
46
|
+
id: number;
|
|
47
|
+
type: "api_key" | "oauth";
|
|
48
|
+
data: Record<string, unknown>;
|
|
49
|
+
}>
|
|
50
|
+
>;
|
|
51
|
+
runtimeOverrides?: Record<string, string>;
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
/**
|
|
39
55
|
* In-memory representation pairing DB row ID with credential.
|
|
40
56
|
* The ID is required for update/delete operations against agent.db.
|
|
@@ -116,6 +132,63 @@ export class AuthStorage {
|
|
|
116
132
|
this.storage = AgentStorage.open(this.dbPath);
|
|
117
133
|
}
|
|
118
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Create an in-memory AuthStorage instance from serialized data.
|
|
137
|
+
* Used by subagent workers to bypass discovery and use parent's credentials.
|
|
138
|
+
*/
|
|
139
|
+
static fromSerialized(data: SerializedAuthStorage): AuthStorage {
|
|
140
|
+
const instance = Object.create(AuthStorage.prototype) as AuthStorage;
|
|
141
|
+
instance.data = new Map();
|
|
142
|
+
instance.runtimeOverrides = new Map();
|
|
143
|
+
instance.providerRoundRobinIndex = new Map();
|
|
144
|
+
instance.sessionLastCredential = new Map();
|
|
145
|
+
instance.credentialBackoff = new Map();
|
|
146
|
+
instance.codexUsageCache = new Map();
|
|
147
|
+
|
|
148
|
+
for (const [provider, creds] of Object.entries(data.credentials)) {
|
|
149
|
+
instance.data.set(
|
|
150
|
+
provider,
|
|
151
|
+
creds.map((c) => ({
|
|
152
|
+
id: c.id,
|
|
153
|
+
credential:
|
|
154
|
+
c.type === "api_key"
|
|
155
|
+
? ({ type: "api_key", key: c.data.key as string } satisfies ApiKeyCredential)
|
|
156
|
+
: ({ type: "oauth", ...c.data } as OAuthCredential),
|
|
157
|
+
})),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (data.runtimeOverrides) {
|
|
161
|
+
for (const [k, v] of Object.entries(data.runtimeOverrides)) {
|
|
162
|
+
instance.runtimeOverrides.set(k, v);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return instance;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Serialize AuthStorage for passing to subagent workers.
|
|
171
|
+
* Excludes runtime state (round-robin, backoff, usage cache).
|
|
172
|
+
*/
|
|
173
|
+
serialize(): SerializedAuthStorage {
|
|
174
|
+
const credentials: SerializedAuthStorage["credentials"] = {};
|
|
175
|
+
for (const [provider, creds] of this.data.entries()) {
|
|
176
|
+
credentials[provider] = creds.map((c) => ({
|
|
177
|
+
id: c.id,
|
|
178
|
+
type: c.credential.type,
|
|
179
|
+
data: c.credential.type === "api_key" ? { key: c.credential.key } : { ...c.credential },
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
const runtimeOverrides: Record<string, string> = {};
|
|
183
|
+
for (const [k, v] of this.runtimeOverrides.entries()) {
|
|
184
|
+
runtimeOverrides[k] = v;
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
credentials,
|
|
188
|
+
runtimeOverrides: Object.keys(runtimeOverrides).length > 0 ? runtimeOverrides : undefined,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
119
192
|
/**
|
|
120
193
|
* Converts legacy auth.json path to agent.db path, or returns .db path as-is.
|
|
121
194
|
* @param authPath - Path to auth.json or agent.db
|
|
@@ -668,15 +741,41 @@ export class AuthStorage {
|
|
|
668
741
|
const normalizedBase = this.normalizeCodexBaseUrl(baseUrl);
|
|
669
742
|
const cacheKey = this.getCodexUsageCacheKey(accountId, normalizedBase);
|
|
670
743
|
const now = Date.now();
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
744
|
+
|
|
745
|
+
// Check in-memory cache first (fastest)
|
|
746
|
+
const memCached = this.codexUsageCache.get(cacheKey);
|
|
747
|
+
if (memCached && memCached.expiresAt > now) {
|
|
748
|
+
return memCached.usage;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Check DB cache (survives restarts)
|
|
752
|
+
const dbCached = this.storage.getCache(`codex_usage:${cacheKey}`);
|
|
753
|
+
if (dbCached) {
|
|
754
|
+
try {
|
|
755
|
+
const parsed = JSON.parse(dbCached) as CodexUsage;
|
|
756
|
+
// Store in memory for faster subsequent access
|
|
757
|
+
this.codexUsageCache.set(cacheKey, {
|
|
758
|
+
fetchedAt: now,
|
|
759
|
+
expiresAt: now + AuthStorage.codexUsageCacheTtlMs,
|
|
760
|
+
usage: parsed,
|
|
761
|
+
});
|
|
762
|
+
return parsed;
|
|
763
|
+
} catch {
|
|
764
|
+
// Invalid cache, continue to fetch
|
|
765
|
+
}
|
|
674
766
|
}
|
|
675
767
|
|
|
768
|
+
// Fetch from API
|
|
676
769
|
const usage = await this.fetchCodexUsage(credential, normalizedBase);
|
|
677
770
|
if (usage) {
|
|
678
771
|
const expiresAt = this.getCodexUsageExpiryMs(usage, now);
|
|
679
772
|
this.codexUsageCache.set(cacheKey, { fetchedAt: now, expiresAt, usage });
|
|
773
|
+
// Store in DB with 60s TTL
|
|
774
|
+
this.storage.setCache(
|
|
775
|
+
`codex_usage:${cacheKey}`,
|
|
776
|
+
JSON.stringify(usage),
|
|
777
|
+
Math.floor((now + AuthStorage.codexUsageCacheTtlMs) / 1000),
|
|
778
|
+
);
|
|
680
779
|
return usage;
|
|
681
780
|
}
|
|
682
781
|
|
|
@@ -142,7 +142,7 @@ function createOutputSink(
|
|
|
142
142
|
* @returns Promise resolving to execution result
|
|
143
143
|
*/
|
|
144
144
|
export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
|
|
145
|
-
const { shell, args, env, prefix } = getShellConfig();
|
|
145
|
+
const { shell, args, env, prefix } = await getShellConfig();
|
|
146
146
|
|
|
147
147
|
// Get or create shell snapshot (for aliases, functions, options)
|
|
148
148
|
const snapshotPath = await getOrCreateSnapshot(shell, env);
|
|
@@ -9,7 +9,7 @@ import * as os from "node:os";
|
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import * as typebox from "@sinclair/typebox";
|
|
11
11
|
import { toolCapability } from "../../capability/tool";
|
|
12
|
-
import { type CustomTool,
|
|
12
|
+
import { type CustomTool, loadCapability } from "../../discovery";
|
|
13
13
|
import * as piCodingAgent from "../../index";
|
|
14
14
|
import { theme } from "../../modes/interactive/theme/theme";
|
|
15
15
|
import type { ExecOptions } from "../exec";
|
|
@@ -225,7 +225,7 @@ export async function discoverAndLoadCustomTools(
|
|
|
225
225
|
};
|
|
226
226
|
|
|
227
227
|
// 1. Discover tools via capability system (user + project from all providers)
|
|
228
|
-
const discoveredTools =
|
|
228
|
+
const discoveredTools = await loadCapability<CustomTool>(toolCapability.id, { cwd });
|
|
229
229
|
for (const tool of discoveredTools.items) {
|
|
230
230
|
addPath(tool.path, {
|
|
231
231
|
provider: tool._source.provider,
|
|
@@ -8,7 +8,7 @@ import * as path from "node:path";
|
|
|
8
8
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
9
9
|
import * as TypeBox from "@sinclair/typebox";
|
|
10
10
|
import { type ExtensionModule, extensionModuleCapability } from "../../capability/extension-module";
|
|
11
|
-
import {
|
|
11
|
+
import { loadCapability } from "../../discovery";
|
|
12
12
|
import { getExtensionNameFromPath } from "../../discovery/helpers";
|
|
13
13
|
import * as piCodingAgent from "../../index";
|
|
14
14
|
import { createEventBus, type EventBus } from "../event-bus";
|
|
@@ -408,7 +408,7 @@ export async function discoverAndLoadExtensions(
|
|
|
408
408
|
};
|
|
409
409
|
|
|
410
410
|
// 1. Discover extension modules via capability API (native .omp/.pi only)
|
|
411
|
-
const discovered =
|
|
411
|
+
const discovered = await loadCapability<ExtensionModule>(extensionModuleCapability.id, { cwd });
|
|
412
412
|
for (const ext of discovered.items) {
|
|
413
413
|
if (ext._source.provider !== "native") continue;
|
|
414
414
|
if (isDisabledName(ext.name)) continue;
|
|
@@ -756,7 +756,7 @@ export type GetActiveToolsHandler = () => string[];
|
|
|
756
756
|
|
|
757
757
|
export type GetAllToolsHandler = () => string[];
|
|
758
758
|
|
|
759
|
-
export type SetActiveToolsHandler = (toolNames: string[]) => void
|
|
759
|
+
export type SetActiveToolsHandler = (toolNames: string[]) => Promise<void>;
|
|
760
760
|
|
|
761
761
|
export type SetModelHandler = (model: Model<any>) => Promise<boolean>;
|
|
762
762
|
|
package/src/core/hooks/loader.ts
CHANGED
|
@@ -7,7 +7,7 @@ import * as path from "node:path";
|
|
|
7
7
|
import * as typebox from "@sinclair/typebox";
|
|
8
8
|
import { hookCapability } from "../../capability/hook";
|
|
9
9
|
import type { Hook } from "../../discovery";
|
|
10
|
-
import {
|
|
10
|
+
import { loadCapability } from "../../discovery";
|
|
11
11
|
import * as piCodingAgent from "../../index";
|
|
12
12
|
import { logger } from "../logger";
|
|
13
13
|
import type { HookMessage } from "../messages";
|
|
@@ -278,7 +278,7 @@ export async function discoverAndLoadHooks(configuredPaths: string[], cwd: strin
|
|
|
278
278
|
};
|
|
279
279
|
|
|
280
280
|
// 1. Discover hooks via capability API
|
|
281
|
-
const discovered =
|
|
281
|
+
const discovered = await loadCapability<Hook>(hookCapability.id, { cwd });
|
|
282
282
|
addPaths(discovered.items.map((hook) => hook.path));
|
|
283
283
|
|
|
284
284
|
// 2. Explicitly configured paths (can override/add)
|
package/src/core/mcp/config.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { mcpCapability } from "../../capability/mcp";
|
|
8
8
|
import type { MCPServer } from "../../discovery";
|
|
9
|
-
import {
|
|
9
|
+
import { loadCapability } from "../../discovery";
|
|
10
10
|
import type { MCPServerConfig } from "./types";
|
|
11
11
|
|
|
12
12
|
/** Options for loading MCP configs */
|
|
@@ -81,7 +81,7 @@ export async function loadAllMCPConfigs(cwd: string, options?: LoadMCPConfigsOpt
|
|
|
81
81
|
const filterExa = options?.filterExa ?? true;
|
|
82
82
|
|
|
83
83
|
// Load MCP servers via capability system
|
|
84
|
-
const result = await
|
|
84
|
+
const result = await loadCapability<MCPServer>(mcpCapability.id, { cwd });
|
|
85
85
|
|
|
86
86
|
// Filter out project-level configs if disabled
|
|
87
87
|
const servers = enableProjectConfig
|
|
@@ -86,6 +86,15 @@ interface ProviderOverride {
|
|
|
86
86
|
apiKey?: string;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Serialized representation of ModelRegistry for passing to subagent workers.
|
|
91
|
+
*/
|
|
92
|
+
export interface SerializedModelRegistry {
|
|
93
|
+
models: Model<Api>[];
|
|
94
|
+
customProviderApiKeys?: Record<string, string>;
|
|
95
|
+
loadError?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
89
98
|
/** Result of loading custom models from models.json */
|
|
90
99
|
interface CustomModelsResult {
|
|
91
100
|
models: Model<Api>[];
|
|
@@ -140,6 +149,43 @@ export class ModelRegistry {
|
|
|
140
149
|
this.loadModels();
|
|
141
150
|
}
|
|
142
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Create an in-memory ModelRegistry instance from serialized data.
|
|
154
|
+
* Used by subagent workers to bypass discovery and use parent's models.
|
|
155
|
+
*/
|
|
156
|
+
static fromSerialized(data: SerializedModelRegistry, authStorage: AuthStorage): ModelRegistry {
|
|
157
|
+
const instance = Object.create(ModelRegistry.prototype) as ModelRegistry;
|
|
158
|
+
(instance as any).authStorage = authStorage;
|
|
159
|
+
instance.models = data.models;
|
|
160
|
+
instance.customProviderApiKeys = new Map(Object.entries(data.customProviderApiKeys ?? {}));
|
|
161
|
+
instance.loadError = data.loadError;
|
|
162
|
+
|
|
163
|
+
authStorage.setFallbackResolver((provider) => {
|
|
164
|
+
const keyConfig = instance.customProviderApiKeys.get(provider);
|
|
165
|
+
if (keyConfig) {
|
|
166
|
+
return resolveApiKeyConfig(keyConfig);
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return instance;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Serialize ModelRegistry for passing to subagent workers.
|
|
176
|
+
*/
|
|
177
|
+
serialize(): SerializedModelRegistry {
|
|
178
|
+
const customProviderApiKeys: Record<string, string> = {};
|
|
179
|
+
for (const [k, v] of this.customProviderApiKeys.entries()) {
|
|
180
|
+
customProviderApiKeys[k] = v;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
models: this.models,
|
|
184
|
+
customProviderApiKeys: Object.keys(customProviderApiKeys).length > 0 ? customProviderApiKeys : undefined,
|
|
185
|
+
loadError: this.loadError,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
143
189
|
/**
|
|
144
190
|
* Reload models from disk (built-in + custom from models.json).
|
|
145
191
|
*/
|
package/src/core/sdk.ts
CHANGED
|
@@ -33,7 +33,7 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
33
33
|
import chalk from "chalk";
|
|
34
34
|
// Import discovery to register all providers on startup
|
|
35
35
|
import "../discovery";
|
|
36
|
-
import {
|
|
36
|
+
import { loadCapability } from "../capability/index";
|
|
37
37
|
import { type Rule, ruleCapability } from "../capability/rule";
|
|
38
38
|
import { getAgentDir, getConfigDirPaths } from "../config";
|
|
39
39
|
import { initializeWithSettings } from "../discovery";
|
|
@@ -153,6 +153,9 @@ export interface CreateAgentSessionOptions {
|
|
|
153
153
|
/** Enable MCP server discovery from .mcp.json files. Default: true */
|
|
154
154
|
enableMCP?: boolean;
|
|
155
155
|
|
|
156
|
+
/** Enable LSP integration (tool, formatting, diagnostics, warmup). Default: true */
|
|
157
|
+
enableLsp?: boolean;
|
|
158
|
+
|
|
156
159
|
/** Tool names explicitly requested (enables disabled-by-default tools) */
|
|
157
160
|
toolNames?: string[];
|
|
158
161
|
|
|
@@ -286,12 +289,12 @@ export async function discoverExtensions(cwd?: string): Promise<LoadExtensionsRe
|
|
|
286
289
|
/**
|
|
287
290
|
* Discover skills from cwd and agentDir.
|
|
288
291
|
*/
|
|
289
|
-
export function discoverSkills(
|
|
292
|
+
export async function discoverSkills(
|
|
290
293
|
cwd?: string,
|
|
291
294
|
_agentDir?: string,
|
|
292
295
|
settings?: SkillsSettings,
|
|
293
|
-
): { skills: Skill[]; warnings: SkillWarning[] } {
|
|
294
|
-
return loadSkillsInternal({
|
|
296
|
+
): Promise<{ skills: Skill[]; warnings: SkillWarning[] }> {
|
|
297
|
+
return await loadSkillsInternal({
|
|
295
298
|
...settings,
|
|
296
299
|
cwd: cwd ?? process.cwd(),
|
|
297
300
|
});
|
|
@@ -301,11 +304,11 @@ export function discoverSkills(
|
|
|
301
304
|
* Discover context files (AGENTS.md) walking up from cwd.
|
|
302
305
|
* Returns files sorted by depth (farther from cwd first, so closer files appear last/more prominent).
|
|
303
306
|
*/
|
|
304
|
-
export function discoverContextFiles(
|
|
307
|
+
export async function discoverContextFiles(
|
|
305
308
|
cwd?: string,
|
|
306
309
|
_agentDir?: string,
|
|
307
|
-
): Array<{ path: string; content: string; depth?: number }
|
|
308
|
-
return loadContextFilesInternal({
|
|
310
|
+
): Promise<Array<{ path: string; content: string; depth?: number }>> {
|
|
311
|
+
return await loadContextFilesInternal({
|
|
309
312
|
cwd: cwd ?? process.cwd(),
|
|
310
313
|
});
|
|
311
314
|
}
|
|
@@ -323,7 +326,7 @@ export async function discoverPromptTemplates(cwd?: string, agentDir?: string):
|
|
|
323
326
|
/**
|
|
324
327
|
* Discover file-based slash commands from commands/ directories.
|
|
325
328
|
*/
|
|
326
|
-
export function discoverSlashCommands(cwd?: string): FileSlashCommand[] {
|
|
329
|
+
export async function discoverSlashCommands(cwd?: string): Promise<FileSlashCommand[]> {
|
|
327
330
|
return loadSlashCommandsInternal({ cwd: cwd ?? process.cwd() });
|
|
328
331
|
}
|
|
329
332
|
|
|
@@ -364,8 +367,8 @@ export interface BuildSystemPromptOptions {
|
|
|
364
367
|
/**
|
|
365
368
|
* Build the default system prompt.
|
|
366
369
|
*/
|
|
367
|
-
export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
|
|
368
|
-
return buildSystemPromptInternal({
|
|
370
|
+
export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<string> {
|
|
371
|
+
return await buildSystemPromptInternal({
|
|
369
372
|
cwd: options.cwd,
|
|
370
373
|
skills: options.skills,
|
|
371
374
|
contextFiles: options.contextFiles,
|
|
@@ -378,8 +381,8 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|
|
378
381
|
/**
|
|
379
382
|
* Load settings from agentDir/settings.json merged with cwd/.omp/settings.json.
|
|
380
383
|
*/
|
|
381
|
-
export function loadSettings(cwd?: string, agentDir?: string): Settings {
|
|
382
|
-
const manager = SettingsManager.create(cwd ?? process.cwd(), agentDir ?? getDefaultAgentDir());
|
|
384
|
+
export async function loadSettings(cwd?: string, agentDir?: string): Promise<Settings> {
|
|
385
|
+
const manager = await SettingsManager.create(cwd ?? process.cwd(), agentDir ?? getDefaultAgentDir());
|
|
383
386
|
return {
|
|
384
387
|
modelRoles: manager.getModelRoles(),
|
|
385
388
|
defaultThinkingLevel: manager.getDefaultThinkingLevel(),
|
|
@@ -543,7 +546,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
543
546
|
const modelRegistry = options.modelRegistry ?? (await discoverModels(authStorage, agentDir));
|
|
544
547
|
time("discoverModels");
|
|
545
548
|
|
|
546
|
-
const settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);
|
|
549
|
+
const settingsManager = options.settingsManager ?? (await SettingsManager.create(cwd, agentDir));
|
|
547
550
|
time("settingsManager");
|
|
548
551
|
initializeWithSettings(settingsManager);
|
|
549
552
|
time("initializeWithSettings");
|
|
@@ -596,12 +599,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
596
599
|
|
|
597
600
|
// Fall back to first available model with a valid API key
|
|
598
601
|
if (!model) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
602
|
+
const allModels = modelRegistry.getAll();
|
|
603
|
+
const keyResults = await Promise.all(
|
|
604
|
+
allModels.map(async (m) => ({ model: m, hasKey: !!(await modelRegistry.getApiKey(m, sessionId)) })),
|
|
605
|
+
);
|
|
606
|
+
model = keyResults.find((r) => r.hasKey)?.model;
|
|
605
607
|
time("findAvailableModel");
|
|
606
608
|
if (model) {
|
|
607
609
|
if (modelFallbackMessage) {
|
|
@@ -636,7 +638,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
636
638
|
skills = options.skills;
|
|
637
639
|
skillWarnings = [];
|
|
638
640
|
} else {
|
|
639
|
-
const discovered = discoverSkills(cwd, agentDir, settingsManager.getSkillsSettings());
|
|
641
|
+
const discovered = await discoverSkills(cwd, agentDir, settingsManager.getSkillsSettings());
|
|
640
642
|
skills = discovered.skills;
|
|
641
643
|
skillWarnings = discovered.warnings;
|
|
642
644
|
}
|
|
@@ -644,7 +646,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
644
646
|
|
|
645
647
|
// Discover rules
|
|
646
648
|
const ttsrManager = createTtsrManager(settingsManager.getTtsrSettings());
|
|
647
|
-
const rulesResult = loadCapability<Rule>(ruleCapability.id, { cwd });
|
|
649
|
+
const rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });
|
|
648
650
|
for (const rule of rulesResult.items) {
|
|
649
651
|
if (rule.ttsrTrigger) {
|
|
650
652
|
ttsrManager.addRule(rule);
|
|
@@ -653,7 +655,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
653
655
|
time("discoverTtsrRules");
|
|
654
656
|
|
|
655
657
|
// Filter rules for the rulebook (non-TTSR, non-alwaysApply, with descriptions)
|
|
656
|
-
const rulebookRules = rulesResult.items.filter((rule) => {
|
|
658
|
+
const rulebookRules = rulesResult.items.filter((rule: Rule) => {
|
|
657
659
|
if (rule.ttsrTrigger) return false;
|
|
658
660
|
if (rule.alwaysApply) return false;
|
|
659
661
|
if (!rule.description) return false;
|
|
@@ -661,15 +663,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
661
663
|
});
|
|
662
664
|
time("filterRulebookRules");
|
|
663
665
|
|
|
664
|
-
const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
|
|
666
|
+
const contextFiles = options.contextFiles ?? (await discoverContextFiles(cwd, agentDir));
|
|
665
667
|
time("discoverContextFiles");
|
|
666
668
|
|
|
667
669
|
let agent: Agent;
|
|
668
670
|
let session: AgentSession;
|
|
669
671
|
|
|
672
|
+
const enableLsp = options.enableLsp ?? true;
|
|
673
|
+
|
|
670
674
|
const toolSession: ToolSession = {
|
|
671
675
|
cwd,
|
|
672
676
|
hasUI: options.hasUI ?? false,
|
|
677
|
+
enableLsp,
|
|
673
678
|
eventBus,
|
|
674
679
|
outputSchema: options.outputSchema,
|
|
675
680
|
requireCompleteTool: options.requireCompleteTool,
|
|
@@ -681,6 +686,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
681
686
|
return activeModel ? formatModelString(activeModel) : undefined;
|
|
682
687
|
},
|
|
683
688
|
settings: settingsManager,
|
|
689
|
+
authStorage,
|
|
690
|
+
modelRegistry,
|
|
684
691
|
};
|
|
685
692
|
|
|
686
693
|
const builtinTools = await createTools(toolSession, options.toolNames);
|
|
@@ -706,6 +713,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
706
713
|
});
|
|
707
714
|
time("discoverAndLoadMCPTools");
|
|
708
715
|
mcpManager = mcpResult.manager;
|
|
716
|
+
toolSession.mcpManager = mcpManager;
|
|
709
717
|
|
|
710
718
|
// If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
|
|
711
719
|
if (mcpResult.exaApiKeys.length > 0 && !process.env.EXA_API_KEY) {
|
|
@@ -846,9 +854,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
846
854
|
}
|
|
847
855
|
time("combineTools");
|
|
848
856
|
|
|
849
|
-
const rebuildSystemPrompt = (toolNames: string[], tools: Map<string, AgentTool>): string => {
|
|
857
|
+
const rebuildSystemPrompt = async (toolNames: string[], tools: Map<string, AgentTool>): Promise<string> => {
|
|
850
858
|
toolContextStore.setToolNames(toolNames);
|
|
851
|
-
const defaultPrompt = buildSystemPromptInternal({
|
|
859
|
+
const defaultPrompt = await buildSystemPromptInternal({
|
|
852
860
|
cwd,
|
|
853
861
|
skills,
|
|
854
862
|
contextFiles,
|
|
@@ -862,7 +870,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
862
870
|
return defaultPrompt;
|
|
863
871
|
}
|
|
864
872
|
if (typeof options.systemPrompt === "string") {
|
|
865
|
-
return buildSystemPromptInternal({
|
|
873
|
+
return await buildSystemPromptInternal({
|
|
866
874
|
cwd,
|
|
867
875
|
skills,
|
|
868
876
|
contextFiles,
|
|
@@ -876,13 +884,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
876
884
|
return options.systemPrompt(defaultPrompt);
|
|
877
885
|
};
|
|
878
886
|
|
|
879
|
-
const systemPrompt = rebuildSystemPrompt(Array.from(toolRegistry.keys()), toolRegistry);
|
|
887
|
+
const systemPrompt = await rebuildSystemPrompt(Array.from(toolRegistry.keys()), toolRegistry);
|
|
880
888
|
time("buildSystemPrompt");
|
|
881
889
|
|
|
882
890
|
const promptTemplates = options.promptTemplates ?? (await discoverPromptTemplates(cwd, agentDir));
|
|
883
891
|
time("discoverPromptTemplates");
|
|
884
892
|
|
|
885
|
-
const slashCommands = options.slashCommands ?? discoverSlashCommands(cwd);
|
|
893
|
+
const slashCommands = options.slashCommands ?? (await discoverSlashCommands(cwd));
|
|
886
894
|
time("discoverSlashCommands");
|
|
887
895
|
|
|
888
896
|
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
|
@@ -991,7 +999,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
991
999
|
|
|
992
1000
|
// Warm up LSP servers (connects to detected servers)
|
|
993
1001
|
let lspServers: CreateAgentSessionResult["lspServers"];
|
|
994
|
-
if (settingsManager.getLspDiagnosticsOnWrite()) {
|
|
1002
|
+
if (enableLsp && settingsManager.getLspDiagnosticsOnWrite()) {
|
|
995
1003
|
try {
|
|
996
1004
|
const result = await warmupLspServers(cwd, {
|
|
997
1005
|
onConnecting: (serverNames) => {
|