@oh-my-pi/pi-coding-agent 9.3.1 → 9.6.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 +98 -0
- package/examples/hooks/snake.ts +5 -5
- package/package.json +9 -8
- package/src/capability/index.ts +7 -9
- package/src/cli/config-cli.ts +86 -73
- package/src/cli/update-cli.ts +45 -3
- package/src/commit/agentic/agent.ts +4 -4
- package/src/commit/agentic/index.ts +6 -5
- package/src/commit/agentic/tools/analyze-file.ts +5 -7
- package/src/commit/agentic/tools/index.ts +3 -3
- package/src/commit/model-selection.ts +13 -17
- package/src/commit/pipeline.ts +5 -5
- package/src/config/model-registry.ts +7 -0
- package/src/config/settings-schema.ts +836 -0
- package/src/config/settings.ts +702 -0
- package/src/discovery/helpers.ts +55 -11
- package/src/exa/index.ts +1 -1
- package/src/exec/bash-executor.ts +13 -13
- package/src/exec/shell-session.ts +15 -3
- package/src/export/ttsr.ts +1 -1
- package/src/extensibility/skills.ts +40 -9
- package/src/index.ts +2 -10
- package/src/ipy/gateway-coordinator.ts +5 -159
- package/src/ipy/kernel.ts +6 -171
- package/src/ipy/runtime.ts +198 -0
- package/src/lsp/client.ts +14 -1
- package/src/lsp/defaults.json +0 -6
- package/src/lsp/index.ts +1 -1
- package/src/lsp/types.ts +2 -0
- package/src/main.ts +26 -48
- package/src/modes/components/armin.ts +7 -7
- package/src/modes/components/extensions/extension-dashboard.ts +33 -13
- package/src/modes/components/extensions/extension-list.ts +2 -2
- package/src/modes/components/footer.ts +5 -5
- package/src/modes/components/history-search.ts +2 -1
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/index.ts +1 -1
- package/src/modes/components/model-selector.ts +7 -7
- package/src/modes/components/session-selector.ts +2 -1
- package/src/modes/components/settings-defs.ts +210 -915
- package/src/modes/components/settings-selector.ts +80 -106
- package/src/modes/components/status-line/types.ts +2 -8
- package/src/modes/components/status-line-segment-editor.ts +4 -4
- package/src/modes/components/status-line.ts +28 -5
- package/src/modes/components/welcome.ts +3 -3
- package/src/modes/controllers/command-controller.ts +2 -2
- package/src/modes/controllers/event-controller.ts +9 -8
- package/src/modes/controllers/input-controller.ts +19 -15
- package/src/modes/controllers/selector-controller.ts +30 -14
- package/src/modes/interactive-mode.ts +10 -10
- package/src/modes/rpc/rpc-mode.ts +10 -0
- package/src/modes/rpc/rpc-types.ts +3 -0
- package/src/modes/types.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +4 -3
- package/src/patch/index.ts +7 -7
- package/src/patch/normalize.ts +3 -1
- package/src/prompts/system/plan-mode-active.md +5 -4
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/bash.md +12 -2
- package/src/prompts/tools/task.md +180 -73
- package/src/sdk.ts +38 -61
- package/src/session/agent-session.ts +66 -55
- package/src/session/agent-storage.ts +1 -1
- package/src/session/session-manager.ts +10 -10
- package/src/system-prompt.ts +2 -2
- package/src/task/executor.ts +9 -9
- package/src/task/index.ts +2 -2
- package/src/tools/ask.ts +5 -6
- package/src/tools/bash-interceptor.ts +39 -1
- package/src/tools/bash-normalize.ts +126 -0
- package/src/tools/bash.ts +31 -5
- package/src/tools/find.ts +51 -33
- package/src/tools/gemini-image.ts +7 -8
- package/src/tools/index.ts +5 -23
- package/src/tools/plan-mode-guard.ts +1 -6
- package/src/tools/python.ts +29 -4
- package/src/tools/read.ts +2 -2
- package/src/tools/write.ts +2 -2
- package/src/tui/output-block.ts +2 -2
- package/src/tui/utils.ts +2 -2
- package/src/utils/ignore-files.ts +119 -0
- package/src/web/search/auth.ts +6 -58
- package/src/web/search/index.ts +2 -6
- package/src/web/search/providers/anthropic.ts +6 -6
- package/src/web/search/providers/exa.ts +2 -62
- package/src/web/search/providers/perplexity.ts +7 -53
- package/examples/sdk/10-settings.ts +0 -37
- package/src/config/settings-manager.ts +0 -2015
|
@@ -12,13 +12,18 @@ import {
|
|
|
12
12
|
type TabBarTheme,
|
|
13
13
|
Text,
|
|
14
14
|
} from "@oh-my-pi/pi-tui";
|
|
15
|
-
import type
|
|
15
|
+
import { type SettingPath, settings } from "../../config/settings";
|
|
16
|
+
import type {
|
|
17
|
+
SettingTab,
|
|
18
|
+
StatusLinePreset,
|
|
19
|
+
StatusLineSegmentId,
|
|
20
|
+
StatusLineSeparatorStyle,
|
|
21
|
+
} from "../../config/settings-schema";
|
|
16
22
|
import { getSelectListTheme, getSettingsListTheme, theme } from "../../modes/theme/theme";
|
|
17
23
|
import { DynamicBorder } from "./dynamic-border";
|
|
18
24
|
import { PluginSettingsComponent } from "./plugin-settings";
|
|
19
25
|
import { getSettingsForTab, type SettingDef } from "./settings-defs";
|
|
20
26
|
import { getPreset } from "./status-line/presets";
|
|
21
|
-
import { StatusLineSegmentEditorComponent } from "./status-line-segment-editor";
|
|
22
27
|
|
|
23
28
|
function getTabBarTheme(): TabBarTheme {
|
|
24
29
|
return {
|
|
@@ -111,11 +116,10 @@ class SelectSubmenu extends Container {
|
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
type TabId = string;
|
|
115
|
-
|
|
116
119
|
const SETTINGS_TABS: Tab[] = [
|
|
117
120
|
{ id: "behavior", label: "Behavior" },
|
|
118
121
|
{ id: "tools", label: "Tools" },
|
|
122
|
+
{ id: "bash", label: "Bash" },
|
|
119
123
|
{ id: "display", label: "Display" },
|
|
120
124
|
{ id: "ttsr", label: "TTSR" },
|
|
121
125
|
{ id: "status", label: "Status" },
|
|
@@ -126,7 +130,7 @@ const SETTINGS_TABS: Tab[] = [
|
|
|
126
130
|
|
|
127
131
|
/**
|
|
128
132
|
* Dynamic context for settings that need runtime data.
|
|
129
|
-
* Some settings (like thinking level) are managed by the session, not
|
|
133
|
+
* Some settings (like thinking level) are managed by the session, not Settings.
|
|
130
134
|
*/
|
|
131
135
|
export interface SettingsRuntimeContext {
|
|
132
136
|
/** Available thinking levels (from session) */
|
|
@@ -139,19 +143,21 @@ export interface SettingsRuntimeContext {
|
|
|
139
143
|
cwd: string;
|
|
140
144
|
}
|
|
141
145
|
|
|
142
|
-
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
/** Status line settings subset for preview */
|
|
147
|
+
export interface StatusLinePreviewSettings {
|
|
148
|
+
preset?: StatusLinePreset;
|
|
149
|
+
leftSegments?: StatusLineSegmentId[];
|
|
150
|
+
rightSegments?: StatusLineSegmentId[];
|
|
151
|
+
separator?: StatusLineSeparatorStyle;
|
|
152
|
+
}
|
|
147
153
|
|
|
148
154
|
export interface SettingsCallbacks {
|
|
149
155
|
/** Called when any setting value changes */
|
|
150
|
-
onChange:
|
|
156
|
+
onChange: (path: SettingPath, newValue: unknown) => void;
|
|
151
157
|
/** Called for theme preview while browsing */
|
|
152
158
|
onThemePreview?: (theme: string) => void;
|
|
153
|
-
/** Called for status line preview while configuring
|
|
154
|
-
onStatusLinePreview?: (settings:
|
|
159
|
+
/** Called for status line preview while configuring */
|
|
160
|
+
onStatusLinePreview?: (settings: StatusLinePreviewSettings) => void;
|
|
155
161
|
/** Get current rendered status line for inline preview */
|
|
156
162
|
getStatusLinePreview?: () => string;
|
|
157
163
|
/** Called when plugins change */
|
|
@@ -171,16 +177,14 @@ export class SettingsSelectorComponent extends Container {
|
|
|
171
177
|
private pluginComponent: PluginSettingsComponent | null = null;
|
|
172
178
|
private statusPreviewContainer: Container | null = null;
|
|
173
179
|
private statusPreviewText: Text | null = null;
|
|
174
|
-
private currentTabId:
|
|
180
|
+
private currentTabId: SettingTab | "plugins" = "behavior";
|
|
175
181
|
|
|
176
|
-
private settingsManager: SettingsManager;
|
|
177
182
|
private context: SettingsRuntimeContext;
|
|
178
183
|
private callbacks: SettingsCallbacks;
|
|
179
184
|
|
|
180
|
-
constructor(
|
|
185
|
+
constructor(context: SettingsRuntimeContext, callbacks: SettingsCallbacks) {
|
|
181
186
|
super();
|
|
182
187
|
|
|
183
|
-
this.settingsManager = settingsManager;
|
|
184
188
|
this.context = context;
|
|
185
189
|
this.callbacks = callbacks;
|
|
186
190
|
|
|
@@ -190,7 +194,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
190
194
|
// Tab bar
|
|
191
195
|
this.tabBar = new TabBar("Settings", SETTINGS_TABS, getTabBarTheme());
|
|
192
196
|
this.tabBar.onTabChange = () => {
|
|
193
|
-
this.switchToTab(this.tabBar.getActiveTab().id as
|
|
197
|
+
this.switchToTab(this.tabBar.getActiveTab().id as SettingTab | "plugins");
|
|
194
198
|
};
|
|
195
199
|
this.addChild(this.tabBar);
|
|
196
200
|
|
|
@@ -204,7 +208,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
204
208
|
this.addChild(new DynamicBorder());
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
private switchToTab(tabId:
|
|
211
|
+
private switchToTab(tabId: SettingTab | "plugins"): void {
|
|
208
212
|
this.currentTabId = tabId;
|
|
209
213
|
|
|
210
214
|
// Remove current content
|
|
@@ -250,7 +254,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
250
254
|
switch (def.type) {
|
|
251
255
|
case "boolean":
|
|
252
256
|
return {
|
|
253
|
-
id: def.
|
|
257
|
+
id: def.path,
|
|
254
258
|
label: def.label,
|
|
255
259
|
description: def.description,
|
|
256
260
|
currentValue: currentValue ? "true" : "false",
|
|
@@ -259,7 +263,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
259
263
|
|
|
260
264
|
case "enum":
|
|
261
265
|
return {
|
|
262
|
-
id: def.
|
|
266
|
+
id: def.path,
|
|
263
267
|
label: def.label,
|
|
264
268
|
description: def.description,
|
|
265
269
|
currentValue: currentValue as string,
|
|
@@ -268,26 +272,24 @@ export class SettingsSelectorComponent extends Container {
|
|
|
268
272
|
|
|
269
273
|
case "submenu":
|
|
270
274
|
return {
|
|
271
|
-
id: def.
|
|
275
|
+
id: def.path,
|
|
272
276
|
label: def.label,
|
|
273
277
|
description: def.description,
|
|
274
|
-
currentValue: currentValue
|
|
278
|
+
currentValue: String(currentValue ?? ""),
|
|
275
279
|
submenu: (cv, done) => this.createSubmenu(def, cv, done),
|
|
276
280
|
};
|
|
277
281
|
}
|
|
278
282
|
}
|
|
279
283
|
|
|
280
284
|
/**
|
|
281
|
-
* Get the current value for a setting
|
|
285
|
+
* Get the current value for a setting.
|
|
282
286
|
*/
|
|
283
|
-
private getCurrentValue(def: SettingDef):
|
|
284
|
-
// Special
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
return this.context.thinkingLevel;
|
|
288
|
-
default:
|
|
289
|
-
return def.get(this.settingsManager);
|
|
287
|
+
private getCurrentValue(def: SettingDef): unknown {
|
|
288
|
+
// Special case: thinking level comes from runtime context
|
|
289
|
+
if (def.path === "defaultThinkingLevel") {
|
|
290
|
+
return this.context.thinkingLevel;
|
|
290
291
|
}
|
|
292
|
+
return settings.get(def.path);
|
|
291
293
|
}
|
|
292
294
|
|
|
293
295
|
/**
|
|
@@ -298,20 +300,15 @@ export class SettingsSelectorComponent extends Container {
|
|
|
298
300
|
currentValue: string,
|
|
299
301
|
done: (value?: string) => void,
|
|
300
302
|
): Container {
|
|
301
|
-
|
|
302
|
-
if (def.id === "statusLineSegments") {
|
|
303
|
-
return this.createSegmentEditor(done);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
let options = def.getOptions(this.settingsManager);
|
|
303
|
+
let options = def.getOptions();
|
|
307
304
|
|
|
308
|
-
// Special case: inject runtime options
|
|
309
|
-
if (def.
|
|
305
|
+
// Special case: inject runtime options for thinking level
|
|
306
|
+
if (def.path === "defaultThinkingLevel") {
|
|
310
307
|
options = this.context.availableThinkingLevels.map(level => {
|
|
311
|
-
const baseOpt = def.getOptions(
|
|
308
|
+
const baseOpt = def.getOptions().find(o => o.value === level);
|
|
312
309
|
return baseOpt || { value: level, label: level };
|
|
313
310
|
});
|
|
314
|
-
} else if (def.
|
|
311
|
+
} else if (def.path === "theme") {
|
|
315
312
|
options = this.context.availableThemes.map(t => ({ value: t, label: t }));
|
|
316
313
|
}
|
|
317
314
|
|
|
@@ -319,14 +316,16 @@ export class SettingsSelectorComponent extends Container {
|
|
|
319
316
|
let onPreview: ((value: string) => void) | undefined;
|
|
320
317
|
let onPreviewCancel: (() => void) | undefined;
|
|
321
318
|
|
|
322
|
-
if (def.
|
|
319
|
+
if (def.path === "theme") {
|
|
323
320
|
onPreview = this.callbacks.onThemePreview;
|
|
324
321
|
onPreviewCancel = () => this.callbacks.onThemePreview?.(currentValue);
|
|
325
|
-
} else if (def.
|
|
322
|
+
} else if (def.path === "statusLine.preset") {
|
|
326
323
|
onPreview = value => {
|
|
327
|
-
const presetDef = getPreset(
|
|
324
|
+
const presetDef = getPreset(
|
|
325
|
+
value as "default" | "minimal" | "compact" | "full" | "nerd" | "ascii" | "custom",
|
|
326
|
+
);
|
|
328
327
|
this.callbacks.onStatusLinePreview?.({
|
|
329
|
-
preset: value as
|
|
328
|
+
preset: value as StatusLinePreset,
|
|
330
329
|
leftSegments: presetDef.leftSegments,
|
|
331
330
|
rightSegments: presetDef.rightSegments,
|
|
332
331
|
separator: presetDef.separator,
|
|
@@ -334,7 +333,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
334
333
|
this.updateStatusPreview();
|
|
335
334
|
};
|
|
336
335
|
onPreviewCancel = () => {
|
|
337
|
-
const currentPreset =
|
|
336
|
+
const currentPreset = settings.get("statusLine.preset");
|
|
338
337
|
const presetDef = getPreset(currentPreset);
|
|
339
338
|
this.callbacks.onStatusLinePreview?.({
|
|
340
339
|
preset: currentPreset,
|
|
@@ -344,26 +343,20 @@ export class SettingsSelectorComponent extends Container {
|
|
|
344
343
|
});
|
|
345
344
|
this.updateStatusPreview();
|
|
346
345
|
};
|
|
347
|
-
} else if (def.
|
|
346
|
+
} else if (def.path === "statusLine.separator") {
|
|
348
347
|
onPreview = value => {
|
|
349
|
-
this.callbacks.onStatusLinePreview?.({
|
|
350
|
-
separator: value as StatusLineSettings["separator"],
|
|
351
|
-
});
|
|
348
|
+
this.callbacks.onStatusLinePreview?.({ separator: value as StatusLineSeparatorStyle });
|
|
352
349
|
this.updateStatusPreview();
|
|
353
350
|
};
|
|
354
351
|
onPreviewCancel = () => {
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
currentSettings.separator ?? getPreset(this.settingsManager.getStatusLinePreset()).separator;
|
|
358
|
-
this.callbacks.onStatusLinePreview?.({
|
|
359
|
-
separator,
|
|
360
|
-
});
|
|
352
|
+
const separator = settings.get("statusLine.separator");
|
|
353
|
+
this.callbacks.onStatusLinePreview?.({ separator });
|
|
361
354
|
this.updateStatusPreview();
|
|
362
355
|
};
|
|
363
356
|
}
|
|
364
357
|
|
|
365
358
|
// Provide status line preview for theme selection
|
|
366
|
-
const getPreview = def.
|
|
359
|
+
const getPreview = def.path === "theme" ? this.callbacks.getStatusLinePreview : undefined;
|
|
367
360
|
|
|
368
361
|
return new SelectSubmenu(
|
|
369
362
|
def.label,
|
|
@@ -371,10 +364,10 @@ export class SettingsSelectorComponent extends Container {
|
|
|
371
364
|
options,
|
|
372
365
|
currentValue,
|
|
373
366
|
value => {
|
|
374
|
-
// Persist
|
|
375
|
-
|
|
376
|
-
// Notify
|
|
377
|
-
this.callbacks.onChange(def.
|
|
367
|
+
// Persist
|
|
368
|
+
this.setSettingValue(def.path, value);
|
|
369
|
+
// Notify
|
|
370
|
+
this.callbacks.onChange(def.path, value);
|
|
378
371
|
done(value);
|
|
379
372
|
},
|
|
380
373
|
() => {
|
|
@@ -387,47 +380,24 @@ export class SettingsSelectorComponent extends Container {
|
|
|
387
380
|
}
|
|
388
381
|
|
|
389
382
|
/**
|
|
390
|
-
*
|
|
383
|
+
* Set a setting value, handling type conversion.
|
|
391
384
|
*/
|
|
392
|
-
private
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
this.settingsManager.setStatusLineLeftSegments(left);
|
|
403
|
-
this.settingsManager.setStatusLineRightSegments(right);
|
|
404
|
-
this.callbacks.onChange("statusLineSegments", "saved");
|
|
405
|
-
this.callbacks.onStatusLinePreview?.({ leftSegments: left, rightSegments: right });
|
|
406
|
-
this.updateStatusPreview();
|
|
407
|
-
done("saved");
|
|
408
|
-
},
|
|
409
|
-
onCancel: () => {
|
|
410
|
-
// Restore preview to saved state
|
|
411
|
-
const saved = this.settingsManager.getStatusLineSettings();
|
|
412
|
-
const savedPreset = getPreset(saved.preset ?? "default");
|
|
413
|
-
this.callbacks.onStatusLinePreview?.({
|
|
414
|
-
leftSegments: saved.leftSegments ?? savedPreset.leftSegments,
|
|
415
|
-
rightSegments: saved.rightSegments ?? savedPreset.rightSegments,
|
|
416
|
-
});
|
|
417
|
-
this.updateStatusPreview();
|
|
418
|
-
done();
|
|
419
|
-
},
|
|
420
|
-
onPreview: (left, right) => {
|
|
421
|
-
this.callbacks.onStatusLinePreview?.({ leftSegments: left, rightSegments: right });
|
|
422
|
-
this.updateStatusPreview();
|
|
423
|
-
},
|
|
424
|
-
});
|
|
385
|
+
private setSettingValue(path: SettingPath, value: string): void {
|
|
386
|
+
// Handle number conversions
|
|
387
|
+
const currentValue = settings.get(path);
|
|
388
|
+
if (typeof currentValue === "number") {
|
|
389
|
+
settings.set(path, Number(value) as never);
|
|
390
|
+
} else if (typeof currentValue === "boolean") {
|
|
391
|
+
settings.set(path, (value === "true") as never);
|
|
392
|
+
} else {
|
|
393
|
+
settings.set(path, value as never);
|
|
394
|
+
}
|
|
425
395
|
}
|
|
426
396
|
|
|
427
397
|
/**
|
|
428
398
|
* Show a settings tab using definitions.
|
|
429
399
|
*/
|
|
430
|
-
private showSettingsTab(tabId:
|
|
400
|
+
private showSettingsTab(tabId: SettingTab): void {
|
|
431
401
|
const defs = getSettingsForTab(tabId);
|
|
432
402
|
const items: SettingItem[] = [];
|
|
433
403
|
|
|
@@ -454,22 +424,22 @@ export class SettingsSelectorComponent extends Container {
|
|
|
454
424
|
10,
|
|
455
425
|
getSettingsListTheme(),
|
|
456
426
|
(id, newValue) => {
|
|
457
|
-
const def = defs.find(d => d.
|
|
427
|
+
const def = defs.find(d => d.path === id);
|
|
458
428
|
if (!def) return;
|
|
459
429
|
|
|
460
|
-
|
|
430
|
+
const path = def.path;
|
|
431
|
+
|
|
461
432
|
if (def.type === "boolean") {
|
|
462
433
|
const boolValue = newValue === "true";
|
|
463
|
-
|
|
464
|
-
this.callbacks.onChange(
|
|
434
|
+
settings.set(path, boolValue as never);
|
|
435
|
+
this.callbacks.onChange(path, boolValue);
|
|
465
436
|
|
|
466
|
-
// Trigger status line preview for status tab boolean settings
|
|
467
437
|
if (tabId === "status") {
|
|
468
438
|
this.triggerStatusLinePreview();
|
|
469
439
|
}
|
|
470
440
|
} else if (def.type === "enum") {
|
|
471
|
-
|
|
472
|
-
this.callbacks.onChange(
|
|
441
|
+
settings.set(path, newValue as never);
|
|
442
|
+
this.callbacks.onChange(path, newValue);
|
|
473
443
|
}
|
|
474
444
|
// Submenu types are handled in createSubmenu
|
|
475
445
|
},
|
|
@@ -493,9 +463,13 @@ export class SettingsSelectorComponent extends Container {
|
|
|
493
463
|
* Trigger status line preview with current settings.
|
|
494
464
|
*/
|
|
495
465
|
private triggerStatusLinePreview(): void {
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
466
|
+
const statusLineSettings: StatusLinePreviewSettings = {
|
|
467
|
+
preset: settings.get("statusLine.preset"),
|
|
468
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
469
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
470
|
+
separator: settings.get("statusLine.separator"),
|
|
471
|
+
};
|
|
472
|
+
this.callbacks.onStatusLinePreview?.(statusLineSettings);
|
|
499
473
|
this.updateStatusPreview();
|
|
500
474
|
}
|
|
501
475
|
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
StatusLinePreset,
|
|
3
|
-
StatusLineSegmentId,
|
|
4
|
-
StatusLineSegmentOptions,
|
|
5
|
-
StatusLineSeparatorStyle,
|
|
6
|
-
StatusLineSettings,
|
|
7
|
-
} from "../../../config/settings-manager";
|
|
1
|
+
import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../../config/settings-schema";
|
|
8
2
|
import type { AgentSession } from "../../../session/agent-session";
|
|
3
|
+
import type { StatusLineSegmentOptions, StatusLineSettings } from "../status-line";
|
|
9
4
|
|
|
10
|
-
// Re-export types from settings-manager (single source of truth)
|
|
11
5
|
export type {
|
|
12
6
|
StatusLinePreset,
|
|
13
7
|
StatusLineSegmentId,
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* - Shift+J/K: Reorder segment within column
|
|
9
9
|
* - Live preview shown in the actual status line above
|
|
10
10
|
*/
|
|
11
|
-
import { Container, matchesKey } from "@oh-my-pi/pi-tui";
|
|
12
|
-
import type { StatusLineSegmentId } from "../../config/settings-
|
|
11
|
+
import { Container, matchesKey, padding } from "@oh-my-pi/pi-tui";
|
|
12
|
+
import type { StatusLineSegmentId } from "../../config/settings-schema";
|
|
13
13
|
import { theme } from "../../modes/theme/theme";
|
|
14
14
|
import { ALL_SEGMENT_IDS } from "./status-line/segments";
|
|
15
15
|
|
|
@@ -351,7 +351,7 @@ export class StatusLineSegmentEditorComponent extends Container {
|
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
// Pad to column width (accounting for ANSI codes)
|
|
354
|
-
const
|
|
355
|
-
return text +
|
|
354
|
+
const padSize = colWidth - label.length - 1;
|
|
355
|
+
return text + padding(Math.max(0, padSize));
|
|
356
356
|
}
|
|
357
357
|
}
|
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
4
|
-
import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import { type Component, padding, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { $ } from "bun";
|
|
6
|
-
import
|
|
6
|
+
import { settings } from "../../config/settings";
|
|
7
|
+
import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../config/settings-schema";
|
|
7
8
|
import { theme } from "../../modes/theme/theme";
|
|
8
9
|
import type { AgentSession } from "../../session/agent-session";
|
|
9
10
|
import { getPreset } from "./status-line/presets";
|
|
10
11
|
import { renderSegment, type SegmentContext } from "./status-line/segments";
|
|
11
12
|
import { getSeparator } from "./status-line/separators";
|
|
12
13
|
|
|
14
|
+
export interface StatusLineSegmentOptions {
|
|
15
|
+
model?: { showThinkingLevel?: boolean };
|
|
16
|
+
path?: { abbreviate?: boolean; maxLength?: number; stripWorkPrefix?: boolean };
|
|
17
|
+
git?: { showBranch?: boolean; showStaged?: boolean; showUnstaged?: boolean; showUntracked?: boolean };
|
|
18
|
+
time?: { format?: "12h" | "24h"; showSeconds?: boolean };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface StatusLineSettings {
|
|
22
|
+
preset?: StatusLinePreset;
|
|
23
|
+
leftSegments?: StatusLineSegmentId[];
|
|
24
|
+
rightSegments?: StatusLineSegmentId[];
|
|
25
|
+
separator?: StatusLineSeparatorStyle;
|
|
26
|
+
segmentOptions?: StatusLineSegmentOptions;
|
|
27
|
+
showHookStatus?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
13
30
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
14
31
|
// Rendering Helpers
|
|
15
32
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -60,8 +77,14 @@ export class StatusLineComponent implements Component {
|
|
|
60
77
|
|
|
61
78
|
constructor(session: AgentSession) {
|
|
62
79
|
this.session = session;
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
this.settings = {
|
|
81
|
+
preset: settings.get("statusLine.preset"),
|
|
82
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
83
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
84
|
+
separator: settings.get("statusLine.separator"),
|
|
85
|
+
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
86
|
+
segmentOptions: settings.getGroup("statusLine").segmentOptions,
|
|
87
|
+
};
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
updateSettings(settings: StatusLineSettings): void {
|
|
@@ -378,7 +401,7 @@ export class StatusLineComponent implements Component {
|
|
|
378
401
|
leftWidth = groupWidth(left, leftCapWidth, leftSepWidth);
|
|
379
402
|
rightWidth = groupWidth(right, rightCapWidth, rightSepWidth);
|
|
380
403
|
const gapWidth = Math.max(1, topFillWidth - leftWidth - rightWidth);
|
|
381
|
-
return leftGroup +
|
|
404
|
+
return leftGroup + padding(gapWidth) + rightGroup;
|
|
382
405
|
}
|
|
383
406
|
|
|
384
407
|
getTopBorder(width: number): { content: string; width: number } {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
1
|
+
import { type Component, padding, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { APP_NAME } from "../../config";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
4
|
|
|
@@ -169,7 +169,7 @@ export class WelcomeComponent implements Component {
|
|
|
169
169
|
}
|
|
170
170
|
const leftPad = Math.floor((width - visLen) / 2);
|
|
171
171
|
const rightPad = width - visLen - leftPad;
|
|
172
|
-
return
|
|
172
|
+
return padding(leftPad) + text + padding(rightPad);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
/** Apply magenta→cyan gradient to a string */
|
|
@@ -224,6 +224,6 @@ export class WelcomeComponent implements Component {
|
|
|
224
224
|
}
|
|
225
225
|
return `${truncated}${ellipsis}`;
|
|
226
226
|
}
|
|
227
|
-
return str +
|
|
227
|
+
return str + padding(width - visLen);
|
|
228
228
|
}
|
|
229
229
|
}
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { UsageLimit, UsageReport } from "@oh-my-pi/pi-ai";
|
|
5
|
-
import { Loader, Markdown, Spacer, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { $ } from "bun";
|
|
7
7
|
import { nanoid } from "nanoid";
|
|
8
8
|
import { loadCustomShare } from "../../export/custom-share";
|
|
@@ -773,7 +773,7 @@ function formatAccountHeader(limit: UsageLimit, report: UsageReport, index: numb
|
|
|
773
773
|
function padColumn(text: string, width: number): string {
|
|
774
774
|
const visible = visibleWidth(text);
|
|
775
775
|
if (visible >= width) return text;
|
|
776
|
-
return `${text}${
|
|
776
|
+
return `${text}${padding(width - visible)}`;
|
|
777
777
|
}
|
|
778
778
|
|
|
779
779
|
function resolveAggregateStatus(limits: UsageLimit[]): UsageLimit["status"] {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Loader, Text } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import { settings } from "../../config/settings";
|
|
2
3
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
3
4
|
import { ReadToolGroupComponent } from "../../modes/components/read-tool-group";
|
|
4
5
|
import { TodoReminderComponent } from "../../modes/components/todo-reminder";
|
|
@@ -136,9 +137,9 @@ export class EventController {
|
|
|
136
137
|
content.name,
|
|
137
138
|
content.arguments,
|
|
138
139
|
{
|
|
139
|
-
showImages:
|
|
140
|
-
editFuzzyThreshold:
|
|
141
|
-
editAllowFuzzy:
|
|
140
|
+
showImages: settings.get("terminal.showImages"),
|
|
141
|
+
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
|
142
|
+
editAllowFuzzy: settings.get("edit.fuzzyMatch"),
|
|
142
143
|
},
|
|
143
144
|
tool,
|
|
144
145
|
this.ctx.ui,
|
|
@@ -210,9 +211,9 @@ export class EventController {
|
|
|
210
211
|
event.toolName,
|
|
211
212
|
event.args,
|
|
212
213
|
{
|
|
213
|
-
showImages:
|
|
214
|
-
editFuzzyThreshold:
|
|
215
|
-
editAllowFuzzy:
|
|
214
|
+
showImages: settings.get("terminal.showImages"),
|
|
215
|
+
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
|
216
|
+
editAllowFuzzy: settings.get("edit.fuzzyMatch"),
|
|
216
217
|
},
|
|
217
218
|
tool,
|
|
218
219
|
this.ctx.ui,
|
|
@@ -383,10 +384,10 @@ export class EventController {
|
|
|
383
384
|
sendCompletionNotification(): void {
|
|
384
385
|
if (this.ctx.isBackgrounded === false) return;
|
|
385
386
|
if (isNotificationSuppressed()) return;
|
|
386
|
-
const method =
|
|
387
|
+
const method = settings.get("notifications.onComplete");
|
|
387
388
|
if (method === "off") return;
|
|
388
389
|
const protocol = method === "auto" ? detectNotificationProtocol() : method;
|
|
389
|
-
const title = this.ctx.sessionManager.
|
|
390
|
+
const title = this.ctx.sessionManager.getSessionName();
|
|
390
391
|
const message = title ? `${title}: Complete` : "Complete";
|
|
391
392
|
sendNotification(protocol, message);
|
|
392
393
|
}
|
|
@@ -4,6 +4,7 @@ import * as os from "node:os";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
6
|
import { nanoid } from "nanoid";
|
|
7
|
+
import { settings } from "../../config/settings";
|
|
7
8
|
import { theme } from "../../modes/theme/theme";
|
|
8
9
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
9
10
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
@@ -40,17 +41,20 @@ export class InputController {
|
|
|
40
41
|
this.ctx.isPythonMode = false;
|
|
41
42
|
this.ctx.updateEditorBorderColor();
|
|
42
43
|
} else if (!this.ctx.editor.getText().trim()) {
|
|
43
|
-
// Double-escape with empty editor triggers /tree
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
// Double-escape with empty editor triggers /tree, /branch, or nothing based on setting
|
|
45
|
+
const action = settings.get("doubleEscapeAction");
|
|
46
|
+
if (action !== "none") {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
if (now - this.ctx.lastEscapeTime < 500) {
|
|
49
|
+
if (action === "tree") {
|
|
50
|
+
this.ctx.showTreeSelector();
|
|
51
|
+
} else {
|
|
52
|
+
this.ctx.showUserMessageSelector();
|
|
53
|
+
}
|
|
54
|
+
this.ctx.lastEscapeTime = 0;
|
|
48
55
|
} else {
|
|
49
|
-
this.ctx.
|
|
56
|
+
this.ctx.lastEscapeTime = now;
|
|
50
57
|
}
|
|
51
|
-
this.ctx.lastEscapeTime = 0;
|
|
52
|
-
} else {
|
|
53
|
-
this.ctx.lastEscapeTime = now;
|
|
54
58
|
}
|
|
55
59
|
}
|
|
56
60
|
};
|
|
@@ -242,7 +246,7 @@ export class InputController {
|
|
|
242
246
|
return;
|
|
243
247
|
}
|
|
244
248
|
if (text === "/branch") {
|
|
245
|
-
if (
|
|
249
|
+
if (settings.get("doubleEscapeAction") === "tree") {
|
|
246
250
|
this.ctx.showTreeSelector();
|
|
247
251
|
} else {
|
|
248
252
|
this.ctx.showUserMessageSelector();
|
|
@@ -418,13 +422,13 @@ export class InputController {
|
|
|
418
422
|
|
|
419
423
|
// Generate session title on first message
|
|
420
424
|
const hasUserMessages = this.ctx.agent.state.messages.some((m: AgentMessage) => m.role === "user");
|
|
421
|
-
if (!hasUserMessages && !this.ctx.sessionManager.
|
|
425
|
+
if (!hasUserMessages && !this.ctx.sessionManager.getSessionName() && !process.env.OMP_NO_TITLE) {
|
|
422
426
|
const registry = this.ctx.session.modelRegistry;
|
|
423
|
-
const smolModel = this.ctx.
|
|
427
|
+
const smolModel = this.ctx.settings.getModelRole("smol");
|
|
424
428
|
generateSessionTitle(text, registry, smolModel, this.ctx.session.sessionId)
|
|
425
429
|
.then(async title => {
|
|
426
430
|
if (title) {
|
|
427
|
-
await this.ctx.sessionManager.
|
|
431
|
+
await this.ctx.sessionManager.setSessionName(title);
|
|
428
432
|
setTerminalTitle(`π: ${title}`);
|
|
429
433
|
}
|
|
430
434
|
})
|
|
@@ -560,7 +564,7 @@ export class InputController {
|
|
|
560
564
|
const image = await readImageFromClipboard();
|
|
561
565
|
if (image) {
|
|
562
566
|
let imageData = image;
|
|
563
|
-
if (
|
|
567
|
+
if (settings.get("images.autoResize")) {
|
|
564
568
|
try {
|
|
565
569
|
const resized = await resizeImage({
|
|
566
570
|
type: "image",
|
|
@@ -651,7 +655,7 @@ export class InputController {
|
|
|
651
655
|
|
|
652
656
|
toggleThinkingBlockVisibility(): void {
|
|
653
657
|
this.ctx.hideThinkingBlock = !this.ctx.hideThinkingBlock;
|
|
654
|
-
|
|
658
|
+
settings.set("hideThinkingBlock", this.ctx.hideThinkingBlock);
|
|
655
659
|
|
|
656
660
|
// Rebuild chat from session messages
|
|
657
661
|
this.ctx.chatContainer.clear();
|