wepscli 0.1.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.
Files changed (130) hide show
  1. package/README.md +293 -0
  2. package/dist/WEPSCLI-shell/agent-runtime.js +824 -0
  3. package/dist/WEPSCLI-shell/agent-runtime.js.map +1 -0
  4. package/dist/WEPSCLI-shell/approval-overlay.js +275 -0
  5. package/dist/WEPSCLI-shell/approval-overlay.js.map +1 -0
  6. package/dist/WEPSCLI-shell/chat-components.js +760 -0
  7. package/dist/WEPSCLI-shell/chat-components.js.map +1 -0
  8. package/dist/WEPSCLI-shell/components.js +850 -0
  9. package/dist/WEPSCLI-shell/components.js.map +1 -0
  10. package/dist/WEPSCLI-shell/config-overlays.js +205 -0
  11. package/dist/WEPSCLI-shell/config-overlays.js.map +1 -0
  12. package/dist/WEPSCLI-shell/debug-log.js +16 -0
  13. package/dist/WEPSCLI-shell/debug-log.js.map +1 -0
  14. package/dist/WEPSCLI-shell/file-change-preview.js +261 -0
  15. package/dist/WEPSCLI-shell/file-change-preview.js.map +1 -0
  16. package/dist/WEPSCLI-shell/helpers.js +112 -0
  17. package/dist/WEPSCLI-shell/helpers.js.map +1 -0
  18. package/dist/WEPSCLI-shell/index.js +3 -0
  19. package/dist/WEPSCLI-shell/index.js.map +1 -0
  20. package/dist/WEPSCLI-shell/provider-add-flow.js +406 -0
  21. package/dist/WEPSCLI-shell/provider-add-flow.js.map +1 -0
  22. package/dist/WEPSCLI-shell/run-wepscli-shell.js +21 -0
  23. package/dist/WEPSCLI-shell/run-wepscli-shell.js.map +1 -0
  24. package/dist/WEPSCLI-shell/runtime-recovery.js +37 -0
  25. package/dist/WEPSCLI-shell/runtime-recovery.js.map +1 -0
  26. package/dist/WEPSCLI-shell/runtime-status.js +66 -0
  27. package/dist/WEPSCLI-shell/runtime-status.js.map +1 -0
  28. package/dist/WEPSCLI-shell/shell-app.js +1047 -0
  29. package/dist/WEPSCLI-shell/shell-app.js.map +1 -0
  30. package/dist/WEPSCLI-shell/shell-modes.js +77 -0
  31. package/dist/WEPSCLI-shell/shell-modes.js.map +1 -0
  32. package/dist/WEPSCLI-shell/slash-commands.js +135 -0
  33. package/dist/WEPSCLI-shell/slash-commands.js.map +1 -0
  34. package/dist/WEPSCLI-shell/theme.js +19 -0
  35. package/dist/WEPSCLI-shell/theme.js.map +1 -0
  36. package/dist/WEPSCLI-shell/tool-approval.js +85 -0
  37. package/dist/WEPSCLI-shell/tool-approval.js.map +1 -0
  38. package/dist/WEPSCLI-shell/tool-diff.js +76 -0
  39. package/dist/WEPSCLI-shell/tool-diff.js.map +1 -0
  40. package/dist/WEPSCLI-shell/tool-file-changes.js +268 -0
  41. package/dist/WEPSCLI-shell/tool-file-changes.js.map +1 -0
  42. package/dist/WEPSCLI-shell/tool-message-detail.js +138 -0
  43. package/dist/WEPSCLI-shell/tool-message-detail.js.map +1 -0
  44. package/dist/WEPSCLI-shell/tool-messages.js +145 -0
  45. package/dist/WEPSCLI-shell/tool-messages.js.map +1 -0
  46. package/dist/WEPSCLI-shell/transcript-panel.js +372 -0
  47. package/dist/WEPSCLI-shell/transcript-panel.js.map +1 -0
  48. package/dist/WEPSCLI-shell/transcript-state.js +62 -0
  49. package/dist/WEPSCLI-shell/transcript-state.js.map +1 -0
  50. package/dist/WEPSCLI-shell/types.js +1 -0
  51. package/dist/WEPSCLI-shell/types.js.map +1 -0
  52. package/dist/cli.js +11 -0
  53. package/dist/cli.js.map +1 -0
  54. package/dist/config.js +40 -0
  55. package/dist/config.js.map +1 -0
  56. package/dist/index.js +4 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/main.js +140 -0
  59. package/dist/main.js.map +1 -0
  60. package/dist/onboarding/action-screen.js +90 -0
  61. package/dist/onboarding/action-screen.js.map +1 -0
  62. package/dist/onboarding/framed-screen.js +35 -0
  63. package/dist/onboarding/framed-screen.js.map +1 -0
  64. package/dist/onboarding/onboarding-app.js +312 -0
  65. package/dist/onboarding/onboarding-app.js.map +1 -0
  66. package/dist/onboarding/run-onboarding.js +23 -0
  67. package/dist/onboarding/run-onboarding.js.map +1 -0
  68. package/dist/onboarding/select-screen.js +21 -0
  69. package/dist/onboarding/select-screen.js.map +1 -0
  70. package/dist/onboarding/summary-screen.js +23 -0
  71. package/dist/onboarding/summary-screen.js.map +1 -0
  72. package/dist/onboarding/text-input-screen.js +55 -0
  73. package/dist/onboarding/text-input-screen.js.map +1 -0
  74. package/dist/onboarding/theme.js +26 -0
  75. package/dist/onboarding/theme.js.map +1 -0
  76. package/dist/provider-profiles/api-key-store.js +51 -0
  77. package/dist/provider-profiles/api-key-store.js.map +1 -0
  78. package/dist/provider-profiles/defaults.js +18 -0
  79. package/dist/provider-profiles/defaults.js.map +1 -0
  80. package/dist/provider-profiles/fetch-models.js +53 -0
  81. package/dist/provider-profiles/fetch-models.js.map +1 -0
  82. package/dist/provider-profiles/index.js +6 -0
  83. package/dist/provider-profiles/index.js.map +1 -0
  84. package/dist/provider-profiles/provider-profile-service.js +223 -0
  85. package/dist/provider-profiles/provider-profile-service.js.map +1 -0
  86. package/dist/provider-profiles/providers-config-store.js +17 -0
  87. package/dist/provider-profiles/providers-config-store.js.map +1 -0
  88. package/dist/provider-profiles/types.js +1 -0
  89. package/dist/provider-profiles/types.js.map +1 -0
  90. package/dist/session-history/session-history-service.js +142 -0
  91. package/dist/session-history/session-history-service.js.map +1 -0
  92. package/dist/shell/animator.js +30 -0
  93. package/dist/shell/animator.js.map +1 -0
  94. package/dist/shell/clickables.js +101 -0
  95. package/dist/shell/clickables.js.map +1 -0
  96. package/dist/shell/dashboard-shell.js +292 -0
  97. package/dist/shell/dashboard-shell.js.map +1 -0
  98. package/dist/shell/index.js +5 -0
  99. package/dist/shell/index.js.map +1 -0
  100. package/dist/shell/keymap.js +14 -0
  101. package/dist/shell/keymap.js.map +1 -0
  102. package/dist/shell/mouse.js +39 -0
  103. package/dist/shell/mouse.js.map +1 -0
  104. package/dist/shell/render.js +122 -0
  105. package/dist/shell/render.js.map +1 -0
  106. package/dist/shell/run-shell.js +36 -0
  107. package/dist/shell/run-shell.js.map +1 -0
  108. package/dist/shell/theme.js +56 -0
  109. package/dist/shell/theme.js.map +1 -0
  110. package/dist/storage/locked-json-file.js +88 -0
  111. package/dist/storage/locked-json-file.js.map +1 -0
  112. package/dist/workbench/animator.js +30 -0
  113. package/dist/workbench/animator.js.map +1 -0
  114. package/dist/workbench/index.js +6 -0
  115. package/dist/workbench/index.js.map +1 -0
  116. package/dist/workbench/mouse.js +39 -0
  117. package/dist/workbench/mouse.js.map +1 -0
  118. package/dist/workbench/render.js +82 -0
  119. package/dist/workbench/render.js.map +1 -0
  120. package/dist/workbench/renderer.js +364 -0
  121. package/dist/workbench/renderer.js.map +1 -0
  122. package/dist/workbench/run-workbench.js +36 -0
  123. package/dist/workbench/run-workbench.js.map +1 -0
  124. package/dist/workbench/theme.js +63 -0
  125. package/dist/workbench/theme.js.map +1 -0
  126. package/dist/workbench/types.js +1 -0
  127. package/dist/workbench/types.js.map +1 -0
  128. package/dist/workbench/workbench-shell.js +649 -0
  129. package/dist/workbench/workbench-shell.js.map +1 -0
  130. package/package.json +65 -0
@@ -0,0 +1,649 @@
1
+ import { getKeybindings, Input, Key, matchesKey } from "@mariozechner/pi-tui";
2
+ import { getAgentDir } from "../config.js";
3
+ import { WorkbenchAnimator } from "./animator.js";
4
+ import { computeWorkbenchLayout } from "./render.js";
5
+ import { renderWorkbench } from "./renderer.js";
6
+ import { workbenchTheme } from "./theme.js";
7
+ export class WorkbenchShell {
8
+ composer = new Input();
9
+ timeline = [];
10
+ sessions = [{
11
+ id: "wepscli-shell-rebuild",
12
+ title: "Continue WEPSCLI shell rebuild",
13
+ summary: "Track the OpenCode-first workbench rewrite and validate the new control surfaces.",
14
+ state: "active",
15
+ updatedLabel: "Active now"
16
+ }, {
17
+ id: "provider-setup-pass",
18
+ title: "Provider setup sanity pass",
19
+ summary: "Keep onboarding stable while the new shell grows around the existing provider runtime.",
20
+ state: "recent",
21
+ updatedLabel: "Earlier today"
22
+ }, {
23
+ id: "visual-affordance-notes",
24
+ title: "Clickable affordance notes",
25
+ summary: "Capture which controls still feel like hidden targets and move them to cards, chips, or dialogs.",
26
+ state: "ready",
27
+ updatedLabel: "Queued"
28
+ }];
29
+ profiles = [];
30
+ view = "home";
31
+ focusRegion = "main";
32
+ railSelectionIndex = 0;
33
+ mainSelectionIndex = 0;
34
+ overlaySelectionIndex = 0;
35
+ toolbarRegions = [];
36
+ railRegions = [];
37
+ mainRegions = [];
38
+ dockRegions = [];
39
+ overlayRegions = [];
40
+ _focused = false;
41
+ constructor(ui, profileService, onExit) {
42
+ this.ui = ui;
43
+ this.profileService = profileService;
44
+ this.onExit = onExit;
45
+ this.animator = new WorkbenchAnimator(ui);
46
+ this.reloadProfiles();
47
+ this.seedTimeline();
48
+ this.composer.onSubmit = value => {
49
+ const trimmed = value.trim();
50
+ if (!trimmed) return;
51
+ if (this.handleComposerCommand(trimmed)) {
52
+ this.composer.setValue("");
53
+ this.ui.requestRender();
54
+ return;
55
+ }
56
+ this.pushTimeline(`Draft queued: ${trimmed}`);
57
+ this.ensureDraftSession(trimmed);
58
+ this.composer.setValue("");
59
+ this.ui.requestRender();
60
+ };
61
+ this.composer.onEscape = () => {
62
+ if (this.composer.getValue().trim()) {
63
+ return;
64
+ }
65
+ if (this.overlay) {
66
+ this.closeOverlay();
67
+ return;
68
+ }
69
+ this.onExit();
70
+ };
71
+ }
72
+ get focused() {
73
+ return this._focused;
74
+ }
75
+ set focused(value) {
76
+ this._focused = value;
77
+ this.composer.focused = value && this.focusRegion === "composer";
78
+ }
79
+ dispose() {
80
+ this.animator.dispose();
81
+ }
82
+ invalidate() {
83
+ this.composer.invalidate();
84
+ }
85
+ handleInput(data) {
86
+ const kb = getKeybindings();
87
+ if (matchesKey(data, Key.ctrl("c"))) {
88
+ this.onExit();
89
+ return;
90
+ }
91
+ if (this.overlay) {
92
+ this.handleOverlayInput(data);
93
+ return;
94
+ }
95
+ if (kb.matches(data, "tui.input.tab")) {
96
+ this.cycleFocus();
97
+ return;
98
+ }
99
+ if (this.focusRegion !== "composer" && this.handleShortcut(data)) {
100
+ return;
101
+ }
102
+ switch (this.focusRegion) {
103
+ case "rail":
104
+ this.handleRailInput(data);
105
+ return;
106
+ case "main":
107
+ this.handleMainInput(data);
108
+ return;
109
+ case "composer":
110
+ this.composer.handleInput(data);
111
+ return;
112
+ case "overlay":
113
+ return;
114
+ }
115
+ }
116
+ handleMouseEvent(event) {
117
+ if (event.action === "scroll") {
118
+ return this.handleScroll(event);
119
+ }
120
+ if (!this.isPrimaryMouseActivation(event)) {
121
+ return false;
122
+ }
123
+ if (this.overlay) {
124
+ const overlayHit = this.findHit(event.col, event.row, this.overlayRegions);
125
+ if (!overlayHit) {
126
+ return false;
127
+ }
128
+ this.focusRegion = "overlay";
129
+ this.activateAction(overlayHit.id);
130
+ return true;
131
+ }
132
+ if (this.composerRegion && this.pointInRect(event.col, event.row, this.composerRegion)) {
133
+ this.setFocusRegion("composer");
134
+ return true;
135
+ }
136
+ const region = this.findHit(event.col, event.row, this.toolbarRegions) ?? this.findHit(event.col, event.row, this.railRegions) ?? this.findHit(event.col, event.row, this.mainRegions) ?? this.findHit(event.col, event.row, this.dockRegions);
137
+ if (!region) {
138
+ return false;
139
+ }
140
+ this.setFocusRegion(region.group === "rail" ? "rail" : "main");
141
+ this.activateAction(region.id);
142
+ return true;
143
+ }
144
+ render(width) {
145
+ this.reloadProfiles();
146
+ const animation = this.animator.getSnapshot();
147
+ const mainCards = this.getMainCards();
148
+ const overlayOptions = this.getOverlayOptions();
149
+ this.mainSelectionIndex = mainCards.length === 0 ? 0 : Math.min(this.mainSelectionIndex, mainCards.length - 1);
150
+ this.overlaySelectionIndex = overlayOptions.length === 0 ? 0 : Math.min(this.overlaySelectionIndex, overlayOptions.length - 1);
151
+ const result = renderWorkbench({
152
+ width,
153
+ height: this.ui.terminal.rows,
154
+ view: this.view,
155
+ focusRegion: this.focusRegion,
156
+ overlay: this.overlay,
157
+ railSelectionIndex: this.railSelectionIndex,
158
+ mainSelectionIndex: this.mainSelectionIndex,
159
+ overlaySelectionIndex: this.overlaySelectionIndex,
160
+ frame: animation.frame,
161
+ focusPulseActive: animation.focusPulseActive,
162
+ showBody: animation.showBody,
163
+ showInspector: animation.showInspector,
164
+ showDock: animation.showDock,
165
+ profiles: this.profiles,
166
+ activeProfile: this.getActiveProfile(),
167
+ activeSelection: this.profileService.getActiveSelection(),
168
+ railItems: this.getRailItems(),
169
+ mainCards,
170
+ overlayOptions,
171
+ timeline: this.timeline,
172
+ sessionsCount: this.sessions.length,
173
+ agentDir: getAgentDir(),
174
+ renderComposer: innerWidth => this.composer.render(innerWidth),
175
+ styleButton: (label, variant) => this.styleButton(label, variant),
176
+ truncateLabel: (value, maxLength) => this.truncateLabel(value, maxLength),
177
+ getViewLabel: view => this.getViewLabel(view),
178
+ getFocusLabel: focus => this.getFocusLabel(focus)
179
+ });
180
+ this.toolbarRegions = result.toolbarRegions;
181
+ this.railRegions = result.railRegions;
182
+ this.mainRegions = result.mainRegions;
183
+ this.dockRegions = result.dockRegions;
184
+ this.overlayRegions = result.overlayRegions;
185
+ this.composerRegion = result.composerRegion;
186
+ return result.lines;
187
+ }
188
+ handleRailInput(data) {
189
+ const kb = getKeybindings();
190
+ const items = this.getRailItems();
191
+ if (kb.matches(data, "tui.select.cancel")) {
192
+ this.setFocusRegion("main");
193
+ return;
194
+ }
195
+ if (kb.matches(data, "tui.select.up")) {
196
+ this.railSelectionIndex = this.railSelectionIndex <= 0 ? items.length - 1 : this.railSelectionIndex - 1;
197
+ this.animator.markFocusPulse();
198
+ this.ui.requestRender();
199
+ return;
200
+ }
201
+ if (kb.matches(data, "tui.select.down")) {
202
+ this.railSelectionIndex = this.railSelectionIndex >= items.length - 1 ? 0 : this.railSelectionIndex + 1;
203
+ this.animator.markFocusPulse();
204
+ this.ui.requestRender();
205
+ return;
206
+ }
207
+ if (kb.matches(data, "tui.select.confirm")) {
208
+ this.activateAction(`nav:${items[this.railSelectionIndex].id}`);
209
+ }
210
+ }
211
+ handleMainInput(data) {
212
+ const kb = getKeybindings();
213
+ const cards = this.getMainCards();
214
+ if (cards.length === 0) {
215
+ return;
216
+ }
217
+ if (kb.matches(data, "tui.select.cancel")) {
218
+ this.onExit();
219
+ return;
220
+ }
221
+ if (kb.matches(data, "tui.select.up")) {
222
+ this.mainSelectionIndex = this.mainSelectionIndex <= 0 ? cards.length - 1 : this.mainSelectionIndex - 1;
223
+ this.animator.markFocusPulse();
224
+ this.ui.requestRender();
225
+ return;
226
+ }
227
+ if (kb.matches(data, "tui.select.down")) {
228
+ this.mainSelectionIndex = this.mainSelectionIndex >= cards.length - 1 ? 0 : this.mainSelectionIndex + 1;
229
+ this.animator.markFocusPulse();
230
+ this.ui.requestRender();
231
+ return;
232
+ }
233
+ if (kb.matches(data, "tui.select.confirm")) {
234
+ this.activateAction(cards[this.mainSelectionIndex].id);
235
+ }
236
+ }
237
+ handleOverlayInput(data) {
238
+ const kb = getKeybindings();
239
+ const options = this.getOverlayOptions();
240
+ if (kb.matches(data, "tui.select.cancel")) {
241
+ this.closeOverlay();
242
+ return;
243
+ }
244
+ if (options.length === 0) {
245
+ return;
246
+ }
247
+ if (kb.matches(data, "tui.select.up")) {
248
+ this.overlaySelectionIndex = this.overlaySelectionIndex <= 0 ? options.length - 1 : this.overlaySelectionIndex - 1;
249
+ this.animator.markFocusPulse();
250
+ this.ui.requestRender();
251
+ return;
252
+ }
253
+ if (kb.matches(data, "tui.select.down")) {
254
+ this.overlaySelectionIndex = this.overlaySelectionIndex >= options.length - 1 ? 0 : this.overlaySelectionIndex + 1;
255
+ this.animator.markFocusPulse();
256
+ this.ui.requestRender();
257
+ return;
258
+ }
259
+ if (kb.matches(data, "tui.select.confirm")) {
260
+ this.activateAction(options[this.overlaySelectionIndex].id);
261
+ }
262
+ }
263
+ handleShortcut(data) {
264
+ if (data.length !== 1) {
265
+ return false;
266
+ }
267
+ switch (data.toLowerCase()) {
268
+ case "h":
269
+ this.activateAction("nav:home");
270
+ return true;
271
+ case "p":
272
+ this.activateAction("overlay:provider-switch");
273
+ return true;
274
+ case "m":
275
+ this.activateAction("overlay:model-switch");
276
+ return true;
277
+ case "s":
278
+ this.activateAction("overlay:session-list");
279
+ return true;
280
+ case "n":
281
+ this.activateAction("session:new");
282
+ return true;
283
+ default:
284
+ return false;
285
+ }
286
+ }
287
+ handleScroll(event) {
288
+ const delta = event.button === "wheel-up" ? -1 : event.button === "wheel-down" ? 1 : 0;
289
+ if (delta === 0) {
290
+ return false;
291
+ }
292
+ if (this.overlay && this.overlayRegions.some(region => this.pointInRect(event.col, event.row, region))) {
293
+ const options = this.getOverlayOptions();
294
+ if (options.length === 0) {
295
+ return false;
296
+ }
297
+ this.overlaySelectionIndex = (this.overlaySelectionIndex + delta + options.length) % options.length;
298
+ this.ui.requestRender();
299
+ return true;
300
+ }
301
+ if (this.mainRegions.some(region => this.pointInRect(event.col, event.row, region))) {
302
+ const cards = this.getMainCards();
303
+ if (cards.length === 0) {
304
+ return false;
305
+ }
306
+ this.mainSelectionIndex = (this.mainSelectionIndex + delta + cards.length) % cards.length;
307
+ this.setFocusRegion("main");
308
+ return true;
309
+ }
310
+ return false;
311
+ }
312
+ activateAction(actionId) {
313
+ if (actionId === "focus:composer") {
314
+ this.setFocusRegion("composer");
315
+ this.pushTimeline("Composer focus selected from the dock.");
316
+ this.ui.requestRender();
317
+ return;
318
+ }
319
+ if (actionId.startsWith("nav:")) {
320
+ this.setView(actionId.slice(4));
321
+ return;
322
+ }
323
+ if (actionId.startsWith("overlay:")) {
324
+ this.openOverlay(actionId.slice(8));
325
+ return;
326
+ }
327
+ if (actionId === "session:new") {
328
+ this.startNewSession();
329
+ return;
330
+ }
331
+ if (actionId.startsWith("provider:")) {
332
+ this.activateProvider(actionId.slice(9));
333
+ return;
334
+ }
335
+ if (actionId.startsWith("model:")) {
336
+ this.activateModel(actionId.slice(6));
337
+ return;
338
+ }
339
+ if (actionId.startsWith("session:")) {
340
+ this.activateSession(actionId.slice(8));
341
+ }
342
+ }
343
+ setView(view) {
344
+ this.view = view;
345
+ this.railSelectionIndex = Math.max(0, this.getRailItems().findIndex(item => item.id === view));
346
+ this.mainSelectionIndex = 0;
347
+ this.focusRegion = "main";
348
+ this.composer.focused = false;
349
+ this.animator.markFocusPulse();
350
+ this.pushTimeline(`View changed to ${this.getViewLabel(view)}.`);
351
+ this.ui.requestRender();
352
+ }
353
+ openOverlay(overlay) {
354
+ if (overlay === "model-switch" && !this.getActiveProfile()) {
355
+ this.pushTimeline("Model picker blocked because no provider is active.");
356
+ this.ui.requestRender();
357
+ return;
358
+ }
359
+ this.overlay = overlay;
360
+ this.overlaySelectionIndex = 0;
361
+ this.focusRegion = "overlay";
362
+ this.composer.focused = false;
363
+ this.animator.markFocusPulse();
364
+ this.ui.requestRender();
365
+ }
366
+ closeOverlay() {
367
+ this.overlay = undefined;
368
+ this.setFocusRegion("main");
369
+ }
370
+ activateProvider(profileId) {
371
+ const profile = this.profiles.find(item => item.id === profileId);
372
+ if (!profile) {
373
+ return;
374
+ }
375
+ const currentSelection = this.profileService.getActiveSelection();
376
+ const nextModel = currentSelection.profileId === profile.id ? currentSelection.modelId ?? profile.models[0]?.id : profile.models[0]?.id;
377
+ this.profileService.setActiveSelection(profile.id, nextModel);
378
+ this.reloadProfiles();
379
+ this.pushTimeline(`Active provider switched to ${profile.label}${nextModel ? ` (${nextModel})` : ""}.`);
380
+ this.overlay = undefined;
381
+ this.view = "providers";
382
+ this.mainSelectionIndex = Math.max(0, this.profiles.findIndex(item => item.id === profile.id));
383
+ this.setFocusRegion("main");
384
+ }
385
+ activateModel(modelId) {
386
+ const profile = this.getActiveProfile();
387
+ if (!profile) {
388
+ return;
389
+ }
390
+ this.profileService.setActiveSelection(profile.id, modelId);
391
+ this.pushTimeline(`Default model switched to ${modelId}.`);
392
+ this.overlay = undefined;
393
+ this.setFocusRegion("main");
394
+ }
395
+ activateSession(sessionId) {
396
+ const session = this.sessions.find(item => item.id === sessionId);
397
+ if (!session) {
398
+ return;
399
+ }
400
+ this.pushTimeline(`Session selected: ${session.title}. Runtime session history wiring is still pending.`);
401
+ this.overlay = undefined;
402
+ this.view = "history";
403
+ this.mainSelectionIndex = Math.max(0, this.sessions.findIndex(item => item.id === sessionId));
404
+ this.setFocusRegion("main");
405
+ }
406
+ startNewSession() {
407
+ const activeProfile = this.getActiveProfile();
408
+ this.sessions.unshift({
409
+ id: `draft-${Date.now()}`,
410
+ title: "New dashboard session",
411
+ summary: activeProfile ? `Prepared a fresh session surface for ${activeProfile.label}.` : "Prepared a fresh session surface before provider selection.",
412
+ state: "ready",
413
+ updatedLabel: "Just now"
414
+ });
415
+ this.pushTimeline(activeProfile ? `New session staged with ${activeProfile.label}.` : "New session staged. Configure a provider before real execution wiring lands.");
416
+ this.view = "history";
417
+ this.mainSelectionIndex = 0;
418
+ this.setFocusRegion("main");
419
+ }
420
+ cycleFocus() {
421
+ if (this.overlay) {
422
+ this.setFocusRegion("overlay");
423
+ return;
424
+ }
425
+ const layout = computeWorkbenchLayout(this.ui.terminal.columns, this.ui.terminal.rows);
426
+ const order = [];
427
+ if (layout.showRail) {
428
+ order.push("rail");
429
+ }
430
+ order.push("main", "composer");
431
+ const currentIndex = order.indexOf(this.focusRegion);
432
+ const next = order[(currentIndex + 1) % order.length] ?? "main";
433
+ this.setFocusRegion(next);
434
+ }
435
+ setFocusRegion(region) {
436
+ this.focusRegion = region;
437
+ this.composer.focused = this._focused && region === "composer";
438
+ this.animator.markFocusPulse();
439
+ this.ui.requestRender();
440
+ }
441
+ getRailItems() {
442
+ return [{
443
+ id: "home",
444
+ label: "Home",
445
+ description: "Dashboard and quick launch cards"
446
+ }, {
447
+ id: "providers",
448
+ label: "Providers",
449
+ description: "Explicit provider cards and activation state"
450
+ }, {
451
+ id: "history",
452
+ label: "History",
453
+ description: "Recent session placeholders until runtime history is wired"
454
+ }];
455
+ }
456
+ getMainCards() {
457
+ const activeProfile = this.getActiveProfile();
458
+ const selection = this.profileService.getActiveSelection();
459
+ if (this.view === "providers") {
460
+ return this.profiles.map(profile => ({
461
+ id: `provider:${profile.id}`,
462
+ title: profile.label,
463
+ description: [`${profile.family} | ${profile.models.length} cached model${profile.models.length === 1 ? "" : "s"} | ${profile.lastValidationStatus}`, profile.baseUrl],
464
+ actionLabel: selection.profileId === profile.id ? "Active" : "Activate",
465
+ variant: selection.profileId === profile.id ? "success" : "primary"
466
+ }));
467
+ }
468
+ if (this.view === "history") {
469
+ return this.sessions.map(session => ({
470
+ id: `session:${session.id}`,
471
+ title: session.title,
472
+ description: [`${session.summary}`, `${session.updatedLabel} | ${session.state}`],
473
+ actionLabel: session.state === "active" ? "Open" : "Resume",
474
+ variant: session.state === "active" ? "success" : "primary"
475
+ }));
476
+ }
477
+ return [{
478
+ id: "session:new",
479
+ title: "New Session",
480
+ description: [activeProfile ? `Start a fresh workbench task with ${activeProfile.label}.` : "Stage a fresh workbench task before full runtime session wiring lands.", "Explicit cards replace hidden click zones in the main workspace."],
481
+ actionLabel: "Start",
482
+ variant: "success"
483
+ }, {
484
+ id: "overlay:provider-switch",
485
+ title: "Provider Dialog",
486
+ description: [activeProfile ? `Current provider: ${activeProfile.label}` : "No active provider selected yet.", "Open a direct picker to swap endpoints and keep the decision visible."],
487
+ actionLabel: "Open",
488
+ variant: "primary"
489
+ }, {
490
+ id: activeProfile ? "overlay:model-switch" : "nav:providers",
491
+ title: activeProfile ? "Model Dialog" : "Provider Center",
492
+ description: [activeProfile ? `Current model: ${selection.modelId ?? activeProfile.models[0]?.id ?? "none"}` : "Open provider cards first so the model picker has a real target.", activeProfile ? "Switch the default model through a modal row picker." : "The shell keeps provider setup separate from the main workbench cards."],
493
+ actionLabel: activeProfile ? "Switch" : "Open",
494
+ variant: "secondary"
495
+ }, {
496
+ id: "overlay:session-list",
497
+ title: "Resume Session",
498
+ description: [`${this.sessions.length} staged sessions are available in the rebuilt shell.`, "Use the dialog picker now; later this maps to persisted runtime history."],
499
+ actionLabel: "Browse",
500
+ variant: "primary"
501
+ }];
502
+ }
503
+ getOverlayOptions() {
504
+ if (!this.overlay) {
505
+ return [];
506
+ }
507
+ if (this.overlay === "provider-switch") {
508
+ const selection = this.profileService.getActiveSelection();
509
+ return this.profiles.map(profile => ({
510
+ id: `provider:${profile.id}`,
511
+ label: profile.label,
512
+ description: `${profile.family} | ${profile.models.length} model${profile.models.length === 1 ? "" : "s"} | ${this.truncateLabel(profile.baseUrl, 32)}`,
513
+ badge: selection.profileId === profile.id ? "ACTIVE" : undefined
514
+ }));
515
+ }
516
+ if (this.overlay === "model-switch") {
517
+ const profile = this.getActiveProfile();
518
+ const selection = this.profileService.getActiveSelection();
519
+ if (!profile || profile.models.length === 0) {
520
+ return [{
521
+ id: "overlay:model-switch",
522
+ label: "No cached models",
523
+ description: "The active provider has no cached models yet. Re-run onboarding or refresh models later.",
524
+ disabled: true
525
+ }];
526
+ }
527
+ return profile.models.map(model => ({
528
+ id: `model:${model.id}`,
529
+ label: model.id,
530
+ description: `${model.family} | ${model.name}`,
531
+ badge: selection.modelId === model.id ? "CURRENT" : undefined
532
+ }));
533
+ }
534
+ return this.sessions.map(session => ({
535
+ id: `session:${session.id}`,
536
+ label: session.title,
537
+ description: `${session.summary} | ${session.updatedLabel}`,
538
+ badge: session.state === "active" ? "ACTIVE" : undefined
539
+ }));
540
+ }
541
+ getActiveProfile() {
542
+ const selection = this.profileService.getActiveSelection();
543
+ return selection.profileId ? this.profiles.find(item => item.id === selection.profileId) : this.profiles[0];
544
+ }
545
+ seedTimeline() {
546
+ this.timeline.push("Workbench rebuild active: explicit cards, chips, and dialogs replace hidden shell hit targets.");
547
+ this.timeline.push("Onboarding, config, and provider profiles are kept intact from the earlier milestone.");
548
+ const activeProfile = this.getActiveProfile();
549
+ if (activeProfile) {
550
+ this.timeline.push(`Current provider foundation loaded from storage: ${activeProfile.label}.`);
551
+ }
552
+ }
553
+ pushTimeline(message) {
554
+ const timestamp = new Date().toLocaleTimeString("en-US", {
555
+ hour: "2-digit",
556
+ minute: "2-digit"
557
+ });
558
+ this.timeline.unshift(`${timestamp} ${message}`);
559
+ if (this.timeline.length > 12) {
560
+ this.timeline.length = 12;
561
+ }
562
+ }
563
+ ensureDraftSession(prompt) {
564
+ this.sessions.unshift({
565
+ id: `draft-${Date.now()}`,
566
+ title: `Draft: ${this.truncateLabel(prompt, 22)}`,
567
+ summary: "Composer input captured into the rebuilt workbench timeline while full agent execution wiring is pending.",
568
+ state: "ready",
569
+ updatedLabel: "Just now"
570
+ });
571
+ if (this.sessions.length > 8) {
572
+ this.sessions.length = 8;
573
+ }
574
+ }
575
+ handleComposerCommand(value) {
576
+ switch (value) {
577
+ case "/providers":
578
+ this.openOverlay("provider-switch");
579
+ return true;
580
+ case "/models":
581
+ this.openOverlay("model-switch");
582
+ return true;
583
+ case "/sessions":
584
+ this.openOverlay("session-list");
585
+ return true;
586
+ case "/new":
587
+ this.startNewSession();
588
+ return true;
589
+ default:
590
+ return false;
591
+ }
592
+ }
593
+ reloadProfiles() {
594
+ this.profiles = this.profileService.listProfiles();
595
+ const items = this.getRailItems();
596
+ if (this.railSelectionIndex < 0 || this.railSelectionIndex >= items.length) {
597
+ this.railSelectionIndex = Math.max(0, items.findIndex(item => item.id === this.view));
598
+ }
599
+ }
600
+ styleButton(label, variant) {
601
+ const button = `[ ${label} ]`;
602
+ switch (variant) {
603
+ case "active":
604
+ return workbenchTheme.accentStrong(button);
605
+ case "primary":
606
+ return workbenchTheme.accent(button);
607
+ case "secondary":
608
+ return workbenchTheme.muted(button);
609
+ case "success":
610
+ return workbenchTheme.success(button);
611
+ case "warning":
612
+ return workbenchTheme.warning(button);
613
+ }
614
+ }
615
+ getViewLabel(view) {
616
+ switch (view) {
617
+ case "home":
618
+ return "Home";
619
+ case "providers":
620
+ return "Providers";
621
+ case "history":
622
+ return "History";
623
+ }
624
+ }
625
+ getFocusLabel(region) {
626
+ switch (region) {
627
+ case "rail":
628
+ return "Left Rail";
629
+ case "main":
630
+ return "Main Workbench";
631
+ case "composer":
632
+ return "Composer";
633
+ case "overlay":
634
+ return "Dialog";
635
+ }
636
+ }
637
+ truncateLabel(value, maxLength) {
638
+ return value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 3))}...`;
639
+ }
640
+ findHit(col, row, regions) {
641
+ return regions.find(region => this.pointInRect(col, row, region));
642
+ }
643
+ pointInRect(col, row, rect) {
644
+ return col >= rect.col && col < rect.col + rect.width && row >= rect.row && row < rect.row + rect.height;
645
+ }
646
+ isPrimaryMouseActivation(event) {
647
+ return event.button === "left" && (event.action === "press" || event.action === "release");
648
+ }
649
+ }