osdy-pi 0.1.8 → 1.2.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/README.md +209 -161
- package/bin/osdy-pi.mjs +127 -0
- package/extensions/osdy-pi/account-profiles.test.ts +517 -0
- package/extensions/osdy-pi/account-profiles.ts +581 -0
- package/extensions/osdy-pi/animation.ts +47 -5
- package/extensions/osdy-pi/constants.ts +282 -103
- package/extensions/osdy-pi/diff-panel.ts +78 -24
- package/extensions/osdy-pi/editor-settings.test.ts +163 -0
- package/extensions/osdy-pi/editor-settings.ts +112 -0
- package/extensions/osdy-pi/metrics.ts +8 -135
- package/extensions/osdy-pi/plugin-events.ts +26 -0
- package/extensions/osdy-pi/profile-label.ts +29 -0
- package/extensions/osdy-pi/runtime-helpers.test.ts +26 -0
- package/extensions/osdy-pi/runtime-helpers.ts +87 -44
- package/extensions/osdy-pi/runtime.test.ts +62 -0
- package/extensions/osdy-pi/runtime.ts +256 -64
- package/extensions/osdy-pi/types.ts +33 -10
- package/extensions/osdy-pi/ui.test.ts +51 -0
- package/extensions/osdy-pi/ui.ts +298 -114
- package/extensions/osdy-pi/utils.ts +43 -1
- package/extensions/osdy-pi/working-tree.ts +25 -6
- package/package.json +22 -5
- package/scripts/osdy-pi-account-profiles.mjs +501 -0
- package/themes/osdy-pi-catppuccin-frappe.json +94 -0
- package/themes/osdy-pi-catppuccin-latte.json +94 -0
- package/themes/osdy-pi-catppuccin-macchiato.json +94 -0
- package/themes/osdy-pi-catppuccin-mocha.json +94 -0
- package/themes/osdy-pi-dark.json +0 -6
- package/themes/osdy-pi-dracula.json +83 -0
- package/themes/osdy-pi-kanagawa-dragon.json +91 -0
- package/themes/osdy-pi-kanagawa-lotus.json +92 -0
- package/themes/osdy-pi-kanagawa-wave.json +91 -0
- package/themes/osdy-pi-lucent-orange.json +80 -0
- package/themes/osdy-pi-matrix.json +83 -0
- package/themes/osdy-pi-new.json +91 -0
- package/themes/osdy-pi-sexy.json +84 -0
- package/themes/osdy-pi-tokyo-night.json +83 -0
- package/themes/osdy-pi-light.json +0 -84
- package/themes/osdy-pi-simple.json +0 -83
|
@@ -6,14 +6,26 @@ import { createAudioEventRouter } from "./audio-event-router.js";
|
|
|
6
6
|
import { registerAudioNotificationFlags } from "./audio-notification-config.js";
|
|
7
7
|
import { createAudioNotificationService } from "./audio-notification-service.js";
|
|
8
8
|
import { createAudioPlaybackAdapter } from "./audio-playback.js";
|
|
9
|
+
import { subscribeQuestionPromptAudioNotification } from "./plugin-events.js";
|
|
9
10
|
import { createAudioSoundSettingsStore } from "./audio-sound-settings.js";
|
|
10
|
-
import {
|
|
11
|
+
import { createEditorSettingsStore } from "./editor-settings.js";
|
|
12
|
+
import { registerAccountProfilesCommand } from "./account-profiles.js";
|
|
13
|
+
import {
|
|
14
|
+
applyOsdyPi,
|
|
15
|
+
createResponsiveCoordinator,
|
|
16
|
+
disableOsdyPi,
|
|
17
|
+
notifyStatus,
|
|
18
|
+
reconcileResponsiveUi,
|
|
19
|
+
} from "./runtime-helpers.js";
|
|
11
20
|
import { runSoundSetupWizard } from "./sound-setup-wizard.js";
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_EDITOR_MODE,
|
|
23
|
+
EDITOR_MODES,
|
|
24
|
+
type EditorMode,
|
|
25
|
+
type HeaderVariant,
|
|
26
|
+
type OsdyState,
|
|
27
|
+
type WorkingTreeState,
|
|
28
|
+
type WorkingWidgetState,
|
|
17
29
|
} from "./types.js";
|
|
18
30
|
import { WORKING_TREE_WIDGET_KEY, WORKING_WIDGET_KEY } from "./constants.js";
|
|
19
31
|
import {
|
|
@@ -34,11 +46,15 @@ function scheduleOsdyRefresh(
|
|
|
34
46
|
state: OsdyState,
|
|
35
47
|
workingState: WorkingWidgetState,
|
|
36
48
|
workingTreeState: WorkingTreeState,
|
|
49
|
+
pendingRefreshes: Set<ReturnType<typeof setTimeout>>,
|
|
50
|
+
isCurrentSession: () => boolean,
|
|
37
51
|
): void {
|
|
38
|
-
setTimeout(() => {
|
|
39
|
-
|
|
52
|
+
const timeout = setTimeout(() => {
|
|
53
|
+
pendingRefreshes.delete(timeout);
|
|
54
|
+
if (state.enabled && isCurrentSession())
|
|
40
55
|
applyOsdyPi(pi, ctx, state, workingState, workingTreeState);
|
|
41
56
|
}, delayMs);
|
|
57
|
+
pendingRefreshes.add(timeout);
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
function claimOsdyVisualLayer(
|
|
@@ -47,6 +63,8 @@ function claimOsdyVisualLayer(
|
|
|
47
63
|
state: OsdyState,
|
|
48
64
|
workingState: WorkingWidgetState,
|
|
49
65
|
workingTreeState: WorkingTreeState,
|
|
66
|
+
pendingRefreshes: Set<ReturnType<typeof setTimeout>>,
|
|
67
|
+
isCurrentSession: () => boolean,
|
|
50
68
|
): void {
|
|
51
69
|
if (!state.enabled) return;
|
|
52
70
|
applyOsdyPi(pi, ctx, state, workingState, workingTreeState);
|
|
@@ -58,37 +76,12 @@ function claimOsdyVisualLayer(
|
|
|
58
76
|
state,
|
|
59
77
|
workingState,
|
|
60
78
|
workingTreeState,
|
|
79
|
+
pendingRefreshes,
|
|
80
|
+
isCurrentSession,
|
|
61
81
|
);
|
|
62
82
|
}
|
|
63
83
|
}
|
|
64
84
|
|
|
65
|
-
function loadSessionMetadata(
|
|
66
|
-
pi: ExtensionAPI,
|
|
67
|
-
ctx: ExtensionContext,
|
|
68
|
-
state: OsdyState,
|
|
69
|
-
): void {
|
|
70
|
-
void pi
|
|
71
|
-
.exec("git", ["branch", "--show-current"], { cwd: ctx.cwd })
|
|
72
|
-
.then((result) => {
|
|
73
|
-
state.gitLabel = result.stdout.trim() || "detached";
|
|
74
|
-
})
|
|
75
|
-
.catch(() => {
|
|
76
|
-
state.gitLabel = "no-git";
|
|
77
|
-
});
|
|
78
|
-
void pi
|
|
79
|
-
.exec("find", [ctx.cwd, "-name", "AGENTS.md", "-o", "-name", "AGENTS.MD"], {
|
|
80
|
-
cwd: ctx.cwd,
|
|
81
|
-
})
|
|
82
|
-
.then((result) => {
|
|
83
|
-
state.agentsLabel = String(
|
|
84
|
-
result.stdout.split("\n").filter(Boolean).length,
|
|
85
|
-
);
|
|
86
|
-
})
|
|
87
|
-
.catch(() => {
|
|
88
|
-
state.agentsLabel = "0";
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
85
|
function parseCommandArgs(args: string): string[] {
|
|
93
86
|
return args.trim().split(/\s+/).filter(Boolean);
|
|
94
87
|
}
|
|
@@ -119,8 +112,11 @@ function disableOsdyPiCommand(
|
|
|
119
112
|
state: OsdyState,
|
|
120
113
|
workingTreeState: WorkingTreeState,
|
|
121
114
|
controller: WorkingController,
|
|
115
|
+
stopResponsive: () => void,
|
|
122
116
|
): void {
|
|
117
|
+
stopResponsive();
|
|
123
118
|
state.enabled = false;
|
|
119
|
+
workingTreeState.visible = false;
|
|
124
120
|
controller.stopWorking();
|
|
125
121
|
clearWorkingTree(workingTreeState);
|
|
126
122
|
disableOsdyPi(ctx, state);
|
|
@@ -135,8 +131,7 @@ function setHeaderVariant(
|
|
|
135
131
|
variant: HeaderVariant,
|
|
136
132
|
): void {
|
|
137
133
|
state.headerVariant = variant;
|
|
138
|
-
if (state.enabled)
|
|
139
|
-
applyOsdyPi(pi, ctx, state, workingState, workingTreeState);
|
|
134
|
+
if (state.enabled) applyOsdyPi(pi, ctx, state, workingState, workingTreeState);
|
|
140
135
|
ctx.ui.notify(`osdy-pi style: ${variant}`, "info");
|
|
141
136
|
}
|
|
142
137
|
|
|
@@ -150,6 +145,7 @@ function getOsdyCommandCompletions(prefix: string) {
|
|
|
150
145
|
"status",
|
|
151
146
|
"sound",
|
|
152
147
|
"working-tree",
|
|
148
|
+
"editor",
|
|
153
149
|
"diff",
|
|
154
150
|
...HEADER_VARIANTS,
|
|
155
151
|
].map((value) => ({ value, label: value }));
|
|
@@ -167,6 +163,17 @@ function getOsdyCommandCompletions(prefix: string) {
|
|
|
167
163
|
"working-tree position bottom",
|
|
168
164
|
].map((value) => ({ value, label: value }));
|
|
169
165
|
}
|
|
166
|
+
if (trimmed === "editor") {
|
|
167
|
+
return [
|
|
168
|
+
"editor auto",
|
|
169
|
+
"editor extended",
|
|
170
|
+
"editor simple",
|
|
171
|
+
"editor on",
|
|
172
|
+
"editor off",
|
|
173
|
+
"editor toggle",
|
|
174
|
+
"editor status",
|
|
175
|
+
].map((value) => ({ value, label: value }));
|
|
176
|
+
}
|
|
170
177
|
if (parts.length === 1) {
|
|
171
178
|
const valuePrefix = parts[0] ?? "";
|
|
172
179
|
return [
|
|
@@ -175,6 +182,7 @@ function getOsdyCommandCompletions(prefix: string) {
|
|
|
175
182
|
"status",
|
|
176
183
|
"sound",
|
|
177
184
|
"working-tree",
|
|
185
|
+
"editor",
|
|
178
186
|
"diff",
|
|
179
187
|
...HEADER_VARIANTS,
|
|
180
188
|
]
|
|
@@ -187,6 +195,15 @@ function getOsdyCommandCompletions(prefix: string) {
|
|
|
187
195
|
.filter((value) => value.startsWith(valuePrefix))
|
|
188
196
|
.map((value) => ({ value: `sound ${value}`, label: `sound ${value}` }));
|
|
189
197
|
}
|
|
198
|
+
if (parts[0] === "editor" && parts.length === 2) {
|
|
199
|
+
const valuePrefix = parts[1] ?? "";
|
|
200
|
+
return ["auto", "extended", "simple", "on", "off", "toggle", "status"]
|
|
201
|
+
.filter((value) => value.startsWith(valuePrefix))
|
|
202
|
+
.map((value) => ({
|
|
203
|
+
value: `editor ${value}`,
|
|
204
|
+
label: `editor ${value}`,
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
190
207
|
if (parts[0] === "working-tree" && parts.length === 2) {
|
|
191
208
|
const valuePrefix = parts[1] ?? "";
|
|
192
209
|
return ["on", "off", "toggle", "status", "position"]
|
|
@@ -260,6 +277,101 @@ function handleWorkingTreePositionCommand(
|
|
|
260
277
|
);
|
|
261
278
|
}
|
|
262
279
|
|
|
280
|
+
async function setEditorMode(
|
|
281
|
+
mode: EditorMode,
|
|
282
|
+
pi: ExtensionAPI,
|
|
283
|
+
ctx: ExtensionContext,
|
|
284
|
+
state: OsdyState,
|
|
285
|
+
workingTreeState: WorkingTreeState,
|
|
286
|
+
settingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
287
|
+
): Promise<void> {
|
|
288
|
+
state.editorMode = mode;
|
|
289
|
+
if (state.enabled) reconcileResponsiveUi(pi, ctx, state, workingTreeState);
|
|
290
|
+
try {
|
|
291
|
+
await settingsStore.save({
|
|
292
|
+
version: 1,
|
|
293
|
+
editorMode: mode,
|
|
294
|
+
workingTreeEnabled: state.workingTreeEnabled,
|
|
295
|
+
});
|
|
296
|
+
ctx.ui.notify(`osdy-pi editor mode: ${mode}`, "info");
|
|
297
|
+
} catch {
|
|
298
|
+
ctx.ui.notify(
|
|
299
|
+
"osdy-pi editor mode changed but could not be saved",
|
|
300
|
+
"warning",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function handleEditorCommand(
|
|
306
|
+
action: string | undefined,
|
|
307
|
+
rest: string[],
|
|
308
|
+
pi: ExtensionAPI,
|
|
309
|
+
ctx: ExtensionContext,
|
|
310
|
+
state: OsdyState,
|
|
311
|
+
workingTreeState: WorkingTreeState,
|
|
312
|
+
settingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
313
|
+
): Promise<void> {
|
|
314
|
+
if (rest.length > 0) {
|
|
315
|
+
ctx.ui.notify(
|
|
316
|
+
"Usage: /osdy-pi editor auto | extended | simple | on | off | toggle | status",
|
|
317
|
+
"warning",
|
|
318
|
+
);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const modeForAction: Record<string, EditorMode> = {
|
|
322
|
+
auto: EDITOR_MODES.AUTO,
|
|
323
|
+
extended: EDITOR_MODES.EXTENDED,
|
|
324
|
+
simple: EDITOR_MODES.SIMPLE,
|
|
325
|
+
on: EDITOR_MODES.EXTENDED,
|
|
326
|
+
off: EDITOR_MODES.SIMPLE,
|
|
327
|
+
};
|
|
328
|
+
const mode = action ? modeForAction[action] : undefined;
|
|
329
|
+
if (mode) {
|
|
330
|
+
await setEditorMode(mode, pi, ctx, state, workingTreeState, settingsStore);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (action === "toggle") {
|
|
334
|
+
await setEditorMode(
|
|
335
|
+
state.editorMode === EDITOR_MODES.EXTENDED
|
|
336
|
+
? EDITOR_MODES.SIMPLE
|
|
337
|
+
: EDITOR_MODES.EXTENDED,
|
|
338
|
+
pi,
|
|
339
|
+
ctx,
|
|
340
|
+
state,
|
|
341
|
+
workingTreeState,
|
|
342
|
+
settingsStore,
|
|
343
|
+
);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (action === "status" || action === undefined) {
|
|
347
|
+
ctx.ui.notify(
|
|
348
|
+
`osdy-pi editor mode ${state.editorMode}, effective ${state.editorEffective ? "extended" : "simple/native"}`,
|
|
349
|
+
"info",
|
|
350
|
+
);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
ctx.ui.notify(
|
|
354
|
+
"Usage: /osdy-pi editor auto | extended | simple | on | off | toggle | status",
|
|
355
|
+
"warning",
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function saveWorkingTreePreference(
|
|
360
|
+
state: OsdyState,
|
|
361
|
+
settingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
362
|
+
): Promise<boolean> {
|
|
363
|
+
try {
|
|
364
|
+
await settingsStore.save({
|
|
365
|
+
version: 1,
|
|
366
|
+
editorMode: state.editorMode,
|
|
367
|
+
workingTreeEnabled: state.workingTreeEnabled,
|
|
368
|
+
});
|
|
369
|
+
return true;
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
263
375
|
async function handleWorkingTreeCommand(
|
|
264
376
|
action: string | undefined,
|
|
265
377
|
rest: string[],
|
|
@@ -268,6 +380,7 @@ async function handleWorkingTreeCommand(
|
|
|
268
380
|
state: OsdyState,
|
|
269
381
|
workingState: WorkingWidgetState,
|
|
270
382
|
workingTreeState: WorkingTreeState,
|
|
383
|
+
settingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
271
384
|
): Promise<void> {
|
|
272
385
|
if (action === "position") {
|
|
273
386
|
handleWorkingTreePositionCommand(
|
|
@@ -283,15 +396,29 @@ async function handleWorkingTreeCommand(
|
|
|
283
396
|
if (action === "on") {
|
|
284
397
|
state.workingTreeEnabled = true;
|
|
285
398
|
workingTreeState.enabled = true;
|
|
399
|
+
reconcileResponsiveUi(pi, ctx, state, workingTreeState);
|
|
400
|
+
const saved = await saveWorkingTreePreference(state, settingsStore);
|
|
286
401
|
await refreshWorkingTree(pi, ctx, workingTreeState);
|
|
287
|
-
ctx.ui.notify(
|
|
402
|
+
ctx.ui.notify(
|
|
403
|
+
saved
|
|
404
|
+
? "osdy-pi working tree enabled"
|
|
405
|
+
: "osdy-pi working tree enabled but could not be saved",
|
|
406
|
+
saved ? "info" : "warning",
|
|
407
|
+
);
|
|
288
408
|
return;
|
|
289
409
|
}
|
|
290
410
|
if (action === "off") {
|
|
291
411
|
state.workingTreeEnabled = false;
|
|
292
412
|
workingTreeState.enabled = false;
|
|
413
|
+
reconcileResponsiveUi(pi, ctx, state, workingTreeState);
|
|
293
414
|
clearWorkingTree(workingTreeState);
|
|
294
|
-
|
|
415
|
+
const saved = await saveWorkingTreePreference(state, settingsStore);
|
|
416
|
+
ctx.ui.notify(
|
|
417
|
+
saved
|
|
418
|
+
? "osdy-pi working tree disabled"
|
|
419
|
+
: "osdy-pi working tree disabled but could not be saved",
|
|
420
|
+
saved ? "info" : "warning",
|
|
421
|
+
);
|
|
295
422
|
return;
|
|
296
423
|
}
|
|
297
424
|
if (action === "toggle") {
|
|
@@ -304,6 +431,7 @@ async function handleWorkingTreeCommand(
|
|
|
304
431
|
state,
|
|
305
432
|
workingState,
|
|
306
433
|
workingTreeState,
|
|
434
|
+
settingsStore,
|
|
307
435
|
);
|
|
308
436
|
return;
|
|
309
437
|
}
|
|
@@ -360,10 +488,13 @@ function registerCommand(
|
|
|
360
488
|
workingTreeState: WorkingTreeState,
|
|
361
489
|
controller: WorkingController,
|
|
362
490
|
settingsStore: ReturnType<typeof createAudioSoundSettingsStore>,
|
|
491
|
+
editorSettingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
492
|
+
startResponsive: () => void,
|
|
493
|
+
stopResponsive: () => void,
|
|
363
494
|
): void {
|
|
364
495
|
pi.registerCommand("osdy-pi", {
|
|
365
496
|
description:
|
|
366
|
-
"Manage the Osdy Pi experience: enable, disable, status, style, sound setup, working tree, or diff panel.",
|
|
497
|
+
"Manage the Osdy Pi experience: enable, disable, status, editor, style, sound setup, working tree, or diff panel.",
|
|
367
498
|
getArgumentCompletions: getOsdyCommandCompletions,
|
|
368
499
|
handler: async (args, ctx) => {
|
|
369
500
|
const [action = "status", ...rest] = parseCommandArgs(args);
|
|
@@ -376,10 +507,17 @@ function registerCommand(
|
|
|
376
507
|
workingTreeState,
|
|
377
508
|
controller,
|
|
378
509
|
);
|
|
510
|
+
startResponsive();
|
|
379
511
|
return;
|
|
380
512
|
}
|
|
381
513
|
if (action === "disable") {
|
|
382
|
-
disableOsdyPiCommand(
|
|
514
|
+
disableOsdyPiCommand(
|
|
515
|
+
ctx,
|
|
516
|
+
state,
|
|
517
|
+
workingTreeState,
|
|
518
|
+
controller,
|
|
519
|
+
stopResponsive,
|
|
520
|
+
);
|
|
383
521
|
return;
|
|
384
522
|
}
|
|
385
523
|
if (action === "status") {
|
|
@@ -390,6 +528,18 @@ function registerCommand(
|
|
|
390
528
|
await handleSoundCommand(rest, ctx, settingsStore);
|
|
391
529
|
return;
|
|
392
530
|
}
|
|
531
|
+
if (action === "editor") {
|
|
532
|
+
await handleEditorCommand(
|
|
533
|
+
rest[0],
|
|
534
|
+
rest.slice(1),
|
|
535
|
+
pi,
|
|
536
|
+
ctx,
|
|
537
|
+
state,
|
|
538
|
+
workingTreeState,
|
|
539
|
+
editorSettingsStore,
|
|
540
|
+
);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
393
543
|
if (action === "working-tree") {
|
|
394
544
|
await handleWorkingTreeCommand(
|
|
395
545
|
rest[0],
|
|
@@ -399,6 +549,7 @@ function registerCommand(
|
|
|
399
549
|
state,
|
|
400
550
|
workingState,
|
|
401
551
|
workingTreeState,
|
|
552
|
+
editorSettingsStore,
|
|
402
553
|
);
|
|
403
554
|
return;
|
|
404
555
|
}
|
|
@@ -407,18 +558,11 @@ function registerCommand(
|
|
|
407
558
|
return;
|
|
408
559
|
}
|
|
409
560
|
if (isHeaderVariant(action)) {
|
|
410
|
-
setHeaderVariant(
|
|
411
|
-
pi,
|
|
412
|
-
ctx,
|
|
413
|
-
state,
|
|
414
|
-
workingState,
|
|
415
|
-
workingTreeState,
|
|
416
|
-
action,
|
|
417
|
-
);
|
|
561
|
+
setHeaderVariant(pi, ctx, state, workingState, workingTreeState, action);
|
|
418
562
|
return;
|
|
419
563
|
}
|
|
420
564
|
ctx.ui.notify(
|
|
421
|
-
"Usage: /osdy-pi enable | disable | status | sound setup | working-tree ... | diff | osdy-theme | classic",
|
|
565
|
+
"Usage: /osdy-pi enable | disable | status | editor auto|extended|simple|on|off|toggle|status | sound setup | working-tree ... | diff | osdy-theme | classic",
|
|
422
566
|
"warning",
|
|
423
567
|
);
|
|
424
568
|
},
|
|
@@ -428,14 +572,7 @@ function registerCommand(
|
|
|
428
572
|
pi.registerCommand(`osdy-pi-${variant}`, {
|
|
429
573
|
description: `Switch Osdy Pi header to ${variant}.`,
|
|
430
574
|
handler: (_args, ctx) => {
|
|
431
|
-
setHeaderVariant(
|
|
432
|
-
pi,
|
|
433
|
-
ctx,
|
|
434
|
-
state,
|
|
435
|
-
workingState,
|
|
436
|
-
workingTreeState,
|
|
437
|
-
variant,
|
|
438
|
-
);
|
|
575
|
+
setHeaderVariant(pi, ctx, state, workingState, workingTreeState, variant);
|
|
439
576
|
return Promise.resolve();
|
|
440
577
|
},
|
|
441
578
|
});
|
|
@@ -445,9 +582,11 @@ function registerCommand(
|
|
|
445
582
|
export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
446
583
|
const state: OsdyState = {
|
|
447
584
|
enabled: true,
|
|
585
|
+
editorEffective: false,
|
|
586
|
+
editorMode: DEFAULT_EDITOR_MODE,
|
|
448
587
|
headerVariant: "osdy-theme",
|
|
449
|
-
|
|
450
|
-
|
|
588
|
+
smallMode: false,
|
|
589
|
+
tui: undefined,
|
|
451
590
|
workingTreeEnabled: true,
|
|
452
591
|
workingTreePlacement: "aboveEditor",
|
|
453
592
|
};
|
|
@@ -461,13 +600,16 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
461
600
|
const workingTreeState: WorkingTreeState = {
|
|
462
601
|
enabled: true,
|
|
463
602
|
loading: false,
|
|
603
|
+
visible: true,
|
|
464
604
|
snapshot: null,
|
|
465
605
|
error: undefined,
|
|
466
606
|
tui: undefined,
|
|
467
607
|
};
|
|
468
608
|
const controller = createWorkingController(state, workingState);
|
|
469
609
|
const settingsStore = createAudioSoundSettingsStore();
|
|
610
|
+
const editorSettingsStore = createEditorSettingsStore();
|
|
470
611
|
registerAudioNotificationFlags(pi);
|
|
612
|
+
registerAccountProfilesCommand(pi);
|
|
471
613
|
const audioRouter = createAudioEventRouter(
|
|
472
614
|
createAudioNotificationService(
|
|
473
615
|
pi,
|
|
@@ -475,6 +617,23 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
475
617
|
settingsStore,
|
|
476
618
|
),
|
|
477
619
|
);
|
|
620
|
+
let responsiveCoordinator:
|
|
621
|
+
| ReturnType<typeof createResponsiveCoordinator>
|
|
622
|
+
| undefined;
|
|
623
|
+
let sessionContext: ExtensionContext | undefined;
|
|
624
|
+
const pendingOsdyRefreshes = new Set<ReturnType<typeof setTimeout>>();
|
|
625
|
+
const cancelOsdyRefreshes = (): void => {
|
|
626
|
+
for (const timeout of pendingOsdyRefreshes) clearTimeout(timeout);
|
|
627
|
+
pendingOsdyRefreshes.clear();
|
|
628
|
+
};
|
|
629
|
+
const startResponsive = (): void => responsiveCoordinator?.start();
|
|
630
|
+
const stopResponsive = (): void => responsiveCoordinator?.stop();
|
|
631
|
+
|
|
632
|
+
subscribeQuestionPromptAudioNotification(
|
|
633
|
+
pi,
|
|
634
|
+
{ getCurrentSessionContext: () => sessionContext },
|
|
635
|
+
audioRouter,
|
|
636
|
+
);
|
|
478
637
|
|
|
479
638
|
pi.on("agent_start", () => {
|
|
480
639
|
controller.onAgentStart();
|
|
@@ -490,17 +649,47 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
490
649
|
pi.on("tool_execution_end", (event, ctx) => {
|
|
491
650
|
controller.onToolEnd();
|
|
492
651
|
audioRouter.onToolExecutionEnd(event.isError === true, ctx);
|
|
493
|
-
if (
|
|
652
|
+
if (
|
|
653
|
+
state.workingTreeEnabled &&
|
|
654
|
+
event.isError !== true &&
|
|
655
|
+
shouldRefreshWorkingTree(event.toolName)
|
|
656
|
+
) {
|
|
494
657
|
void refreshWorkingTree(pi, ctx, workingTreeState);
|
|
495
658
|
}
|
|
496
659
|
});
|
|
497
660
|
pi.on("session_shutdown", () => {
|
|
661
|
+
cancelOsdyRefreshes();
|
|
498
662
|
controller.onShutdown();
|
|
663
|
+
stopResponsive();
|
|
664
|
+
if (sessionContext) disableOsdyPi(sessionContext, state);
|
|
499
665
|
workingTreeState.tui = undefined;
|
|
666
|
+
sessionContext = undefined;
|
|
500
667
|
});
|
|
501
|
-
pi.on("session_start", (_event, ctx) => {
|
|
502
|
-
|
|
503
|
-
|
|
668
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
669
|
+
cancelOsdyRefreshes();
|
|
670
|
+
stopResponsive();
|
|
671
|
+
sessionContext = ctx;
|
|
672
|
+
const editorSettings = await editorSettingsStore.load();
|
|
673
|
+
state.editorMode = editorSettings.editorMode;
|
|
674
|
+
state.workingTreeEnabled = editorSettings.workingTreeEnabled;
|
|
675
|
+
workingTreeState.enabled = editorSettings.workingTreeEnabled;
|
|
676
|
+
if (sessionContext !== ctx) return;
|
|
677
|
+
claimOsdyVisualLayer(
|
|
678
|
+
pi,
|
|
679
|
+
ctx,
|
|
680
|
+
state,
|
|
681
|
+
workingState,
|
|
682
|
+
workingTreeState,
|
|
683
|
+
pendingOsdyRefreshes,
|
|
684
|
+
() => sessionContext === ctx,
|
|
685
|
+
);
|
|
686
|
+
responsiveCoordinator = createResponsiveCoordinator(
|
|
687
|
+
pi,
|
|
688
|
+
ctx,
|
|
689
|
+
state,
|
|
690
|
+
workingTreeState,
|
|
691
|
+
);
|
|
692
|
+
responsiveCoordinator.start();
|
|
504
693
|
if (state.workingTreeEnabled) {
|
|
505
694
|
void refreshWorkingTree(pi, ctx, workingTreeState);
|
|
506
695
|
}
|
|
@@ -513,5 +702,8 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
513
702
|
workingTreeState,
|
|
514
703
|
controller,
|
|
515
704
|
settingsStore,
|
|
705
|
+
editorSettingsStore,
|
|
706
|
+
startResponsive,
|
|
707
|
+
stopResponsive,
|
|
516
708
|
);
|
|
517
709
|
}
|
|
@@ -2,16 +2,45 @@ import type { TUI } from "@earendil-works/pi-tui";
|
|
|
2
2
|
|
|
3
3
|
export type AnimationMode = "off" | "intro" | "continuous";
|
|
4
4
|
|
|
5
|
+
export const EDITOR_MODES = {
|
|
6
|
+
AUTO: "auto",
|
|
7
|
+
EXTENDED: "extended",
|
|
8
|
+
SIMPLE: "simple",
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type EditorMode = (typeof EDITOR_MODES)[keyof typeof EDITOR_MODES];
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_EDITOR_MODE = EDITOR_MODES.AUTO;
|
|
14
|
+
|
|
15
|
+
export interface GlobalEditorSettings {
|
|
16
|
+
version: number;
|
|
17
|
+
editorMode: EditorMode;
|
|
18
|
+
workingTreeEnabled: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveEffectiveEditorMode(
|
|
22
|
+
mode: EditorMode,
|
|
23
|
+
smallMode: boolean,
|
|
24
|
+
): EditorMode {
|
|
25
|
+
if (smallMode || mode === EDITOR_MODES.SIMPLE) return EDITOR_MODES.SIMPLE;
|
|
26
|
+
return EDITOR_MODES.EXTENDED;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function shouldShowFooterMetadata(editorEffective: boolean): boolean {
|
|
30
|
+
return !editorEffective;
|
|
31
|
+
}
|
|
32
|
+
|
|
5
33
|
export type HeaderVariant = "osdy-theme" | "classic";
|
|
6
34
|
|
|
7
35
|
export type WorkingTreePlacement = "aboveEditor" | "belowEditor";
|
|
8
36
|
|
|
9
37
|
export type OsdyState = {
|
|
10
38
|
enabled: boolean;
|
|
39
|
+
editorEffective: boolean;
|
|
40
|
+
editorMode: EditorMode;
|
|
11
41
|
headerVariant: HeaderVariant;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
agentsLabel: string;
|
|
42
|
+
smallMode: boolean;
|
|
43
|
+
tui: TUI | undefined;
|
|
15
44
|
workingTreeEnabled: boolean;
|
|
16
45
|
workingTreePlacement: WorkingTreePlacement;
|
|
17
46
|
};
|
|
@@ -46,6 +75,7 @@ export type WorkingTreeSnapshot = {
|
|
|
46
75
|
export type WorkingTreeState = {
|
|
47
76
|
enabled: boolean;
|
|
48
77
|
loading: boolean;
|
|
78
|
+
visible: boolean;
|
|
49
79
|
snapshot: WorkingTreeSnapshot | null;
|
|
50
80
|
error: string | undefined;
|
|
51
81
|
tui: TUI | undefined;
|
|
@@ -72,13 +102,6 @@ export type AssistantSessionEntry = {
|
|
|
72
102
|
};
|
|
73
103
|
};
|
|
74
104
|
|
|
75
|
-
export type HeaderMetaRow = {
|
|
76
|
-
leftLabel: string;
|
|
77
|
-
leftValue: string;
|
|
78
|
-
rightLabel: string;
|
|
79
|
-
rightValue: string;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
105
|
export type SimpleTheme = {
|
|
83
106
|
fg(name: string, text: string): string;
|
|
84
107
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
// @ts-expect-error Node's native TypeScript runner resolves test-only TypeScript source imports.
|
|
5
|
+
import * as profileLabel from "./profile-label.ts";
|
|
6
|
+
// @ts-expect-error Node's native TypeScript runner resolves test-only TypeScript source imports.
|
|
7
|
+
import { shouldShowFooterMetadata } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
void test("footer metadata is shown only when the native editor is effective", () => {
|
|
10
|
+
assert.equal(shouldShowFooterMetadata(false), true);
|
|
11
|
+
assert.equal(shouldShowFooterMetadata(true), false);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
void test("maximum thinking uses the existing extra-high theme color", () => {
|
|
15
|
+
const source = readFileSync(new URL("./ui.ts", import.meta.url), "utf8");
|
|
16
|
+
assert.match(source, /max:\s*"thinkingXhigh"/);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
void test("places a managed profile beside the model in simple mode and preserves its fallback", () => {
|
|
20
|
+
assert.equal(
|
|
21
|
+
profileLabel.resolveActiveProfileLabel({ OSDY_PI_PROFILE_NAME: "work" }),
|
|
22
|
+
"work",
|
|
23
|
+
);
|
|
24
|
+
assert.equal(profileLabel.resolveActiveProfileLabel({}), undefined);
|
|
25
|
+
assert.equal(
|
|
26
|
+
profileLabel.formatModelMetadata("gpt-5", "high", "work"),
|
|
27
|
+
"gpt-5 · work · think high",
|
|
28
|
+
);
|
|
29
|
+
assert.equal(
|
|
30
|
+
profileLabel.formatModelMetadata(
|
|
31
|
+
"gpt-5",
|
|
32
|
+
"high",
|
|
33
|
+
profileLabel.resolveActiveProfileLabel({
|
|
34
|
+
OSDY_PI_PROFILE_NAME: "untrusted/profile",
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
"gpt-5 · think high",
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
void test("replaces the extended editor Osdy-Pi title with a managed profile or preserves its fallback", () => {
|
|
42
|
+
assert.equal(profileLabel.resolveEditorTitleLabel("work"), "work");
|
|
43
|
+
assert.equal(
|
|
44
|
+
profileLabel.resolveEditorTitleLabel(
|
|
45
|
+
profileLabel.resolveActiveProfileLabel({
|
|
46
|
+
OSDY_PI_PROFILE_NAME: "untrusted/profile",
|
|
47
|
+
}),
|
|
48
|
+
),
|
|
49
|
+
"Osdy-Pi",
|
|
50
|
+
);
|
|
51
|
+
});
|