@oh-my-pi/pi-coding-agent 11.8.2 → 11.8.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.
- package/docs/tui.md +9 -9
- package/package.json +7 -7
- package/src/cli/file-processor.ts +8 -13
- package/src/cli/oclif-help.ts +1 -1
- package/src/cli.ts +14 -0
- package/src/commit/git/index.ts +16 -16
- package/src/config/keybindings.ts +11 -11
- package/src/config/model-registry.ts +31 -66
- package/src/config/settings.ts +88 -95
- package/src/config.ts +2 -2
- package/src/cursor.ts +4 -4
- package/src/debug/index.ts +28 -28
- package/src/discovery/codex.ts +5 -13
- package/src/discovery/cursor.ts +2 -7
- package/src/exa/mcp-client.ts +2 -2
- package/src/exa/websets.ts +2 -2
- package/src/export/html/index.ts +3 -3
- package/src/export/ttsr.ts +27 -27
- package/src/extensibility/custom-tools/loader.ts +9 -9
- package/src/extensibility/extensions/runner.ts +64 -64
- package/src/extensibility/hooks/runner.ts +46 -46
- package/src/extensibility/plugins/manager.ts +49 -49
- package/src/index.ts +0 -1
- package/src/internal-urls/router.ts +5 -5
- package/src/ipy/kernel.ts +61 -57
- package/src/lsp/client.ts +1 -1
- package/src/lsp/clients/biome-client.ts +2 -2
- package/src/lsp/clients/lsp-linter-client.ts +7 -7
- package/src/lsp/index.ts +9 -9
- package/src/mcp/manager.ts +47 -47
- package/src/mcp/tool-bridge.ts +12 -12
- package/src/mcp/transports/http.ts +34 -34
- package/src/mcp/transports/stdio.ts +47 -47
- package/src/modes/components/assistant-message.ts +25 -25
- package/src/modes/components/bash-execution.ts +51 -51
- package/src/modes/components/bordered-loader.ts +7 -7
- package/src/modes/components/branch-summary-message.ts +7 -7
- package/src/modes/components/compaction-summary-message.ts +7 -7
- package/src/modes/components/countdown-timer.ts +15 -15
- package/src/modes/components/custom-editor.ts +22 -22
- package/src/modes/components/custom-message.ts +21 -21
- package/src/modes/components/dynamic-border.ts +3 -3
- package/src/modes/components/extensions/extension-dashboard.ts +72 -72
- package/src/modes/components/extensions/extension-list.ts +99 -97
- package/src/modes/components/extensions/inspector-panel.ts +26 -26
- package/src/modes/components/footer.ts +36 -36
- package/src/modes/components/history-search.ts +52 -52
- package/src/modes/components/hook-editor.ts +20 -20
- package/src/modes/components/hook-input.ts +20 -20
- package/src/modes/components/hook-message.ts +22 -22
- package/src/modes/components/hook-selector.ts +52 -52
- package/src/modes/components/index.ts +0 -1
- package/src/modes/components/login-dialog.ts +57 -57
- package/src/modes/components/model-selector.ts +173 -173
- package/src/modes/components/oauth-selector.ts +45 -45
- package/src/modes/components/plugin-settings.ts +52 -52
- package/src/modes/components/python-execution.ts +53 -53
- package/src/modes/components/queue-mode-selector.ts +7 -7
- package/src/modes/components/read-tool-group.ts +23 -23
- package/src/modes/components/session-selector.ts +40 -37
- package/src/modes/components/settings-selector.ts +80 -80
- package/src/modes/components/show-images-selector.ts +7 -7
- package/src/modes/components/skill-message.ts +27 -27
- package/src/modes/components/status-line-segment-editor.ts +81 -81
- package/src/modes/components/status-line.ts +73 -73
- package/src/modes/components/theme-selector.ts +11 -11
- package/src/modes/components/thinking-selector.ts +7 -7
- package/src/modes/components/todo-display.ts +19 -19
- package/src/modes/components/todo-reminder.ts +9 -9
- package/src/modes/components/tool-execution.ts +204 -196
- package/src/modes/components/tree-selector.ts +144 -144
- package/src/modes/components/ttsr-notification.ts +17 -17
- package/src/modes/components/user-message-selector.ts +18 -18
- package/src/modes/components/welcome.ts +10 -10
- package/src/modes/controllers/command-controller.ts +0 -7
- package/src/modes/controllers/event-controller.ts +23 -23
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +4 -9
- package/src/modes/interactive-mode.ts +234 -241
- package/src/modes/rpc/rpc-client.ts +77 -77
- package/src/modes/rpc/rpc-mode.ts +5 -5
- package/src/modes/theme/theme.ts +113 -113
- package/src/modes/types.ts +0 -1
- package/src/patch/index.ts +45 -45
- package/src/prompts/tools/task.md +22 -2
- package/src/session/agent-session.ts +463 -476
- package/src/session/agent-storage.ts +72 -75
- package/src/session/auth-storage.ts +186 -252
- package/src/session/history-storage.ts +36 -38
- package/src/session/session-manager.ts +300 -299
- package/src/session/session-storage.ts +65 -90
- package/src/ssh/connection-manager.ts +9 -9
- package/src/task/agents.ts +1 -1
- package/src/task/executor.ts +2 -2
- package/src/task/index.ts +13 -12
- package/src/task/subprocess-tool-registry.ts +5 -5
- package/src/tools/ask.ts +7 -7
- package/src/tools/bash.ts +8 -7
- package/src/tools/browser.ts +123 -123
- package/src/tools/calculator.ts +46 -46
- package/src/tools/context.ts +9 -9
- package/src/tools/exit-plan-mode.ts +5 -5
- package/src/tools/fetch.ts +5 -5
- package/src/tools/find.ts +16 -16
- package/src/tools/grep.ts +10 -10
- package/src/tools/notebook.ts +6 -6
- package/src/tools/output-meta.ts +10 -2
- package/src/tools/python.ts +12 -11
- package/src/tools/read.ts +17 -17
- package/src/tools/ssh.ts +9 -9
- package/src/tools/submit-result.ts +13 -13
- package/src/tools/todo-write.ts +6 -6
- package/src/tools/write.ts +10 -10
- package/src/tui/output-block.ts +6 -6
- package/src/tui/utils.ts +9 -9
- package/src/utils/event-bus.ts +10 -10
- package/src/utils/frontmatter.ts +1 -1
- package/src/utils/ignore-files.ts +1 -1
- package/src/web/search/index.ts +5 -5
- package/src/web/search/providers/anthropic.ts +7 -2
- package/examples/hooks/snake.ts +0 -342
- package/src/modes/components/armin.ts +0 -379
package/src/config/settings.ts
CHANGED
|
@@ -120,39 +120,39 @@ function setByPath(obj: RawSettings, segments: string[], value: unknown): void {
|
|
|
120
120
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
121
121
|
|
|
122
122
|
export class Settings {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
#configPath: string | null;
|
|
124
|
+
#cwd: string;
|
|
125
|
+
#agentDir: string;
|
|
126
|
+
#storage: AgentStorage | null = null;
|
|
127
127
|
|
|
128
128
|
/** Global settings from config.yml */
|
|
129
|
-
|
|
129
|
+
#global: RawSettings = {};
|
|
130
130
|
/** Project settings from .claude/settings.yml etc */
|
|
131
|
-
|
|
131
|
+
#project: RawSettings = {};
|
|
132
132
|
/** Runtime overrides (not persisted) */
|
|
133
|
-
|
|
133
|
+
#overrides: RawSettings = {};
|
|
134
134
|
/** Merged view (global + project + overrides) */
|
|
135
|
-
|
|
135
|
+
#merged: RawSettings = {};
|
|
136
136
|
|
|
137
137
|
/** Paths modified during this session (for partial save) */
|
|
138
|
-
|
|
138
|
+
#modified = new Set<string>();
|
|
139
139
|
|
|
140
140
|
/** Pending save (debounced) */
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
#saveTimer?: NodeJS.Timeout;
|
|
142
|
+
#savePromise?: Promise<void>;
|
|
143
143
|
|
|
144
144
|
/** Whether to persist changes */
|
|
145
|
-
|
|
145
|
+
#persist: boolean;
|
|
146
146
|
|
|
147
147
|
private constructor(options: SettingsOptions = {}) {
|
|
148
|
-
this
|
|
149
|
-
this
|
|
150
|
-
this
|
|
151
|
-
this
|
|
148
|
+
this.#cwd = path.normalize(options.cwd ?? process.cwd());
|
|
149
|
+
this.#agentDir = path.normalize(options.agentDir ?? getAgentDir());
|
|
150
|
+
this.#configPath = options.inMemory ? null : path.join(this.#agentDir, "config.yml");
|
|
151
|
+
this.#persist = !options.inMemory;
|
|
152
152
|
|
|
153
153
|
if (options.overrides) {
|
|
154
154
|
for (const [key, value] of Object.entries(options.overrides)) {
|
|
155
|
-
setByPath(this
|
|
155
|
+
setByPath(this.#overrides, parsePath(key), value);
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
}
|
|
@@ -169,7 +169,7 @@ export class Settings {
|
|
|
169
169
|
if (globalInstancePromise) return globalInstancePromise;
|
|
170
170
|
|
|
171
171
|
const instance = new Settings(options);
|
|
172
|
-
const promise = instance
|
|
172
|
+
const promise = instance.#load();
|
|
173
173
|
globalInstancePromise = promise;
|
|
174
174
|
|
|
175
175
|
return promise.then(
|
|
@@ -191,7 +191,7 @@ export class Settings {
|
|
|
191
191
|
*/
|
|
192
192
|
static isolated(overrides: Partial<Record<SettingPath, unknown>> = {}): Settings {
|
|
193
193
|
const instance = new Settings({ inMemory: true, overrides });
|
|
194
|
-
instance
|
|
194
|
+
instance.#rebuildMerged();
|
|
195
195
|
return instance;
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -216,7 +216,7 @@ export class Settings {
|
|
|
216
216
|
*/
|
|
217
217
|
get<P extends SettingPath>(path: P): SettingValue<P> {
|
|
218
218
|
const segments = parsePath(path);
|
|
219
|
-
const value = getByPath(this
|
|
219
|
+
const value = getByPath(this.#merged, segments);
|
|
220
220
|
if (value !== undefined) {
|
|
221
221
|
return value as SettingValue<P>;
|
|
222
222
|
}
|
|
@@ -231,10 +231,10 @@ export class Settings {
|
|
|
231
231
|
set<P extends SettingPath>(path: P, value: SettingValue<P>): void {
|
|
232
232
|
const prev = this.get(path);
|
|
233
233
|
const segments = parsePath(path);
|
|
234
|
-
setByPath(this
|
|
235
|
-
this
|
|
236
|
-
this
|
|
237
|
-
this
|
|
234
|
+
setByPath(this.#global, segments, value);
|
|
235
|
+
this.#modified.add(path);
|
|
236
|
+
this.#rebuildMerged();
|
|
237
|
+
this.#queueSave();
|
|
238
238
|
|
|
239
239
|
// Trigger hook if exists
|
|
240
240
|
const hook = SETTING_HOOKS[path];
|
|
@@ -248,8 +248,8 @@ export class Settings {
|
|
|
248
248
|
*/
|
|
249
249
|
override<P extends SettingPath>(path: P, value: SettingValue<P>): void {
|
|
250
250
|
const segments = parsePath(path);
|
|
251
|
-
setByPath(this
|
|
252
|
-
this
|
|
251
|
+
setByPath(this.#overrides, segments, value);
|
|
252
|
+
this.#rebuildMerged();
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
/**
|
|
@@ -257,14 +257,14 @@ export class Settings {
|
|
|
257
257
|
*/
|
|
258
258
|
clearOverride(path: SettingPath): void {
|
|
259
259
|
const segments = parsePath(path);
|
|
260
|
-
let current = this
|
|
260
|
+
let current = this.#overrides;
|
|
261
261
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
262
262
|
const segment = segments[i];
|
|
263
263
|
if (!(segment in current)) return;
|
|
264
264
|
current = current[segment] as RawSettings;
|
|
265
265
|
}
|
|
266
266
|
delete current[segments[segments.length - 1]];
|
|
267
|
-
this
|
|
267
|
+
this.#rebuildMerged();
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
/**
|
|
@@ -272,15 +272,15 @@ export class Settings {
|
|
|
272
272
|
* Call before exit to ensure all changes are persisted.
|
|
273
273
|
*/
|
|
274
274
|
async flush(): Promise<void> {
|
|
275
|
-
if (this
|
|
276
|
-
clearTimeout(this
|
|
277
|
-
this
|
|
275
|
+
if (this.#saveTimer) {
|
|
276
|
+
clearTimeout(this.#saveTimer);
|
|
277
|
+
this.#saveTimer = undefined;
|
|
278
278
|
}
|
|
279
|
-
if (this
|
|
280
|
-
await this
|
|
279
|
+
if (this.#savePromise) {
|
|
280
|
+
await this.#savePromise;
|
|
281
281
|
}
|
|
282
|
-
if (this
|
|
283
|
-
await this
|
|
282
|
+
if (this.#modified.size > 0) {
|
|
283
|
+
await this.#saveNow();
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
|
|
@@ -289,19 +289,19 @@ export class Settings {
|
|
|
289
289
|
// ─────────────────────────────────────────────────────────────────────────
|
|
290
290
|
|
|
291
291
|
getStorage(): AgentStorage | null {
|
|
292
|
-
return this
|
|
292
|
+
return this.#storage;
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
getCwd(): string {
|
|
296
|
-
return this
|
|
296
|
+
return this.#cwd;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
getAgentDir(): string {
|
|
300
|
-
return this
|
|
300
|
+
return this.#agentDir;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
getPlansDirectory(): string {
|
|
304
|
-
return path.join(this
|
|
304
|
+
return path.join(this.#agentDir, "plans");
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
/**
|
|
@@ -312,13 +312,6 @@ export class Settings {
|
|
|
312
312
|
return procmgr.getShellConfig(shell);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
/**
|
|
316
|
-
* Serialize current settings for passing to subagent workers.
|
|
317
|
-
*/
|
|
318
|
-
serialize(): RawSettings {
|
|
319
|
-
return structuredClone(this.merged);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
315
|
/**
|
|
323
316
|
* Get all settings in a group with full type safety.
|
|
324
317
|
*/
|
|
@@ -337,7 +330,7 @@ export class Settings {
|
|
|
337
330
|
* Get edit model variants (typed accessor for complex nested config).
|
|
338
331
|
*/
|
|
339
332
|
getEditModelVariants(): Record<string, "patch" | "replace"> {
|
|
340
|
-
const variants = (this
|
|
333
|
+
const variants = (this.#merged.edit as { modelVariants?: Record<string, string> })?.modelVariants ?? {};
|
|
341
334
|
const result: Record<string, "patch" | "replace"> = {};
|
|
342
335
|
for (const pattern in variants) {
|
|
343
336
|
const value = variants[pattern];
|
|
@@ -359,7 +352,7 @@ export class Settings {
|
|
|
359
352
|
*/
|
|
360
353
|
getEditVariantForModel(model: string | undefined): "patch" | "replace" | null {
|
|
361
354
|
if (!model) return null;
|
|
362
|
-
const variants = (this
|
|
355
|
+
const variants = (this.#merged.edit as { modelVariants?: Record<string, string> })?.modelVariants;
|
|
363
356
|
if (!variants) return null;
|
|
364
357
|
const modelLower = model.toLowerCase();
|
|
365
358
|
for (const pattern in variants) {
|
|
@@ -379,7 +372,7 @@ export class Settings {
|
|
|
379
372
|
* Get bash interceptor rules (typed accessor for complex array config).
|
|
380
373
|
*/
|
|
381
374
|
getBashInterceptorRules(): BashInterceptorRule[] {
|
|
382
|
-
const patterns = (this
|
|
375
|
+
const patterns = (this.#merged.bashInterceptor as { patterns?: unknown[] })?.patterns;
|
|
383
376
|
if (!Array.isArray(patterns)) return [];
|
|
384
377
|
|
|
385
378
|
return patterns.filter((p): p is BashInterceptorRule => typeof p === "object" && p !== null && "pattern" in p);
|
|
@@ -432,34 +425,34 @@ export class Settings {
|
|
|
432
425
|
// Loading
|
|
433
426
|
// ─────────────────────────────────────────────────────────────────────────
|
|
434
427
|
|
|
435
|
-
|
|
436
|
-
if (this
|
|
428
|
+
async #load(): Promise<Settings> {
|
|
429
|
+
if (this.#persist) {
|
|
437
430
|
// Open storage
|
|
438
|
-
this
|
|
431
|
+
this.#storage = await AgentStorage.open(getAgentDbPath(this.#agentDir));
|
|
439
432
|
|
|
440
433
|
// Migrate from legacy formats if needed
|
|
441
|
-
await this
|
|
434
|
+
await this.#migrateFromLegacy();
|
|
442
435
|
|
|
443
436
|
// Load global settings from config.yml
|
|
444
|
-
this
|
|
437
|
+
this.#global = await this.#loadYaml(this.#configPath!);
|
|
445
438
|
}
|
|
446
439
|
|
|
447
440
|
// Load project settings
|
|
448
|
-
this
|
|
441
|
+
this.#project = await this.#loadProjectSettings();
|
|
449
442
|
|
|
450
443
|
// Build merged view
|
|
451
|
-
this
|
|
444
|
+
this.#rebuildMerged();
|
|
452
445
|
return this;
|
|
453
446
|
}
|
|
454
447
|
|
|
455
|
-
|
|
448
|
+
async #loadYaml(filePath: string): Promise<RawSettings> {
|
|
456
449
|
try {
|
|
457
450
|
const content = await Bun.file(filePath).text();
|
|
458
451
|
const parsed = YAML.parse(content);
|
|
459
452
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
460
453
|
return {};
|
|
461
454
|
}
|
|
462
|
-
return this
|
|
455
|
+
return this.#migrateRawSettings(parsed as RawSettings);
|
|
463
456
|
} catch (error) {
|
|
464
457
|
if (isEnoent(error)) return {};
|
|
465
458
|
logger.warn("Settings: failed to load", { path: filePath, error: String(error) });
|
|
@@ -467,27 +460,27 @@ export class Settings {
|
|
|
467
460
|
}
|
|
468
461
|
}
|
|
469
462
|
|
|
470
|
-
|
|
463
|
+
async #loadProjectSettings(): Promise<RawSettings> {
|
|
471
464
|
try {
|
|
472
|
-
const result = await loadCapability(settingsCapability.id, { cwd: this
|
|
465
|
+
const result = await loadCapability(settingsCapability.id, { cwd: this.#cwd });
|
|
473
466
|
let merged: RawSettings = {};
|
|
474
467
|
for (const item of result.items as SettingsCapabilityItem[]) {
|
|
475
468
|
if (item.level === "project") {
|
|
476
|
-
merged = this
|
|
469
|
+
merged = this.#deepMerge(merged, item.data as RawSettings);
|
|
477
470
|
}
|
|
478
471
|
}
|
|
479
|
-
return this
|
|
472
|
+
return this.#migrateRawSettings(merged);
|
|
480
473
|
} catch {
|
|
481
474
|
return {};
|
|
482
475
|
}
|
|
483
476
|
}
|
|
484
477
|
|
|
485
|
-
|
|
486
|
-
if (!this
|
|
478
|
+
async #migrateFromLegacy(): Promise<void> {
|
|
479
|
+
if (!this.#configPath) return;
|
|
487
480
|
|
|
488
481
|
// Check if config.yml already exists
|
|
489
482
|
try {
|
|
490
|
-
await Bun.file(this
|
|
483
|
+
await Bun.file(this.#configPath).text();
|
|
491
484
|
return; // Already exists, no migration needed
|
|
492
485
|
} catch (err) {
|
|
493
486
|
if (!isEnoent(err)) return;
|
|
@@ -497,11 +490,11 @@ export class Settings {
|
|
|
497
490
|
let migrated = false;
|
|
498
491
|
|
|
499
492
|
// 1. Migrate from settings.json
|
|
500
|
-
const settingsJsonPath = path.join(this
|
|
493
|
+
const settingsJsonPath = path.join(this.#agentDir, "settings.json");
|
|
501
494
|
try {
|
|
502
495
|
const parsed = JSON.parse(await Bun.file(settingsJsonPath).text());
|
|
503
496
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
504
|
-
settings = this
|
|
497
|
+
settings = this.#deepMerge(settings, this.#migrateRawSettings(parsed));
|
|
505
498
|
migrated = true;
|
|
506
499
|
try {
|
|
507
500
|
fs.renameSync(settingsJsonPath, `${settingsJsonPath}.bak`);
|
|
@@ -511,9 +504,9 @@ export class Settings {
|
|
|
511
504
|
|
|
512
505
|
// 2. Migrate from agent.db
|
|
513
506
|
try {
|
|
514
|
-
const dbSettings = this
|
|
507
|
+
const dbSettings = this.#storage?.getSettings();
|
|
515
508
|
if (dbSettings) {
|
|
516
|
-
settings = this
|
|
509
|
+
settings = this.#deepMerge(settings, this.#migrateRawSettings(dbSettings as RawSettings));
|
|
517
510
|
migrated = true;
|
|
518
511
|
}
|
|
519
512
|
} catch {}
|
|
@@ -521,14 +514,14 @@ export class Settings {
|
|
|
521
514
|
// 3. Write merged settings
|
|
522
515
|
if (migrated && Object.keys(settings).length > 0) {
|
|
523
516
|
try {
|
|
524
|
-
await Bun.write(this
|
|
525
|
-
logger.debug("Settings: migrated to config.yml", { path: this
|
|
517
|
+
await Bun.write(this.#configPath, YAML.stringify(settings, null, 2));
|
|
518
|
+
logger.debug("Settings: migrated to config.yml", { path: this.#configPath });
|
|
526
519
|
} catch {}
|
|
527
520
|
}
|
|
528
521
|
}
|
|
529
522
|
|
|
530
523
|
/** Apply schema migrations to raw settings */
|
|
531
|
-
|
|
524
|
+
#migrateRawSettings(raw: RawSettings): RawSettings {
|
|
532
525
|
// queueMode -> steeringMode
|
|
533
526
|
if ("queueMode" in raw && !("steeringMode" in raw)) {
|
|
534
527
|
raw.steeringMode = raw.queueMode;
|
|
@@ -550,65 +543,65 @@ export class Settings {
|
|
|
550
543
|
// Saving
|
|
551
544
|
// ─────────────────────────────────────────────────────────────────────────
|
|
552
545
|
|
|
553
|
-
|
|
554
|
-
if (!this
|
|
546
|
+
#queueSave(): void {
|
|
547
|
+
if (!this.#persist || !this.#configPath) return;
|
|
555
548
|
|
|
556
549
|
// Debounce: wait 100ms for more changes
|
|
557
|
-
if (this
|
|
558
|
-
clearTimeout(this
|
|
550
|
+
if (this.#saveTimer) {
|
|
551
|
+
clearTimeout(this.#saveTimer);
|
|
559
552
|
}
|
|
560
|
-
this
|
|
561
|
-
this
|
|
562
|
-
this
|
|
553
|
+
this.#saveTimer = setTimeout(() => {
|
|
554
|
+
this.#saveTimer = undefined;
|
|
555
|
+
this.#saveNow().catch(err => {
|
|
563
556
|
logger.warn("Settings: background save failed", { error: String(err) });
|
|
564
557
|
});
|
|
565
558
|
}, 100);
|
|
566
559
|
}
|
|
567
560
|
|
|
568
|
-
|
|
569
|
-
if (!this
|
|
561
|
+
async #saveNow(): Promise<void> {
|
|
562
|
+
if (!this.#persist || !this.#configPath || this.#modified.size === 0) return;
|
|
570
563
|
|
|
571
|
-
const configPath = this
|
|
572
|
-
const modifiedPaths = [...this
|
|
573
|
-
this
|
|
564
|
+
const configPath = this.#configPath;
|
|
565
|
+
const modifiedPaths = [...this.#modified];
|
|
566
|
+
this.#modified.clear();
|
|
574
567
|
|
|
575
568
|
try {
|
|
576
569
|
await withFileLock(configPath, async () => {
|
|
577
570
|
// Re-read to preserve external changes
|
|
578
|
-
const current = await this
|
|
571
|
+
const current = await this.#loadYaml(configPath);
|
|
579
572
|
|
|
580
573
|
// Apply only our modified paths
|
|
581
574
|
for (const modPath of modifiedPaths) {
|
|
582
575
|
const segments = parsePath(modPath);
|
|
583
|
-
const value = getByPath(this
|
|
576
|
+
const value = getByPath(this.#global, segments);
|
|
584
577
|
setByPath(current, segments, value);
|
|
585
578
|
}
|
|
586
579
|
|
|
587
580
|
// Update our global with any external changes we preserved
|
|
588
|
-
this
|
|
589
|
-
await Bun.write(configPath, YAML.stringify(this
|
|
581
|
+
this.#global = current;
|
|
582
|
+
await Bun.write(configPath, YAML.stringify(this.#global, null, 2));
|
|
590
583
|
});
|
|
591
584
|
} catch (error) {
|
|
592
585
|
logger.warn("Settings: save failed", { error: String(error) });
|
|
593
586
|
// Re-add failed paths for retry
|
|
594
587
|
for (const p of modifiedPaths) {
|
|
595
|
-
this
|
|
588
|
+
this.#modified.add(p);
|
|
596
589
|
}
|
|
597
590
|
}
|
|
598
591
|
|
|
599
|
-
this
|
|
592
|
+
this.#rebuildMerged();
|
|
600
593
|
}
|
|
601
594
|
|
|
602
595
|
// ─────────────────────────────────────────────────────────────────────────
|
|
603
596
|
// Utilities
|
|
604
597
|
// ─────────────────────────────────────────────────────────────────────────
|
|
605
598
|
|
|
606
|
-
|
|
607
|
-
this
|
|
608
|
-
this
|
|
599
|
+
#rebuildMerged(): void {
|
|
600
|
+
this.#merged = this.#deepMerge(this.#deepMerge({}, this.#global), this.#project);
|
|
601
|
+
this.#merged = this.#deepMerge(this.#merged, this.#overrides);
|
|
609
602
|
}
|
|
610
603
|
|
|
611
|
-
|
|
604
|
+
#deepMerge(base: RawSettings, overrides: RawSettings): RawSettings {
|
|
612
605
|
const result = { ...base };
|
|
613
606
|
for (const key of Object.keys(overrides)) {
|
|
614
607
|
const override = overrides[key];
|
|
@@ -624,7 +617,7 @@ export class Settings {
|
|
|
624
617
|
baseVal !== null &&
|
|
625
618
|
!Array.isArray(baseVal)
|
|
626
619
|
) {
|
|
627
|
-
result[key] = this
|
|
620
|
+
result[key] = this.#deepMerge(baseVal as RawSettings, override as RawSettings);
|
|
628
621
|
} else {
|
|
629
622
|
result[key] = override;
|
|
630
623
|
}
|
package/src/config.ts
CHANGED
|
@@ -156,8 +156,8 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
156
156
|
#auxValidate?: (value: T) => void;
|
|
157
157
|
|
|
158
158
|
constructor(
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
readonly id: string,
|
|
160
|
+
readonly schema: TSchema,
|
|
161
161
|
configPath: string = path.join(getAgentDir(), `${id}.yml`),
|
|
162
162
|
) {
|
|
163
163
|
this.#basePath = configPath;
|
package/src/cursor.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import * as fs from "node:fs
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
3
|
import type {
|
|
4
4
|
AgentEvent,
|
|
5
5
|
AgentTool,
|
|
@@ -98,9 +98,9 @@ async function executeDelete(options: CursorExecBridgeOptions, pathArg: string,
|
|
|
98
98
|
let result: AgentToolResult<unknown>;
|
|
99
99
|
|
|
100
100
|
try {
|
|
101
|
-
let fileStat:
|
|
101
|
+
let fileStat: fs.Stats | undefined;
|
|
102
102
|
try {
|
|
103
|
-
fileStat =
|
|
103
|
+
fileStat = fs.statSync(absolutePath);
|
|
104
104
|
} catch {
|
|
105
105
|
throw new Error(`File not found: ${pathArg}`);
|
|
106
106
|
}
|
|
@@ -108,7 +108,7 @@ async function executeDelete(options: CursorExecBridgeOptions, pathArg: string,
|
|
|
108
108
|
throw new Error(`Path is not a file: ${pathArg}`);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
fs.rmSync(absolutePath);
|
|
112
112
|
|
|
113
113
|
const sizeText = fileStat.size ? ` (${fileStat.size} bytes)` : "";
|
|
114
114
|
const message = `Deleted ${pathArg}${sizeText}`;
|
package/src/debug/index.ts
CHANGED
|
@@ -30,7 +30,7 @@ const DEBUG_MENU_ITEMS: SelectItem[] = [
|
|
|
30
30
|
* Debug selector component.
|
|
31
31
|
*/
|
|
32
32
|
export class DebugSelectorComponent extends Container {
|
|
33
|
-
|
|
33
|
+
#selectList: SelectList;
|
|
34
34
|
|
|
35
35
|
constructor(
|
|
36
36
|
private ctx: InteractiveModeContext,
|
|
@@ -44,55 +44,55 @@ export class DebugSelectorComponent extends Container {
|
|
|
44
44
|
this.addChild(new Spacer(1));
|
|
45
45
|
|
|
46
46
|
// Select list
|
|
47
|
-
this
|
|
47
|
+
this.#selectList = new SelectList(DEBUG_MENU_ITEMS, 7, getSelectListTheme());
|
|
48
48
|
|
|
49
|
-
this
|
|
49
|
+
this.#selectList.onSelect = item => {
|
|
50
50
|
onDone();
|
|
51
|
-
void this
|
|
51
|
+
void this.#handleSelection(item.value);
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
this
|
|
54
|
+
this.#selectList.onCancel = () => {
|
|
55
55
|
onDone();
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
this.addChild(this
|
|
58
|
+
this.addChild(this.#selectList);
|
|
59
59
|
this.addChild(new DynamicBorder());
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
handleInput(keyData: string): void {
|
|
63
|
-
this
|
|
63
|
+
this.#selectList.handleInput(keyData);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
async #handleSelection(value: string): Promise<void> {
|
|
67
67
|
switch (value) {
|
|
68
68
|
case "open-artifacts":
|
|
69
|
-
await this
|
|
69
|
+
await this.#handleOpenArtifacts();
|
|
70
70
|
break;
|
|
71
71
|
case "performance":
|
|
72
|
-
await this
|
|
72
|
+
await this.#handlePerformanceReport();
|
|
73
73
|
break;
|
|
74
74
|
case "work":
|
|
75
|
-
await this
|
|
75
|
+
await this.#handleWorkReport();
|
|
76
76
|
break;
|
|
77
77
|
case "dump":
|
|
78
|
-
await this
|
|
78
|
+
await this.#handleDumpReport();
|
|
79
79
|
break;
|
|
80
80
|
case "memory":
|
|
81
|
-
await this
|
|
81
|
+
await this.#handleMemoryReport();
|
|
82
82
|
break;
|
|
83
83
|
case "logs":
|
|
84
|
-
await this
|
|
84
|
+
await this.#handleViewLogs();
|
|
85
85
|
break;
|
|
86
86
|
case "system":
|
|
87
|
-
await this
|
|
87
|
+
await this.#handleViewSystemInfo();
|
|
88
88
|
break;
|
|
89
89
|
case "clear-cache":
|
|
90
|
-
await this
|
|
90
|
+
await this.#handleClearCache();
|
|
91
91
|
break;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
async #handlePerformanceReport(): Promise<void> {
|
|
96
96
|
// Start profiling
|
|
97
97
|
let session: ProfilerSession;
|
|
98
98
|
try {
|
|
@@ -146,7 +146,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
146
146
|
const workProfile = getWorkProfile(30);
|
|
147
147
|
const result = await createReportBundle({
|
|
148
148
|
sessionFile: this.ctx.sessionManager.getSessionFile(),
|
|
149
|
-
settings: this
|
|
149
|
+
settings: this.#getResolvedSettings(),
|
|
150
150
|
cpuProfile,
|
|
151
151
|
workProfile,
|
|
152
152
|
});
|
|
@@ -169,7 +169,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
169
169
|
this.ctx.ui.requestRender();
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
async #handleWorkReport(): Promise<void> {
|
|
173
173
|
try {
|
|
174
174
|
const workProfile = getWorkProfile(30);
|
|
175
175
|
|
|
@@ -202,7 +202,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
202
202
|
this.ctx.ui.requestRender();
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
async #handleDumpReport(): Promise<void> {
|
|
206
206
|
const loader = new Loader(
|
|
207
207
|
this.ctx.ui,
|
|
208
208
|
spinner => theme.fg("accent", spinner),
|
|
@@ -216,7 +216,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
216
216
|
try {
|
|
217
217
|
const result = await createReportBundle({
|
|
218
218
|
sessionFile: this.ctx.sessionManager.getSessionFile(),
|
|
219
|
-
settings: this
|
|
219
|
+
settings: this.#getResolvedSettings(),
|
|
220
220
|
});
|
|
221
221
|
|
|
222
222
|
loader.stop();
|
|
@@ -237,7 +237,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
237
237
|
this.ctx.ui.requestRender();
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
|
|
240
|
+
async #handleMemoryReport(): Promise<void> {
|
|
241
241
|
const loader = new Loader(
|
|
242
242
|
this.ctx.ui,
|
|
243
243
|
spinner => theme.fg("accent", spinner),
|
|
@@ -254,7 +254,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
254
254
|
|
|
255
255
|
const result = await createReportBundle({
|
|
256
256
|
sessionFile: this.ctx.sessionManager.getSessionFile(),
|
|
257
|
-
settings: this
|
|
257
|
+
settings: this.#getResolvedSettings(),
|
|
258
258
|
heapSnapshot,
|
|
259
259
|
});
|
|
260
260
|
|
|
@@ -276,7 +276,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
276
276
|
this.ctx.ui.requestRender();
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
|
|
279
|
+
async #handleViewLogs(): Promise<void> {
|
|
280
280
|
try {
|
|
281
281
|
const logs = await getRecentLogs(50);
|
|
282
282
|
if (!logs) {
|
|
@@ -305,7 +305,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
305
305
|
this.ctx.ui.requestRender();
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
|
|
308
|
+
async #handleViewSystemInfo(): Promise<void> {
|
|
309
309
|
try {
|
|
310
310
|
const info = await collectSystemInfo();
|
|
311
311
|
const formatted = formatSystemInfo(info);
|
|
@@ -321,7 +321,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
321
321
|
this.ctx.ui.requestRender();
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
async #handleOpenArtifacts(): Promise<void> {
|
|
325
325
|
const sessionFile = this.ctx.sessionManager.getSessionFile();
|
|
326
326
|
if (!sessionFile) {
|
|
327
327
|
this.ctx.showWarning("No active session file.");
|
|
@@ -357,7 +357,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
|
|
360
|
+
async #handleClearCache(): Promise<void> {
|
|
361
361
|
const sessionsDir = getSessionsDir();
|
|
362
362
|
|
|
363
363
|
// Get stats first
|
|
@@ -416,7 +416,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
416
416
|
this.ctx.ui.requestRender();
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
|
|
419
|
+
#getResolvedSettings(): Record<string, unknown> {
|
|
420
420
|
// Extract key settings for the report
|
|
421
421
|
return {
|
|
422
422
|
model: this.ctx.session.model?.id,
|