@oh-my-pi/pi-coding-agent 8.0.16 → 8.1.0
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 +105 -0
- package/package.json +14 -11
- package/scripts/generate-wasm-b64.ts +24 -0
- package/src/capability/context-file.ts +1 -1
- package/src/capability/extension-module.ts +1 -1
- package/src/capability/extension.ts +1 -1
- package/src/capability/hook.ts +1 -1
- package/src/capability/instruction.ts +1 -1
- package/src/capability/mcp.ts +1 -1
- package/src/capability/prompt.ts +1 -1
- package/src/capability/rule.ts +1 -1
- package/src/capability/settings.ts +1 -1
- package/src/capability/skill.ts +1 -1
- package/src/capability/slash-command.ts +1 -1
- package/src/capability/ssh.ts +1 -1
- package/src/capability/system-prompt.ts +1 -1
- package/src/capability/tool.ts +1 -1
- package/src/cli/args.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -5
- package/src/commit/agentic/agent.ts +309 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +359 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +26 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +40 -0
- package/src/commit/agentic/state.ts +74 -0
- package/src/commit/agentic/tools/analyze-file.ts +131 -0
- package/src/commit/agentic/tools/git-file-diff.ts +194 -0
- package/src/commit/agentic/tools/git-hunk.ts +50 -0
- package/src/commit/agentic/tools/git-overview.ts +84 -0
- package/src/commit/agentic/tools/index.ts +56 -0
- package/src/commit/agentic/tools/propose-changelog.ts +128 -0
- package/src/commit/agentic/tools/propose-commit.ts +154 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/split-commit.ts +284 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +200 -0
- package/src/commit/analysis/conventional.ts +169 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +114 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +36 -0
- package/src/commit/changelog/generate.ts +112 -0
- package/src/commit/changelog/index.ts +233 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +93 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/git/errors.ts +11 -0
- package/src/commit/git/index.ts +217 -0
- package/src/commit/git/operations.ts +53 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/.map-phase.ts.kate-swp +0 -0
- package/src/commit/map-reduce/index.ts +63 -0
- package/src/commit/map-reduce/map-phase.ts +193 -0
- package/src/commit/map-reduce/reduce-phase.ts +147 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +84 -0
- package/src/commit/pipeline.ts +242 -0
- package/src/commit/prompts/analysis-system.md +155 -0
- package/src/commit/prompts/analysis-user.md +41 -0
- package/src/commit/prompts/changelog-system.md +56 -0
- package/src/commit/prompts/changelog-user.md +19 -0
- package/src/commit/prompts/file-observer-system.md +26 -0
- package/src/commit/prompts/file-observer-user.md +9 -0
- package/src/commit/prompts/reduce-system.md +60 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +4 -0
- package/src/commit/prompts/summary-system.md +52 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/types.ts +109 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/config/file-lock.ts +111 -0
- package/src/config/model-registry.ts +16 -7
- package/src/config/settings-manager.ts +115 -40
- package/src/config.ts +5 -5
- package/src/discovery/agents-md.ts +1 -1
- package/src/discovery/builtin.ts +1 -1
- package/src/discovery/claude.ts +1 -1
- package/src/discovery/cline.ts +1 -1
- package/src/discovery/codex.ts +1 -1
- package/src/discovery/cursor.ts +1 -1
- package/src/discovery/gemini.ts +1 -1
- package/src/discovery/github.ts +1 -1
- package/src/discovery/index.ts +11 -11
- package/src/discovery/mcp-json.ts +1 -1
- package/src/discovery/ssh.ts +1 -1
- package/src/discovery/vscode.ts +1 -1
- package/src/discovery/windsurf.ts +1 -1
- package/src/extensibility/custom-commands/loader.ts +1 -1
- package/src/extensibility/custom-commands/types.ts +1 -1
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/loader.ts +1 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/loader.ts +1 -1
- package/src/extensibility/hooks/types.ts +3 -3
- package/src/index.ts +10 -10
- package/src/ipy/executor.ts +97 -1
- package/src/lsp/index.ts +1 -1
- package/src/lsp/render.ts +90 -46
- package/src/main.ts +16 -3
- package/src/mcp/loader.ts +3 -3
- package/src/migrations.ts +3 -3
- package/src/modes/components/assistant-message.ts +29 -1
- package/src/modes/components/tool-execution.ts +5 -3
- package/src/modes/components/tree-selector.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -3
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-mode.ts +1 -4
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/modes/theme/mermaid-cache.ts +89 -0
- package/src/modes/theme/theme.ts +2 -0
- package/src/modes/types.ts +2 -2
- package/src/patch/index.ts +3 -9
- package/src/patch/shared.ts +33 -5
- package/src/prompts/tools/task.md +2 -0
- package/src/sdk.ts +60 -22
- package/src/session/agent-session.ts +3 -3
- package/src/session/agent-storage.ts +32 -28
- package/src/session/artifacts.ts +24 -1
- package/src/session/auth-storage.ts +25 -10
- package/src/session/storage-migration.ts +12 -53
- package/src/system-prompt.ts +2 -2
- package/src/task/.executor.ts.kate-swp +0 -0
- package/src/task/executor.ts +1 -1
- package/src/task/index.ts +10 -1
- package/src/task/output-manager.ts +94 -0
- package/src/task/render.ts +7 -12
- package/src/task/worker.ts +1 -1
- package/src/tools/ask.ts +35 -13
- package/src/tools/bash.ts +80 -87
- package/src/tools/calculator.ts +42 -40
- package/src/tools/complete.ts +1 -1
- package/src/tools/fetch.ts +67 -104
- package/src/tools/find.ts +83 -86
- package/src/tools/grep.ts +80 -96
- package/src/tools/index.ts +10 -7
- package/src/tools/ls.ts +39 -65
- package/src/tools/notebook.ts +48 -64
- package/src/tools/output-utils.ts +1 -1
- package/src/tools/python.ts +71 -183
- package/src/tools/read.ts +74 -15
- package/src/tools/render-utils.ts +1 -15
- package/src/tools/ssh.ts +43 -24
- package/src/tools/todo-write.ts +27 -15
- package/src/tools/write.ts +93 -64
- package/src/tui/code-cell.ts +115 -0
- package/src/tui/file-list.ts +48 -0
- package/src/tui/index.ts +11 -0
- package/src/tui/output-block.ts +73 -0
- package/src/tui/status-line.ts +40 -0
- package/src/tui/tree-list.ts +56 -0
- package/src/tui/types.ts +17 -0
- package/src/tui/utils.ts +49 -0
- package/src/vendor/photon/photon_rs_bg.wasm.b64.js +1 -0
- package/src/web/search/auth.ts +1 -1
- package/src/web/search/index.ts +1 -1
- package/src/web/search/render.ts +119 -163
- package/tsconfig.json +0 -42
|
@@ -116,8 +116,6 @@ export class AgentStorage {
|
|
|
116
116
|
private static instances = new Map<string, AgentStorage>();
|
|
117
117
|
|
|
118
118
|
private listSettingsStmt: Statement;
|
|
119
|
-
private insertSettingStmt: Statement;
|
|
120
|
-
private deleteSettingsStmt: Statement;
|
|
121
119
|
private getCacheStmt: Statement;
|
|
122
120
|
private upsertCacheStmt: Statement;
|
|
123
121
|
private deleteExpiredCacheStmt: Statement;
|
|
@@ -137,10 +135,6 @@ export class AgentStorage {
|
|
|
137
135
|
this.hardenPermissions(dbPath);
|
|
138
136
|
|
|
139
137
|
this.listSettingsStmt = this.db.prepare("SELECT key, value FROM settings");
|
|
140
|
-
this.insertSettingStmt = this.db.prepare(
|
|
141
|
-
"INSERT INTO settings (key, value, updated_at) VALUES (?, ?, unixepoch())",
|
|
142
|
-
);
|
|
143
|
-
this.deleteSettingsStmt = this.db.prepare("DELETE FROM settings");
|
|
144
138
|
|
|
145
139
|
this.getCacheStmt = this.db.prepare("SELECT value FROM cache WHERE key = ? AND expires_at > unixepoch()");
|
|
146
140
|
this.upsertCacheStmt = this.db.prepare(
|
|
@@ -264,20 +258,43 @@ CREATE TABLE settings (
|
|
|
264
258
|
|
|
265
259
|
/**
|
|
266
260
|
* Returns singleton instance for the given database path, creating if needed.
|
|
261
|
+
* Retries on SQLITE_BUSY with exponential backoff.
|
|
267
262
|
* @param dbPath - Path to the SQLite database file (defaults to config path)
|
|
268
263
|
* @returns AgentStorage instance for the given path
|
|
269
264
|
*/
|
|
270
|
-
static open(dbPath: string = getAgentDbPath()): AgentStorage {
|
|
265
|
+
static async open(dbPath: string = getAgentDbPath()): Promise<AgentStorage> {
|
|
271
266
|
const existing = AgentStorage.instances.get(dbPath);
|
|
272
267
|
if (existing) return existing;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
268
|
+
|
|
269
|
+
const maxRetries = 3;
|
|
270
|
+
const baseDelayMs = 100;
|
|
271
|
+
let lastError: Error | undefined;
|
|
272
|
+
|
|
273
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
274
|
+
try {
|
|
275
|
+
const storage = new AgentStorage(dbPath);
|
|
276
|
+
AgentStorage.instances.set(dbPath, storage);
|
|
277
|
+
return storage;
|
|
278
|
+
} catch (err) {
|
|
279
|
+
const isSqliteBusy = err && typeof err === "object" && (err as { code?: string }).code === "SQLITE_BUSY";
|
|
280
|
+
if (!isSqliteBusy) {
|
|
281
|
+
throw err;
|
|
282
|
+
}
|
|
283
|
+
lastError = err as Error;
|
|
284
|
+
const delayMs = baseDelayMs * 2 ** attempt;
|
|
285
|
+
await Bun.sleep(delayMs);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
throw lastError ?? new Error("Failed to open database after retries");
|
|
276
290
|
}
|
|
277
291
|
|
|
278
292
|
/**
|
|
279
|
-
* Retrieves all settings from storage.
|
|
293
|
+
* Retrieves all settings from storage (legacy, for migration only).
|
|
294
|
+
* Settings are now stored in config.yml. This method is only used
|
|
295
|
+
* during migration from agent.db to config.yml.
|
|
280
296
|
* @returns Settings object, or null if no settings are stored
|
|
297
|
+
* @deprecated Use config.yml instead. This is only for migration.
|
|
281
298
|
*/
|
|
282
299
|
getSettings(): Settings | null {
|
|
283
300
|
const rows = (this.listSettingsStmt.all() as SettingsRow[]) ?? [];
|
|
@@ -297,26 +314,13 @@ CREATE TABLE settings (
|
|
|
297
314
|
}
|
|
298
315
|
|
|
299
316
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* @param settings - Settings object to persist
|
|
317
|
+
* @deprecated Settings are now stored in config.yml, not agent.db.
|
|
318
|
+
* This method is kept for backward compatibility but does nothing.
|
|
303
319
|
*/
|
|
304
320
|
saveSettings(settings: Settings): void {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
this.deleteSettingsStmt.run();
|
|
308
|
-
for (const [key, value] of rows) {
|
|
309
|
-
const serialized = JSON.stringify(value);
|
|
310
|
-
if (serialized === undefined) continue;
|
|
311
|
-
this.insertSettingStmt.run(key, serialized);
|
|
312
|
-
}
|
|
321
|
+
logger.warn("AgentStorage.saveSettings is deprecated - settings are now stored in config.yml", {
|
|
322
|
+
keys: Object.keys(settings),
|
|
313
323
|
});
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
replace(entries);
|
|
317
|
-
} catch (error) {
|
|
318
|
-
logger.error("AgentStorage failed to save settings", { error: String(error) });
|
|
319
|
-
}
|
|
320
324
|
}
|
|
321
325
|
|
|
322
326
|
/**
|
package/src/session/artifacts.ts
CHANGED
|
@@ -18,6 +18,7 @@ export class ArtifactManager {
|
|
|
18
18
|
#nextId = 0;
|
|
19
19
|
readonly #dir: string;
|
|
20
20
|
#dirCreated = false;
|
|
21
|
+
#initialized = false;
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* @param sessionFile Path to the session .jsonl file
|
|
@@ -40,6 +41,28 @@ export class ArtifactManager {
|
|
|
40
41
|
await mkdir(this.#dir, { recursive: true });
|
|
41
42
|
this.#dirCreated = true;
|
|
42
43
|
}
|
|
44
|
+
if (!this.#initialized) {
|
|
45
|
+
await this.#scanExistingIds();
|
|
46
|
+
this.#initialized = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Scan existing artifact files to find the next available ID.
|
|
52
|
+
* This ensures we don't overwrite artifacts when resuming a session.
|
|
53
|
+
*/
|
|
54
|
+
async #scanExistingIds(): Promise<void> {
|
|
55
|
+
const files = await this.listFiles();
|
|
56
|
+
let maxId = -1;
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
// Files are named: {id}.{toolType}.log
|
|
59
|
+
const match = file.match(/^(\d+)\..*\.log$/);
|
|
60
|
+
if (match) {
|
|
61
|
+
const id = parseInt(match[1], 10);
|
|
62
|
+
if (id > maxId) maxId = id;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.#nextId = maxId + 1;
|
|
43
66
|
}
|
|
44
67
|
|
|
45
68
|
/**
|
|
@@ -58,7 +81,7 @@ export class ArtifactManager {
|
|
|
58
81
|
async allocatePath(toolType: string): Promise<{ id: string; path: string }> {
|
|
59
82
|
await this.#ensureDir();
|
|
60
83
|
const id = String(this.allocateId());
|
|
61
|
-
const filename = `${id}.${toolType}.
|
|
84
|
+
const filename = `${id}.${toolType}.log`;
|
|
62
85
|
return { id, path: join(this.#dir, filename) };
|
|
63
86
|
}
|
|
64
87
|
|
|
@@ -162,17 +162,14 @@ export class AuthStorage {
|
|
|
162
162
|
private usageLogger?: UsageLogger;
|
|
163
163
|
private fallbackResolver?: (provider: string) => string | undefined;
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
* @param authPath - Legacy auth.json path used for migration and locating agent.db
|
|
167
|
-
* @param fallbackPaths - Additional auth.json paths to migrate (legacy support)
|
|
168
|
-
*/
|
|
169
|
-
constructor(
|
|
165
|
+
private constructor(
|
|
170
166
|
private authPath: string,
|
|
171
167
|
private fallbackPaths: string[] = [],
|
|
168
|
+
storage: AgentStorage,
|
|
172
169
|
options: AuthStorageOptions = {},
|
|
173
170
|
) {
|
|
174
171
|
this.dbPath = AuthStorage.resolveDbPath(authPath);
|
|
175
|
-
this.storage =
|
|
172
|
+
this.storage = storage;
|
|
176
173
|
this.usageProviderResolver = options.usageProviderResolver ?? resolveDefaultUsageProvider;
|
|
177
174
|
this.usageCache = options.usageCache ?? new AuthStorageUsageCache(this.storage);
|
|
178
175
|
this.usageFetch = options.usageFetch ?? fetch;
|
|
@@ -185,17 +182,35 @@ export class AuthStorage {
|
|
|
185
182
|
} satisfies UsageLogger);
|
|
186
183
|
}
|
|
187
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Create an AuthStorage instance.
|
|
187
|
+
* @param authPath - Legacy auth.json path used for migration and locating agent.db
|
|
188
|
+
* @param fallbackPaths - Additional auth.json paths to migrate (legacy support)
|
|
189
|
+
*/
|
|
190
|
+
static async create(
|
|
191
|
+
authPath: string,
|
|
192
|
+
fallbackPaths: string[] = [],
|
|
193
|
+
options: AuthStorageOptions = {},
|
|
194
|
+
): Promise<AuthStorage> {
|
|
195
|
+
const dbPath = AuthStorage.resolveDbPath(authPath);
|
|
196
|
+
const storage = await AgentStorage.open(dbPath);
|
|
197
|
+
return new AuthStorage(authPath, fallbackPaths, storage, options);
|
|
198
|
+
}
|
|
199
|
+
|
|
188
200
|
/**
|
|
189
201
|
* Create an in-memory AuthStorage instance from serialized data.
|
|
190
202
|
* Used by subagent workers to bypass discovery and use parent's credentials.
|
|
191
203
|
*/
|
|
192
|
-
static fromSerialized(data: SerializedAuthStorage, options: AuthStorageOptions = {}): AuthStorage {
|
|
193
|
-
const instance = Object.create(AuthStorage.prototype) as AuthStorage;
|
|
204
|
+
static async fromSerialized(data: SerializedAuthStorage, options: AuthStorageOptions = {}): Promise<AuthStorage> {
|
|
194
205
|
const authPath = data.authPath ?? data.dbPath ?? getAuthPath();
|
|
206
|
+
const dbPath = data.dbPath ?? AuthStorage.resolveDbPath(authPath);
|
|
207
|
+
const storage = await AgentStorage.open(dbPath);
|
|
208
|
+
|
|
209
|
+
const instance = Object.create(AuthStorage.prototype) as AuthStorage;
|
|
195
210
|
instance.authPath = authPath;
|
|
196
211
|
instance.fallbackPaths = [];
|
|
197
|
-
instance.dbPath =
|
|
198
|
-
instance.storage =
|
|
212
|
+
instance.dbPath = dbPath;
|
|
213
|
+
instance.storage = storage;
|
|
199
214
|
instance.data = new Map();
|
|
200
215
|
instance.runtimeOverrides = new Map();
|
|
201
216
|
instance.providerRoundRobinIndex = new Map();
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Migrates legacy
|
|
3
|
-
*
|
|
2
|
+
* Migrates legacy auth.json to SQLite-based agent.db.
|
|
3
|
+
* Auth credentials merge per-provider when missing.
|
|
4
4
|
* Original JSON files are backed up to .bak and removed after successful migration.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: Settings migration is now handled by SettingsManager.migrateToYaml(),
|
|
7
|
+
* which migrates from both settings.json and agent.db to config.yaml.
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
import { getAgentDbPath } from "@oh-my-pi/pi-coding-agent/config";
|
|
8
|
-
import type { Settings } from "@oh-my-pi/pi-coding-agent/config/settings-manager";
|
|
9
11
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
10
12
|
import { AgentStorage } from "./agent-storage";
|
|
11
13
|
import type { AuthCredential, AuthCredentialEntry, AuthStorageData } from "./auth-storage";
|
|
@@ -14,7 +16,7 @@ import type { AuthCredential, AuthCredentialEntry, AuthStorageData } from "./aut
|
|
|
14
16
|
type MigrationPaths = {
|
|
15
17
|
/** Directory containing agent.db */
|
|
16
18
|
agentDir: string;
|
|
17
|
-
/** Path to legacy settings.json file */
|
|
19
|
+
/** Path to legacy settings.json file (kept for API compatibility, no longer used) */
|
|
18
20
|
settingsPath: string;
|
|
19
21
|
/** Candidate paths to search for auth.json (checked in order) */
|
|
20
22
|
authPaths: string[];
|
|
@@ -22,7 +24,7 @@ type MigrationPaths = {
|
|
|
22
24
|
|
|
23
25
|
/** Result of the JSON-to-SQLite storage migration. */
|
|
24
26
|
export interface StorageMigrationResult {
|
|
25
|
-
/** Whether settings.json was migrated
|
|
27
|
+
/** Whether settings.json was migrated (always false - settings migration is handled elsewhere) */
|
|
26
28
|
migratedSettings: boolean;
|
|
27
29
|
/** Whether auth.json was migrated to agent.db */
|
|
28
30
|
migratedAuth: boolean;
|
|
@@ -39,20 +41,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
39
41
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
/**
|
|
43
|
-
* Transforms legacy settings to current schema (e.g., queueMode -> steeringMode).
|
|
44
|
-
* @param settings - Settings object potentially containing deprecated keys
|
|
45
|
-
* @returns Settings with deprecated keys renamed to current equivalents
|
|
46
|
-
*/
|
|
47
|
-
function migrateLegacySettings(settings: Settings): Settings {
|
|
48
|
-
const migrated = { ...settings } as Record<string, unknown>;
|
|
49
|
-
if ("queueMode" in migrated && !("steeringMode" in migrated)) {
|
|
50
|
-
migrated.steeringMode = migrated.queueMode;
|
|
51
|
-
delete migrated.queueMode;
|
|
52
|
-
}
|
|
53
|
-
return migrated as Settings;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
44
|
/**
|
|
57
45
|
* Normalizes credential entries to array format (legacy stored single credentials).
|
|
58
46
|
* @param entry - Single credential or array of credentials
|
|
@@ -99,32 +87,6 @@ async function backupJson(path: string): Promise<void> {
|
|
|
99
87
|
}
|
|
100
88
|
}
|
|
101
89
|
|
|
102
|
-
/**
|
|
103
|
-
* Migrates settings.json to SQLite storage if DB is empty.
|
|
104
|
-
* @param storage - AgentStorage instance to migrate into
|
|
105
|
-
* @param settingsPath - Path to legacy settings.json
|
|
106
|
-
* @param warnings - Array to collect non-fatal warnings
|
|
107
|
-
* @returns True if migration was performed
|
|
108
|
-
*/
|
|
109
|
-
async function migrateSettings(storage: AgentStorage, settingsPath: string, warnings: string[]): Promise<boolean> {
|
|
110
|
-
const settingsFile = Bun.file(settingsPath);
|
|
111
|
-
const settingsExists = await settingsFile.exists();
|
|
112
|
-
const hasDbSettings = storage.getSettings() !== null;
|
|
113
|
-
|
|
114
|
-
if (!settingsExists) return false;
|
|
115
|
-
if (hasDbSettings) {
|
|
116
|
-
warnings.push(`settings.json exists but agent.db is authoritative: ${settingsPath}`);
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const settingsJson = await readJsonFile<Settings>(settingsPath);
|
|
121
|
-
if (!settingsJson) return false;
|
|
122
|
-
|
|
123
|
-
storage.saveSettings(migrateLegacySettings(settingsJson));
|
|
124
|
-
await backupJson(settingsPath);
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
90
|
/**
|
|
129
91
|
* Finds the first valid auth.json from candidate paths (checked in priority order).
|
|
130
92
|
* @param authPaths - Candidate paths to search (e.g., project-local before global)
|
|
@@ -191,19 +153,16 @@ async function migrateAuth(storage: AgentStorage, authPaths: string[], warnings:
|
|
|
191
153
|
}
|
|
192
154
|
|
|
193
155
|
/**
|
|
194
|
-
* Migrates legacy
|
|
195
|
-
* Settings
|
|
156
|
+
* Migrates legacy auth.json to SQLite-based agent.db.
|
|
157
|
+
* Settings migration is handled separately by SettingsManager.migrateToYaml().
|
|
196
158
|
* @param paths - Configuration specifying locations of legacy files and target DB
|
|
197
159
|
* @returns Result indicating what was migrated and any warnings encountered
|
|
198
160
|
*/
|
|
199
161
|
export async function migrateJsonStorage(paths: MigrationPaths): Promise<StorageMigrationResult> {
|
|
200
|
-
const storage = AgentStorage.open(getAgentDbPath(paths.agentDir));
|
|
162
|
+
const storage = await AgentStorage.open(getAgentDbPath(paths.agentDir));
|
|
201
163
|
const warnings: string[] = [];
|
|
202
164
|
|
|
203
|
-
const
|
|
204
|
-
migrateSettings(storage, paths.settingsPath, warnings),
|
|
205
|
-
migrateAuth(storage, paths.authPaths, warnings),
|
|
206
|
-
]);
|
|
165
|
+
const migratedAuth = await migrateAuth(storage, paths.authPaths, warnings);
|
|
207
166
|
|
|
208
167
|
if (warnings.length > 0) {
|
|
209
168
|
for (const warning of warnings) {
|
|
@@ -211,5 +170,5 @@ export async function migrateJsonStorage(paths: MigrationPaths): Promise<Storage
|
|
|
211
170
|
}
|
|
212
171
|
}
|
|
213
172
|
|
|
214
|
-
return { migratedSettings, migratedAuth, warnings };
|
|
173
|
+
return { migratedSettings: false, migratedAuth, warnings };
|
|
215
174
|
}
|
package/src/system-prompt.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type ContextFile,
|
|
14
14
|
loadCapability,
|
|
15
15
|
type SystemPrompt as SystemPromptFile,
|
|
16
|
-
} from "@oh-my-pi/pi-coding-agent/discovery
|
|
16
|
+
} from "@oh-my-pi/pi-coding-agent/discovery";
|
|
17
17
|
import { loadSkills, type Skill } from "@oh-my-pi/pi-coding-agent/extensibility/skills";
|
|
18
18
|
import customSystemPromptTemplate from "@oh-my-pi/pi-coding-agent/prompts/system/custom-system-prompt.md" with {
|
|
19
19
|
type: "text",
|
|
@@ -21,7 +21,7 @@ import customSystemPromptTemplate from "@oh-my-pi/pi-coding-agent/prompts/system
|
|
|
21
21
|
import systemPromptTemplate from "@oh-my-pi/pi-coding-agent/prompts/system/system-prompt.md" with { type: "text" };
|
|
22
22
|
import { $ } from "bun";
|
|
23
23
|
import chalk from "chalk";
|
|
24
|
-
import type { ToolName } from "./tools
|
|
24
|
+
import type { ToolName } from "./tools";
|
|
25
25
|
|
|
26
26
|
interface GitContext {
|
|
27
27
|
isRepo: boolean;
|
|
File without changes
|
package/src/task/executor.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
|
9
9
|
import type { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
|
10
10
|
import { formatModelString, parseModelPattern } from "@oh-my-pi/pi-coding-agent/config/model-resolver";
|
|
11
11
|
import { checkPythonKernelAvailability } from "@oh-my-pi/pi-coding-agent/ipy/kernel";
|
|
12
|
-
import { LspTool } from "@oh-my-pi/pi-coding-agent/lsp
|
|
12
|
+
import { LspTool } from "@oh-my-pi/pi-coding-agent/lsp";
|
|
13
13
|
import type { LspParams } from "@oh-my-pi/pi-coding-agent/lsp/types";
|
|
14
14
|
import { callTool } from "@oh-my-pi/pi-coding-agent/mcp/client";
|
|
15
15
|
import type { MCPManager } from "@oh-my-pi/pi-coding-agent/mcp/manager";
|
package/src/task/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type { Usage } from "@oh-my-pi/pi-ai";
|
|
|
21
21
|
import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
|
|
22
22
|
import type { Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
23
23
|
import taskDescriptionTemplate from "@oh-my-pi/pi-coding-agent/prompts/tools/task.md" with { type: "text" };
|
|
24
|
+
import { AgentOutputManager } from "@oh-my-pi/pi-coding-agent/task/output-manager";
|
|
24
25
|
import { formatDuration } from "@oh-my-pi/pi-coding-agent/tools/render-utils";
|
|
25
26
|
import { $ } from "bun";
|
|
26
27
|
import { nanoid } from "nanoid";
|
|
@@ -101,6 +102,7 @@ function addUsageTotals(target: Usage, usage: Partial<Usage>): void {
|
|
|
101
102
|
export { loadBundledAgents as BUNDLED_AGENTS } from "./agents";
|
|
102
103
|
export { discoverCommands, expandCommand, getCommand } from "./commands";
|
|
103
104
|
export { discoverAgents, getAgent } from "./discovery";
|
|
105
|
+
export { AgentOutputManager } from "./output-manager";
|
|
104
106
|
export type { AgentDefinition, AgentProgress, SingleResult, TaskParams, TaskToolDetails } from "./types";
|
|
105
107
|
export { taskSchema } from "./types";
|
|
106
108
|
|
|
@@ -367,7 +369,14 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
367
369
|
}
|
|
368
370
|
|
|
369
371
|
// Build full prompts with context prepended
|
|
370
|
-
|
|
372
|
+
// Allocate unique IDs across the session to prevent artifact collisions
|
|
373
|
+
const outputManager =
|
|
374
|
+
this.session.agentOutputManager ?? new AgentOutputManager(this.session.getArtifactsDir ?? (() => null));
|
|
375
|
+
const uniqueIds = await outputManager.allocateBatch(tasks.map((t) => t.id));
|
|
376
|
+
const tasksWithUniqueIds = tasks.map((t, i) => ({ ...t, id: uniqueIds[i] }));
|
|
377
|
+
|
|
378
|
+
// Build full prompts with context prepended
|
|
379
|
+
const tasksWithContext = tasksWithUniqueIds.map((t) => renderTemplate(context, t));
|
|
371
380
|
|
|
372
381
|
// Initialize progress for all tasks
|
|
373
382
|
for (let i = 0; i < tasksWithContext.length; i++) {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped manager for agent output IDs.
|
|
3
|
+
*
|
|
4
|
+
* Ensures unique output IDs across task tool invocations within a session.
|
|
5
|
+
* Prefixes each ID with a sequential number (e.g., "0-AuthProvider", "1-AuthApi").
|
|
6
|
+
*
|
|
7
|
+
* This enables reliable agent:// URL resolution and prevents artifact collisions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readdir } from "node:fs/promises";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Manages agent output ID allocation to ensure uniqueness.
|
|
14
|
+
*
|
|
15
|
+
* Each allocated ID gets a numeric prefix based on allocation order.
|
|
16
|
+
* On resume, scans existing files to find the next available index.
|
|
17
|
+
*/
|
|
18
|
+
export class AgentOutputManager {
|
|
19
|
+
#nextId = 0;
|
|
20
|
+
#initialized = false;
|
|
21
|
+
readonly #getArtifactsDir: () => string | null;
|
|
22
|
+
|
|
23
|
+
constructor(getArtifactsDir: () => string | null) {
|
|
24
|
+
this.#getArtifactsDir = getArtifactsDir;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Scan existing agent output files to find the next available ID.
|
|
29
|
+
* This ensures we don't overwrite outputs when resuming a session.
|
|
30
|
+
*/
|
|
31
|
+
async #ensureInitialized(): Promise<void> {
|
|
32
|
+
if (this.#initialized) return;
|
|
33
|
+
this.#initialized = true;
|
|
34
|
+
|
|
35
|
+
const dir = this.#getArtifactsDir();
|
|
36
|
+
if (!dir) return;
|
|
37
|
+
|
|
38
|
+
let files: string[];
|
|
39
|
+
try {
|
|
40
|
+
files = await readdir(dir);
|
|
41
|
+
} catch {
|
|
42
|
+
return; // Directory doesn't exist yet
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let maxId = -1;
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
// Agent outputs are named: {index}-{id}.md (e.g., "0-AuthProvider.md")
|
|
48
|
+
const match = file.match(/^(\d+)-.*\.md$/);
|
|
49
|
+
if (match) {
|
|
50
|
+
const id = parseInt(match[1], 10);
|
|
51
|
+
if (id > maxId) maxId = id;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
this.#nextId = maxId + 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Allocate a unique ID with numeric prefix.
|
|
59
|
+
*
|
|
60
|
+
* @param id Requested ID (e.g., "AuthProvider")
|
|
61
|
+
* @returns Unique ID with prefix (e.g., "0-AuthProvider")
|
|
62
|
+
*/
|
|
63
|
+
async allocate(id: string): Promise<string> {
|
|
64
|
+
await this.#ensureInitialized();
|
|
65
|
+
return `${this.#nextId++}-${id}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Allocate unique IDs for a batch of tasks.
|
|
70
|
+
*
|
|
71
|
+
* @param ids Array of requested IDs
|
|
72
|
+
* @returns Array of unique IDs in same order
|
|
73
|
+
*/
|
|
74
|
+
async allocateBatch(ids: string[]): Promise<string[]> {
|
|
75
|
+
await this.#ensureInitialized();
|
|
76
|
+
return ids.map((id) => `${this.#nextId++}-${id}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the next ID that would be allocated (without allocating).
|
|
81
|
+
*/
|
|
82
|
+
async peekNextIndex(): Promise<number> {
|
|
83
|
+
await this.#ensureInitialized();
|
|
84
|
+
return this.#nextId;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reset state (primarily for testing).
|
|
89
|
+
*/
|
|
90
|
+
reset(): void {
|
|
91
|
+
this.#nextId = 0;
|
|
92
|
+
this.#initialized = false;
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/task/render.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type ReportFindingDetails,
|
|
24
24
|
type SubmitReviewDetails,
|
|
25
25
|
} from "@oh-my-pi/pi-coding-agent/tools/review";
|
|
26
|
+
import { renderStatusLine } from "@oh-my-pi/pi-coding-agent/tui";
|
|
26
27
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
27
28
|
import { Container, Text } from "@oh-my-pi/pi-tui";
|
|
28
29
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
@@ -229,7 +230,7 @@ function renderOutputSection(
|
|
|
229
230
|
maxExpanded = 10,
|
|
230
231
|
): string[] {
|
|
231
232
|
const lines: string[] = [];
|
|
232
|
-
const trimmedOutput = output.
|
|
233
|
+
const trimmedOutput = output.trimEnd();
|
|
233
234
|
if (!trimmedOutput) return lines;
|
|
234
235
|
|
|
235
236
|
if (trimmedOutput.startsWith("{") || trimmedOutput.startsWith("[")) {
|
|
@@ -261,7 +262,7 @@ function renderOutputSection(
|
|
|
261
262
|
|
|
262
263
|
lines.push(`${continuePrefix}${theme.fg("dim", "Output")}`);
|
|
263
264
|
|
|
264
|
-
const outputLines = output.split("\n")
|
|
265
|
+
const outputLines = output.trimEnd().split("\n");
|
|
265
266
|
const previewCount = expanded ? maxExpanded : maxCollapsed;
|
|
266
267
|
for (const line of outputLines.slice(0, previewCount)) {
|
|
267
268
|
lines.push(`${continuePrefix} ${theme.fg("dim", truncate(line, 70, theme.format.ellipsis))}`);
|
|
@@ -395,13 +396,8 @@ function renderArgsSection(
|
|
|
395
396
|
* Render the tool call arguments.
|
|
396
397
|
*/
|
|
397
398
|
export function renderCall(args: TaskParams, theme: Theme): Component {
|
|
398
|
-
const label = theme.fg("toolTitle", theme.bold("Task"));
|
|
399
|
-
const agentTag = theme.italic(
|
|
400
|
-
theme.fg("dim", `${theme.format.bracketLeft}${args.agent}${theme.format.bracketRight}`),
|
|
401
|
-
);
|
|
402
|
-
|
|
403
399
|
const lines: string[] = [];
|
|
404
|
-
lines.push(
|
|
400
|
+
lines.push(renderStatusLine({ icon: "pending", title: "Task", description: args.agent }, theme));
|
|
405
401
|
|
|
406
402
|
const contextTemplate = args.context ?? "";
|
|
407
403
|
const context = contextTemplate.trim();
|
|
@@ -734,13 +730,12 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
734
730
|
if (handler?.renderFinal && (dataArray as unknown[]).length > 0) {
|
|
735
731
|
hasCustomRendering = true;
|
|
736
732
|
const component = handler.renderFinal(dataArray as unknown[], theme, expanded);
|
|
733
|
+
lines.push(`${continuePrefix}${theme.fg("dim", `Tool: ${toolName}`)}`);
|
|
737
734
|
if (component instanceof Text) {
|
|
738
735
|
// Prefix each line with continuePrefix
|
|
739
736
|
const text = component.getText();
|
|
740
737
|
for (const line of text.split("\n")) {
|
|
741
|
-
|
|
742
|
-
lines.push(`${continuePrefix}${line}`);
|
|
743
|
-
}
|
|
738
|
+
lines.push(`${continuePrefix}${line}`);
|
|
744
739
|
}
|
|
745
740
|
} else if (component instanceof Container) {
|
|
746
741
|
// For containers, render each child
|
|
@@ -845,7 +840,7 @@ export function renderResult(
|
|
|
845
840
|
}
|
|
846
841
|
}
|
|
847
842
|
|
|
848
|
-
const indented = lines.map((line) => (line.
|
|
843
|
+
const indented = lines.map((line) => (line.length > 0 ? ` ${line}` : ""));
|
|
849
844
|
return new Text(indented.join("\n"), 0, 0);
|
|
850
845
|
}
|
|
851
846
|
|
package/src/task/worker.ts
CHANGED
|
@@ -552,7 +552,7 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
|
|
|
552
552
|
let modelRegistry: ModelRegistry;
|
|
553
553
|
|
|
554
554
|
if (payload.serializedAuth && payload.serializedModels) {
|
|
555
|
-
authStorage = AuthStorage.fromSerialized(payload.serializedAuth);
|
|
555
|
+
authStorage = await AuthStorage.fromSerialized(payload.serializedAuth);
|
|
556
556
|
modelRegistry = ModelRegistry.fromSerialized(payload.serializedModels, authStorage);
|
|
557
557
|
} else {
|
|
558
558
|
authStorage = await discoverAuthStorage();
|
package/src/tools/ask.ts
CHANGED
|
@@ -20,10 +20,11 @@ import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-te
|
|
|
20
20
|
import type { RenderResultOptions } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools/types";
|
|
21
21
|
import { type Theme, theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
22
22
|
import askDescription from "@oh-my-pi/pi-coding-agent/prompts/tools/ask.md" with { type: "text" };
|
|
23
|
+
import { renderStatusLine } from "@oh-my-pi/pi-coding-agent/tui";
|
|
23
24
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
24
25
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
25
26
|
import { Type } from "@sinclair/typebox";
|
|
26
|
-
import type { ToolSession } from "
|
|
27
|
+
import type { ToolSession } from ".";
|
|
27
28
|
import { ToolUIKit } from "./render-utils";
|
|
28
29
|
|
|
29
30
|
// =============================================================================
|
|
@@ -381,36 +382,56 @@ export const askToolRenderer = {
|
|
|
381
382
|
const { details } = result;
|
|
382
383
|
if (!details) {
|
|
383
384
|
const txt = result.content[0];
|
|
384
|
-
|
|
385
|
+
const fallback = txt?.type === "text" && txt.text ? txt.text : "";
|
|
386
|
+
const header = renderStatusLine({ icon: "warning", title: "Ask" }, uiTheme);
|
|
387
|
+
return new Text([header, uiTheme.fg("dim", fallback)].join("\n"), 0, 0);
|
|
385
388
|
}
|
|
386
389
|
|
|
387
390
|
// Multi-part results
|
|
388
391
|
if (details.results && details.results.length > 0) {
|
|
389
392
|
const lines: string[] = [];
|
|
390
|
-
|
|
391
|
-
|
|
393
|
+
const hasAnySelection = details.results.some(
|
|
394
|
+
(r) => r.customInput || (r.selectedOptions && r.selectedOptions.length > 0),
|
|
395
|
+
);
|
|
396
|
+
const header = renderStatusLine(
|
|
397
|
+
{
|
|
398
|
+
icon: hasAnySelection ? "success" : "warning",
|
|
399
|
+
title: "Ask",
|
|
400
|
+
meta: [`${details.results.length} questions`],
|
|
401
|
+
},
|
|
402
|
+
uiTheme,
|
|
403
|
+
);
|
|
404
|
+
lines.push(header);
|
|
405
|
+
|
|
406
|
+
for (let i = 0; i < details.results.length; i++) {
|
|
407
|
+
const r = details.results[i];
|
|
408
|
+
const isLastQuestion = i === details.results.length - 1;
|
|
409
|
+
const branch = isLastQuestion ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
410
|
+
const continuation = isLastQuestion ? " " : `${uiTheme.fg("dim", uiTheme.tree.vertical)} `;
|
|
392
411
|
const hasSelection = r.customInput || r.selectedOptions.length > 0;
|
|
393
412
|
const statusIcon = hasSelection
|
|
394
413
|
? uiTheme.styledSymbol("status.success", "success")
|
|
395
414
|
: uiTheme.styledSymbol("status.warning", "warning");
|
|
396
415
|
|
|
397
|
-
lines.push(
|
|
416
|
+
lines.push(
|
|
417
|
+
` ${uiTheme.fg("dim", branch)} ${statusIcon} ${uiTheme.fg("dim", `[${r.id}]`)} ${uiTheme.fg("accent", r.question)}`,
|
|
418
|
+
);
|
|
398
419
|
|
|
399
420
|
if (r.customInput) {
|
|
400
421
|
lines.push(
|
|
401
|
-
|
|
422
|
+
`${continuation}${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", r.customInput)}`,
|
|
402
423
|
);
|
|
403
424
|
} else if (r.selectedOptions.length > 0) {
|
|
404
425
|
for (let j = 0; j < r.selectedOptions.length; j++) {
|
|
405
426
|
const isLast = j === r.selectedOptions.length - 1;
|
|
406
|
-
const
|
|
427
|
+
const optBranch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
407
428
|
lines.push(
|
|
408
|
-
|
|
429
|
+
`${continuation}${uiTheme.fg("dim", optBranch)} ${uiTheme.fg("success", uiTheme.checkbox.checked)} ${uiTheme.fg("toolOutput", r.selectedOptions[j])}`,
|
|
409
430
|
);
|
|
410
431
|
}
|
|
411
432
|
} else {
|
|
412
433
|
lines.push(
|
|
413
|
-
|
|
434
|
+
`${continuation}${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.warning", "warning")} ${uiTheme.fg("warning", "Cancelled")}`,
|
|
414
435
|
);
|
|
415
436
|
}
|
|
416
437
|
}
|
|
@@ -425,11 +446,12 @@ export const askToolRenderer = {
|
|
|
425
446
|
}
|
|
426
447
|
|
|
427
448
|
const hasSelection = details.customInput || (details.selectedOptions && details.selectedOptions.length > 0);
|
|
428
|
-
const
|
|
429
|
-
?
|
|
430
|
-
|
|
449
|
+
const header = renderStatusLine(
|
|
450
|
+
{ icon: hasSelection ? "success" : "warning", title: "Ask", description: details.question },
|
|
451
|
+
uiTheme,
|
|
452
|
+
);
|
|
431
453
|
|
|
432
|
-
let text =
|
|
454
|
+
let text = header;
|
|
433
455
|
|
|
434
456
|
if (details.customInput) {
|
|
435
457
|
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", details.customInput)}`;
|