@bramblex/codex-workbench 0.1.14 → 0.1.15
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 +60 -20
- package/package.json +2 -2
- package/src/cli-output.js +38 -26
- package/src/cli.js +18 -3
- package/src/config.js +44 -4
- package/src/model/metadata.js +44 -0
- package/src/model/session-store.js +39 -116
- package/src/model/workbench-config.js +48 -12
- package/src/providers/codex.js +267 -0
- package/src/providers/index.js +59 -0
- package/src/providers/pi.js +326 -0
- package/src/services/codex-runner.js +27 -62
- package/src/services/session-sources.js +52 -8
- package/src/ui/workbench.js +84 -18
package/src/ui/workbench.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
LOCAL_SOURCE,
|
|
11
11
|
createSourceDirectory,
|
|
12
12
|
listSourceDirectories,
|
|
13
|
+
listSourceBackends,
|
|
13
14
|
loadLocalWorkbenchSessions,
|
|
14
15
|
loadRemoteSourceSessions,
|
|
15
16
|
loadWorkbenchSessions,
|
|
@@ -149,6 +150,27 @@ async function runWorkbench() {
|
|
|
149
150
|
style: { border: { fg: 'red' }, fg: 'white', bg: 'black' },
|
|
150
151
|
});
|
|
151
152
|
|
|
153
|
+
const backendPicker = blessed.list({
|
|
154
|
+
parent: screen,
|
|
155
|
+
label: ' Backend ',
|
|
156
|
+
top: 'center',
|
|
157
|
+
left: 'center',
|
|
158
|
+
width: 42,
|
|
159
|
+
height: 8,
|
|
160
|
+
border: 'line',
|
|
161
|
+
hidden: true,
|
|
162
|
+
mouse: true,
|
|
163
|
+
keys: true,
|
|
164
|
+
vi: false,
|
|
165
|
+
style: {
|
|
166
|
+
border: { fg: 'yellow' },
|
|
167
|
+
selected: { fg: 'black', bg: 'yellow', bold: true },
|
|
168
|
+
item: { fg: 'white' },
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
let backendPickerState = null;
|
|
173
|
+
|
|
152
174
|
const sessionsForSource = (sourceId) => sessions.filter((session) => session.sourceId === sourceId);
|
|
153
175
|
|
|
154
176
|
const buildGroups = () => {
|
|
@@ -223,12 +245,13 @@ async function runWorkbench() {
|
|
|
223
245
|
|
|
224
246
|
const sessionLabel = (session) => {
|
|
225
247
|
const flags = [
|
|
248
|
+
session.backend || '',
|
|
226
249
|
session.name ? 'renamed' : '',
|
|
227
250
|
session.note ? 'note' : '',
|
|
228
251
|
].filter(Boolean).join(',');
|
|
229
252
|
const title = session.name || session.first || session.last || '(no prompt)';
|
|
230
253
|
const flagText = flags ? `[${flags}]` : '';
|
|
231
|
-
return `${shortId(session.id)} ${String(session.turns).padStart(2)}t ${truncate(localTime(session.updatedAt), 18)} ${flagText} ${truncate(title,
|
|
254
|
+
return `${shortId(session.id)} ${String(session.turns).padStart(2)}t ${truncate(localTime(session.updatedAt), 18)} ${flagText} ${truncate(title, 88)}`;
|
|
232
255
|
};
|
|
233
256
|
|
|
234
257
|
const detailContent = (session) => {
|
|
@@ -238,6 +261,7 @@ async function runWorkbench() {
|
|
|
238
261
|
title,
|
|
239
262
|
'',
|
|
240
263
|
`id: ${session.id}`,
|
|
264
|
+
`backend: ${session.backend || 'unknown'}`,
|
|
241
265
|
`source: ${session.sourceLabel || 'Local'}`,
|
|
242
266
|
`cwd: ${session.cwd}`,
|
|
243
267
|
`started: ${localTime(session.startedAt)}`,
|
|
@@ -256,7 +280,7 @@ async function runWorkbench() {
|
|
|
256
280
|
status.style.fg = isError ? 'red' : 'white';
|
|
257
281
|
};
|
|
258
282
|
|
|
259
|
-
const visibleSession = (session) => !session.archived
|
|
283
|
+
const visibleSession = (session) => !session.archived;
|
|
260
284
|
|
|
261
285
|
const sortSessionList = (list) => {
|
|
262
286
|
list.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
|
|
@@ -463,6 +487,31 @@ async function runWorkbench() {
|
|
|
463
487
|
question.ask(label, (err, answer) => resolve(!err && Boolean(answer)));
|
|
464
488
|
});
|
|
465
489
|
|
|
490
|
+
const askBackend = (source) => new Promise((resolve) => {
|
|
491
|
+
let backends = [];
|
|
492
|
+
try {
|
|
493
|
+
backends = listSourceBackends(source);
|
|
494
|
+
} catch (err) {
|
|
495
|
+
setMessage(`error: ${err.message}`, true);
|
|
496
|
+
render();
|
|
497
|
+
resolve(null);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (backends.length <= 1) {
|
|
501
|
+
resolve(backends[0] ? backends[0].id : null);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
backendPickerState = { backends, resolve };
|
|
506
|
+
backendPicker.clearItems();
|
|
507
|
+
backendPicker.setItems(backends.map((backend) => `${backend.id} ${backend.label || backend.id}`));
|
|
508
|
+
backendPicker.select(0);
|
|
509
|
+
backendPicker.show();
|
|
510
|
+
backendPicker.setFront();
|
|
511
|
+
backendPicker.focus();
|
|
512
|
+
screen.render();
|
|
513
|
+
});
|
|
514
|
+
|
|
466
515
|
const directoryPicker = createDirectoryPicker({
|
|
467
516
|
askInput,
|
|
468
517
|
focusOnClose: () => focusPanel(projectsList, 'projects'),
|
|
@@ -470,7 +519,16 @@ async function runWorkbench() {
|
|
|
470
519
|
truncate,
|
|
471
520
|
});
|
|
472
521
|
|
|
473
|
-
const
|
|
522
|
+
const closeBackendPicker = (backend = null) => {
|
|
523
|
+
if (!backendPickerState) return;
|
|
524
|
+
const { resolve } = backendPickerState;
|
|
525
|
+
backendPickerState = null;
|
|
526
|
+
backendPicker.hide();
|
|
527
|
+
focusPanel(sessionsList, 'sessions');
|
|
528
|
+
resolve(backend);
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
const promptOpen = () => prompt.visible || question.visible || directoryPicker.isOpen() || Boolean(backendPickerState);
|
|
474
532
|
|
|
475
533
|
const leaveScreen = () => {
|
|
476
534
|
closed = true;
|
|
@@ -573,17 +631,23 @@ async function runWorkbench() {
|
|
|
573
631
|
createDirectory: (parent, name) => createSourceDirectory(source, parent, name),
|
|
574
632
|
});
|
|
575
633
|
|
|
576
|
-
const runNewCodexAndReturn = (cwd, args = []) => {
|
|
634
|
+
const runNewCodexAndReturn = async (cwd, args = []) => {
|
|
577
635
|
const source = currentSource();
|
|
578
636
|
const resolvedCwd = source.remote ? cwd : usableCwd(cwd);
|
|
637
|
+
const backend = await askBackend(source);
|
|
638
|
+
if (!backend) {
|
|
639
|
+
setMessage('New session cancelled.');
|
|
640
|
+
render();
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
579
643
|
screen.leave();
|
|
580
644
|
let status = 0;
|
|
581
645
|
try {
|
|
582
|
-
status = runSourceNewSession(source, resolvedCwd, args);
|
|
646
|
+
status = runSourceNewSession(source, resolvedCwd, args, backend);
|
|
583
647
|
} finally {
|
|
584
648
|
screen.enter();
|
|
585
649
|
}
|
|
586
|
-
const label = source.remote ? `${source.label}: ${resolvedCwd}` : resolvedCwd
|
|
650
|
+
const label = source.remote ? `${source.label}: ${resolvedCwd}` : `${resolvedCwd} (${backend})`;
|
|
587
651
|
if (status === 0) refreshAfterAction(`New session finished in ${label}.`, false, resolvedCwd, source.id);
|
|
588
652
|
else refreshAfterAction(`new session exited with code ${status}.`, true, resolvedCwd, source.id);
|
|
589
653
|
return status;
|
|
@@ -695,6 +759,16 @@ async function runWorkbench() {
|
|
|
695
759
|
focusPanel(projectsList, 'projects');
|
|
696
760
|
});
|
|
697
761
|
|
|
762
|
+
backendPicker.key(['enter'], () => {
|
|
763
|
+
if (!backendPickerState) return;
|
|
764
|
+
const backend = backendPickerState.backends[backendPicker.selected];
|
|
765
|
+
closeBackendPicker(backend ? backend.id : null);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
backendPicker.key(['escape', 'q'], () => {
|
|
769
|
+
closeBackendPicker(null);
|
|
770
|
+
});
|
|
771
|
+
|
|
698
772
|
screen.on('resize', () => {
|
|
699
773
|
applyLayout();
|
|
700
774
|
if (directoryPicker.isOpen()) directoryPicker.applyLayout();
|
|
@@ -766,10 +840,10 @@ async function runWorkbench() {
|
|
|
766
840
|
setMessage('New project cancelled.');
|
|
767
841
|
return render();
|
|
768
842
|
}
|
|
769
|
-
runNewCodexAndReturn(dir);
|
|
843
|
+
await runNewCodexAndReturn(dir);
|
|
770
844
|
return;
|
|
771
845
|
}
|
|
772
|
-
runNewCodexAndReturn(currentProjectCwd());
|
|
846
|
+
await runNewCodexAndReturn(currentProjectCwd());
|
|
773
847
|
});
|
|
774
848
|
|
|
775
849
|
screen.key(['r'], () => runAction(async (session) => {
|
|
@@ -799,11 +873,8 @@ async function runWorkbench() {
|
|
|
799
873
|
const status = runCodexAndReturn('delete', session, ['--force'], `Deleted ${shortId(session.id)}.`);
|
|
800
874
|
if (status !== 0) {
|
|
801
875
|
if (session.sourceRemote) {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
updateSourceMetadata(session, { hidden: true });
|
|
805
|
-
refreshAfterAction(`Hidden ${shortId(session.id)}.`);
|
|
806
|
-
}
|
|
876
|
+
setMessage(`Remote delete failed for ${shortId(session.id)}.`, true);
|
|
877
|
+
render();
|
|
807
878
|
return;
|
|
808
879
|
}
|
|
809
880
|
const removeFile = await askConfirm(`Codex could not delete ${shortId(session.id)}. Delete its session file?`);
|
|
@@ -812,11 +883,6 @@ async function runWorkbench() {
|
|
|
812
883
|
refreshAfterAction(`Deleted file for ${shortId(session.id)}.`);
|
|
813
884
|
return;
|
|
814
885
|
}
|
|
815
|
-
const hideSession = await askConfirm(`Hide ${shortId(session.id)} from workbench instead?`);
|
|
816
|
-
if (hideSession) {
|
|
817
|
-
updateSourceMetadata(session, { hidden: true });
|
|
818
|
-
refreshAfterAction(`Hidden ${shortId(session.id)}.`);
|
|
819
|
-
}
|
|
820
886
|
}
|
|
821
887
|
}));
|
|
822
888
|
|