@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.
@@ -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, 90)}`;
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 && !session.hidden;
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 promptOpen = () => prompt.visible || question.visible || directoryPicker.isOpen();
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
- const hideSession = await askConfirm(`Remote delete failed for ${shortId(session.id)}. Hide from remote workbench instead?`);
803
- if (hideSession) {
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