@pellux/goodvibes-agent 1.1.4 → 1.1.5

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.
@@ -1,3 +1,4 @@
1
+ import type { ModelPickerTarget } from './model-picker.ts';
1
2
  import type { AgentWorkspaceChannelStatus } from './agent-workspace-channels.ts';
2
3
  import type { AgentWorkspaceSetupChecklistItem } from './agent-workspace-setup.ts';
3
4
  import type { AgentWorkspaceVoiceMediaReadiness } from './agent-workspace-voice-media.ts';
@@ -17,7 +18,7 @@ export const AGENT_WORKSPACE_CATEGORY_IDS = [
17
18
  'onboarding-channels',
18
19
  'onboarding-voice-media',
19
20
  'onboarding-context',
20
- 'onboarding-verify',
21
+ 'onboarding-automation',
21
22
  'research',
22
23
  'artifacts',
23
24
  'conversation',
@@ -40,7 +41,7 @@ export const AGENT_WORKSPACE_CATEGORY_IDS = [
40
41
 
41
42
  export type AgentWorkspaceCategoryId = (typeof AGENT_WORKSPACE_CATEGORY_IDS)[number];
42
43
 
43
- export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | 'setting' | 'settings-import' | 'local-selection' | 'local-operation' | 'onboarding-complete';
44
+ export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | 'setting' | 'settings-import' | 'model-picker' | 'settings-modal' | 'local-selection' | 'local-operation' | 'onboarding-complete';
44
45
 
45
46
  export type AgentWorkspaceLocalEditorKind = 'memory' | 'note' | 'persona' | 'skill' | 'routine' | 'profile';
46
47
 
@@ -258,6 +259,9 @@ export interface AgentWorkspaceAction {
258
259
  readonly editorKind?: AgentWorkspaceEditorKind;
259
260
  readonly settingKey?: string;
260
261
  readonly settingValueHint?: string;
262
+ readonly modelPickerTarget?: ModelPickerTarget;
263
+ readonly modelPickerFlow?: 'providerModel' | 'model';
264
+ readonly settingsTarget?: string;
261
265
  readonly visibleWhenSettingKey?: string;
262
266
  readonly visibleWhenSettingValue?: string | boolean | number;
263
267
  readonly localKind?: AgentWorkspaceLocalEditorKind;
@@ -362,11 +366,49 @@ export interface AgentWorkspaceRuntimeSnapshot {
362
366
  readonly provider: string;
363
367
  readonly model: string;
364
368
  readonly modelDisplayName: string;
369
+ readonly embeddingProvider: string;
370
+ readonly reasoningEffort: string;
371
+ readonly helperEnabled: boolean;
372
+ readonly toolLlmEnabled: boolean;
373
+ readonly providerFailureHints: boolean;
374
+ readonly cacheEnabled: boolean;
375
+ readonly cacheStableTtl: string;
376
+ readonly cacheMonitorHitRate: boolean;
377
+ readonly cacheHitRateWarningThreshold: number;
378
+ readonly hitlMode: string;
379
+ readonly guidanceMode: string;
380
+ readonly saveHistory: boolean;
381
+ readonly autoApprove: boolean;
382
+ readonly autoCompactThreshold: number;
383
+ readonly staleContextWarnings: boolean;
384
+ readonly showThinking: boolean;
385
+ readonly showReasoningSummary: boolean;
386
+ readonly theme: string;
387
+ readonly stream: boolean;
388
+ readonly lineNumbers: string;
389
+ readonly operationalMessages: string;
390
+ readonly systemMessages: string;
391
+ readonly releaseChannel: string;
392
+ readonly permissionMode: string;
393
+ readonly toolAutoHeal: boolean;
394
+ readonly toolsDefaultTokenBudget: number;
395
+ readonly artifactMaxBytes: number;
396
+ readonly rawPromptTelemetry: boolean;
397
+ readonly automationEnabled: boolean;
398
+ readonly automationMaxConcurrentRuns: number;
399
+ readonly automationRunHistoryLimit: number;
400
+ readonly automationDefaultTimeoutMs: number;
401
+ readonly automationCatchUpWindowMinutes: number;
402
+ readonly automationFailureCooldownMs: number;
403
+ readonly automationDeleteAfterRun: boolean;
365
404
  readonly sessionId: string;
366
405
  readonly workingDirectory: string;
367
406
  readonly homeDirectory: string;
368
407
  readonly runtimeBaseUrl: string;
369
408
  readonly runtimeOwnership: 'external';
409
+ readonly activeSubscriptionCount: number;
410
+ readonly pendingSubscriptionCount: number;
411
+ readonly availableSubscriptionProviderCount: number;
370
412
  readonly sessionMemoryCount: number;
371
413
  readonly localMemoryCount: number;
372
414
  readonly localMemoryReviewQueueCount: number;
@@ -6,21 +6,21 @@ import type { CommandContext } from './command-registry.ts';
6
6
  import { AgentNoteRegistry } from '../agent/note-registry.ts';
7
7
  import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
8
8
  import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
9
- import { createAgentRuntimeProfile, type AgentRuntimeProfileInfo } from '../agent/runtime-profile.ts';
9
+ import type { AgentRuntimeProfileInfo } from '../agent/runtime-profile.ts';
10
10
  import { AgentSkillRegistry } from '../agent/skill-registry.ts';
11
11
  import { activateAgentWorkspaceSelection } from './agent-workspace-activation.ts';
12
12
  import { AGENT_WORKSPACE_CATEGORIES } from './agent-workspace-categories.ts';
13
13
  import { buildAgentWorkspaceCommandEditorSubmission, isAgentWorkspaceCommandEditorKind } from './agent-workspace-command-editor.ts';
14
- import { createDeleteEditor, editorCategoryId, isAffirmative, splitList } from './agent-workspace-editors.ts';
15
- import { createAgentWorkspaceLearnedBehavior } from './agent-workspace-learned-behavior.ts';
14
+ import { createDeleteEditor, editorCategoryId } from './agent-workspace-editors.ts';
15
+ import { submitAgentWorkspaceLocalRegistryEditor } from './agent-workspace-local-editor-submission.ts';
16
16
  import { clampAgentWorkspaceLocalLibrarySelection, moveAgentWorkspaceLocalLibraryItemSelection, selectedAgentWorkspaceLocalLibraryItem, type AgentWorkspaceLocalSelectionIndexes } from './agent-workspace-local-selection.ts';
17
17
  import { applyAgentWorkspaceLocalLibraryOperation } from './agent-workspace-local-operations.ts';
18
18
  import { deleteAgentWorkspaceMemoryEditor, submitAgentWorkspaceMemoryEditor } from './agent-workspace-memory-editor.ts';
19
19
  import { jumpAgentWorkspaceSelection, moveAgentWorkspaceSelection, selectAgentWorkspaceCategory } from './agent-workspace-navigation.ts';
20
- import { buildAgentWorkspaceRequirements } from './agent-workspace-requirements.ts';
21
20
  import { appendAgentWorkspaceActionSearchText, backspaceAgentWorkspaceActionSearch, beginAgentWorkspaceActionSearch, clearAgentWorkspaceActionSearch, commitAgentWorkspaceActionSearchSelection, searchAgentWorkspaceActions } from './agent-workspace-search.ts';
22
21
  import { agentWorkspaceSettingSchema, applyAgentWorkspaceSettingValue, buildAgentWorkspaceSettingActionEffect, importAgentWorkspaceTuiSettings, isAgentWorkspaceActionVisible } from './agent-workspace-settings.ts';
23
22
  import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
23
+ import { submitAgentWorkspaceSubscriptionLoginFinishEditor, submitAgentWorkspaceSubscriptionLoginStartEditor, submitAgentWorkspaceSubscriptionLogoutEditor } from './agent-workspace-subscription-editor.ts';
24
24
  import type { AgentWorkspaceAction, AgentWorkspaceActionResult, AgentWorkspaceActionSearchResult, AgentWorkspaceCategory, AgentWorkspaceCommandDispatcher, AgentWorkspaceEditorField, AgentWorkspaceFocusPane, AgentWorkspaceLocalEditor, AgentWorkspaceLocalEditorKind, AgentWorkspaceLocalLibraryItem, AgentWorkspaceLocalOperation, AgentWorkspacePromptDispatcher, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
25
25
  import { writeOnboardingCheckMarker, writeOnboardingCompletionMarker } from '../runtime/onboarding/index.ts';
26
26
 
@@ -333,6 +333,55 @@ export class AgentWorkspace {
333
333
  }
334
334
  }
335
335
 
336
+ openModelPickerAction(action: AgentWorkspaceAction, requestRender?: () => void): void {
337
+ const target = action.modelPickerTarget ?? 'main';
338
+ const opened = action.modelPickerFlow === 'model'
339
+ ? this.context?.openModelPickerWithTarget?.(target)
340
+ : this.context?.openProviderModelPickerWithTarget?.(target);
341
+ if (!opened) {
342
+ this.status = 'Model picker is unavailable.';
343
+ this.lastActionResult = {
344
+ kind: 'error',
345
+ title: 'Model picker unavailable',
346
+ detail: 'This runtime cannot open the model picker from Agent workspace.',
347
+ safety: action.safety,
348
+ };
349
+ requestRender?.();
350
+ return;
351
+ }
352
+ this.status = `Opening ${action.label}.`;
353
+ this.lastActionResult = {
354
+ kind: 'dispatched',
355
+ title: `Opening ${action.label}`,
356
+ detail: 'Opened the shared provider/model picker for this setup target.',
357
+ safety: action.safety,
358
+ };
359
+ requestRender?.();
360
+ }
361
+
362
+ openSettingsModalAction(action: AgentWorkspaceAction, requestRender?: () => void): void {
363
+ if (!this.context?.openSettingsModal) {
364
+ this.status = 'Settings are unavailable.';
365
+ this.lastActionResult = {
366
+ kind: 'error',
367
+ title: 'Settings unavailable',
368
+ detail: 'This runtime cannot open settings from Agent workspace.',
369
+ safety: action.safety,
370
+ };
371
+ requestRender?.();
372
+ return;
373
+ }
374
+ this.context.openSettingsModal(action.settingsTarget);
375
+ this.status = `Opening ${action.label}.`;
376
+ this.lastActionResult = {
377
+ kind: 'dispatched',
378
+ title: `Opening ${action.label}`,
379
+ detail: 'Opened the shared settings surface for this setup area.',
380
+ safety: action.safety,
381
+ };
382
+ requestRender?.();
383
+ }
384
+
336
385
  applySettingAction(action: AgentWorkspaceAction, requestRender?: () => void): void {
337
386
  const effect = buildAgentWorkspaceSettingActionEffect(this.context, action);
338
387
  if (effect.kind === 'result') {
@@ -446,6 +495,19 @@ export class AgentWorkspace {
446
495
  this.status = `${missing.label} is required.`;
447
496
  return;
448
497
  }
498
+ if (editor.kind === 'subscription-login-start') {
499
+ void submitAgentWorkspaceSubscriptionLoginStartEditor(this, editor, this.context, (id) => this.editorField(id)).finally(() => requestRender?.());
500
+ return;
501
+ }
502
+ if (editor.kind === 'subscription-login-finish') {
503
+ void submitAgentWorkspaceSubscriptionLoginFinishEditor(this, editor, this.context, (id) => this.editorField(id)).finally(() => requestRender?.());
504
+ return;
505
+ }
506
+ if (editor.kind === 'subscription-logout') {
507
+ submitAgentWorkspaceSubscriptionLogoutEditor(this, editor, this.context, (id) => this.editorField(id));
508
+ requestRender?.();
509
+ return;
510
+ }
449
511
  if (isAgentWorkspaceCommandEditorKind(editor.kind)) {
450
512
  this.submitCommandEditor(editor);
451
513
  requestRender?.();
@@ -496,135 +558,13 @@ export class AgentWorkspace {
496
558
  return;
497
559
  }
498
560
  try {
499
- if (editor.mode === 'delete') {
500
- this.submitLocalDeleteEditor(shellPaths, editor);
501
- return;
502
- }
503
- if (editor.kind === 'learned-behavior') {
504
- const created = createAgentWorkspaceLearnedBehavior(shellPaths, {
505
- target: this.learnedBehaviorTarget(),
506
- name: this.editorField('name'),
507
- description: this.editorField('description'),
508
- notes: this.editorField('notes'),
509
- tags: splitList(this.editorField('tags')),
510
- triggers: splitList(this.editorField('triggers')),
511
- enable: isAffirmative(this.editorField('enable')),
512
- });
513
- this.finishLocalEditor(created.kind, created.id, created.name, 'Created');
514
- } else if (editor.kind === 'profile') {
515
- const template = this.editorField('template');
516
- const templateId = template && template.toLowerCase() !== 'none' ? template : undefined;
517
- const profile = createAgentRuntimeProfile(shellPaths.homeDirectory, this.editorField('name'), {
518
- ...(templateId ? { templateId } : {}),
519
- });
520
- this.finishProfileEditor(profile);
521
- } else if (editor.kind === 'note') {
522
- const registry = AgentNoteRegistry.fromShellPaths(shellPaths);
523
- if (editor.mode === 'update' && editor.recordId) {
524
- const updated = registry.update(editor.recordId, {
525
- title: this.editorField('title'),
526
- body: this.editorField('body'),
527
- sourceUrl: this.editorField('sourceUrl'),
528
- tags: splitList(this.editorField('tags')),
529
- provenance: 'Workspace',
530
- });
531
- this.finishLocalEditor(editor.kind, updated.id, updated.title, 'Updated');
532
- return;
533
- }
534
- const created = registry.create({
535
- title: this.editorField('title'),
536
- body: this.editorField('body'),
537
- sourceUrl: this.editorField('sourceUrl'),
538
- tags: splitList(this.editorField('tags')),
539
- source: 'user',
540
- provenance: 'Workspace',
541
- });
542
- this.finishLocalEditor(editor.kind, created.id, created.title, 'Created');
543
- } else if (editor.kind === 'persona') {
544
- const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
545
- if (editor.mode === 'update' && editor.recordId) {
546
- const wasActive = registry.snapshot().activePersonaId === editor.recordId;
547
- const updated = registry.update(editor.recordId, {
548
- name: this.editorField('name'),
549
- description: this.editorField('description'),
550
- body: this.editorField('body'),
551
- tags: splitList(this.editorField('tags')),
552
- triggers: splitList(this.editorField('triggers')),
553
- provenance: 'Workspace',
554
- });
555
- if (isAffirmative(this.editorField('activate'))) registry.setActive(updated.id);
556
- else if (wasActive) registry.clearActive();
557
- this.finishLocalEditor(editor.kind, updated.id, updated.name, 'Updated');
558
- return;
559
- }
560
- const created = registry.create({
561
- name: this.editorField('name'),
562
- description: this.editorField('description'),
563
- body: this.editorField('body'),
564
- tags: splitList(this.editorField('tags')),
565
- triggers: splitList(this.editorField('triggers')),
566
- source: 'user',
567
- provenance: 'Workspace',
568
- });
569
- if (isAffirmative(this.editorField('activate'))) registry.setActive(created.id);
570
- this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
571
- } else if (editor.kind === 'skill') {
572
- const registry = AgentSkillRegistry.fromShellPaths(shellPaths);
573
- if (editor.mode === 'update' && editor.recordId) {
574
- const updated = registry.update(editor.recordId, {
575
- name: this.editorField('name'),
576
- description: this.editorField('description'),
577
- procedure: this.editorField('procedure'),
578
- triggers: splitList(this.editorField('triggers')),
579
- tags: splitList(this.editorField('tags')),
580
- requirements: buildAgentWorkspaceRequirements((id) => this.editorField(id)),
581
- provenance: 'Workspace',
582
- });
583
- registry.setEnabled(updated.id, isAffirmative(this.editorField('enabled')));
584
- this.finishLocalEditor(editor.kind, updated.id, updated.name, 'Updated');
585
- return;
586
- }
587
- const created = registry.create({
588
- name: this.editorField('name'),
589
- description: this.editorField('description'),
590
- procedure: this.editorField('procedure'),
591
- triggers: splitList(this.editorField('triggers')),
592
- tags: splitList(this.editorField('tags')),
593
- requirements: buildAgentWorkspaceRequirements((id) => this.editorField(id)),
594
- enabled: isAffirmative(this.editorField('enabled')),
595
- source: 'user',
596
- provenance: 'Workspace',
597
- });
598
- this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
599
- } else {
600
- const registry = AgentRoutineRegistry.fromShellPaths(shellPaths);
601
- if (editor.mode === 'update' && editor.recordId) {
602
- const updated = registry.update(editor.recordId, {
603
- name: this.editorField('name'),
604
- description: this.editorField('description'),
605
- steps: this.editorField('steps'),
606
- triggers: splitList(this.editorField('triggers')),
607
- tags: splitList(this.editorField('tags')),
608
- requirements: buildAgentWorkspaceRequirements((id) => this.editorField(id)),
609
- provenance: 'Workspace',
610
- });
611
- registry.setEnabled(updated.id, isAffirmative(this.editorField('enabled')));
612
- this.finishLocalEditor(editor.kind, updated.id, updated.name, 'Updated');
613
- return;
614
- }
615
- const created = registry.create({
616
- name: this.editorField('name'),
617
- description: this.editorField('description'),
618
- steps: this.editorField('steps'),
619
- triggers: splitList(this.editorField('triggers')),
620
- tags: splitList(this.editorField('tags')),
621
- requirements: buildAgentWorkspaceRequirements((id) => this.editorField(id)),
622
- enabled: isAffirmative(this.editorField('enabled')),
623
- source: 'user',
624
- provenance: 'Workspace',
625
- });
626
- this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
627
- }
561
+ submitAgentWorkspaceLocalRegistryEditor(shellPaths, editor, {
562
+ readField: (id) => this.editorField(id),
563
+ learnedBehaviorTarget: () => this.learnedBehaviorTarget(),
564
+ submitDeleteEditor: () => this.submitLocalDeleteEditor(shellPaths, editor),
565
+ finishLocalEditor: (kind, id, name, verb) => this.finishLocalEditor(kind, id, name, verb),
566
+ finishProfileEditor: (profile) => this.finishProfileEditor(profile),
567
+ });
628
568
  } catch (error) {
629
569
  const detail = error instanceof Error ? error.message : String(error);
630
570
  this.localEditor = { ...editor, message: detail };
@@ -131,6 +131,18 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
131
131
  return withState(state, true);
132
132
  }
133
133
 
134
+ if (handleModelPickerToken({
135
+ modelPicker: state.modelPicker,
136
+ modalStack: state.modalStack,
137
+ commandContext: state.commandContext,
138
+ getViewportHeight: state.getViewportHeight,
139
+ requestRender: state.requestRender,
140
+ handleEscape: state.handleEscape,
141
+ onModelPickerCommit: state.onModelPickerCommit,
142
+ }, token)) {
143
+ return withState(state, true);
144
+ }
145
+
134
146
  if (handleSettingsModalToken({
135
147
  settingsModal: state.settingsModal,
136
148
  commandContext: state.commandContext,
@@ -200,18 +212,6 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
200
212
  return withState(state, true, historyState);
201
213
  }
202
214
 
203
- if (handleModelPickerToken({
204
- modelPicker: state.modelPicker,
205
- modalStack: state.modalStack,
206
- commandContext: state.commandContext,
207
- getViewportHeight: state.getViewportHeight,
208
- requestRender: state.requestRender,
209
- handleEscape: state.handleEscape,
210
- onModelPickerCommit: state.onModelPickerCommit,
211
- }, token)) {
212
- return withState(state, true);
213
- }
214
-
215
215
  if (handleLiveTailToken({
216
216
  liveTailModal: state.liveTailModal,
217
217
  processModal: state.processModal,
@@ -336,7 +336,8 @@ export class InputHandler {
336
336
  public handleBlockToggle(): void { handleBlockToggleForHandler(this); }
337
337
  public handleCtrlC(): void { handleCtrlCForHandler(this); }
338
338
  public modalOpened(name: string): void {
339
- if (name !== 'agentWorkspace' && this.agentWorkspace.active) {
339
+ const keepAgentWorkspaceUnderlay = name === 'modelPicker' || name === 'settings';
340
+ if (name !== 'agentWorkspace' && !keepAgentWorkspaceUnderlay && this.agentWorkspace.active) {
340
341
  this.closeAgentWorkspaceModal();
341
342
  }
342
343
  modalOpenedForHandler(this, name);
package/src/main.ts CHANGED
@@ -15,6 +15,8 @@ import { PermissionPromptUI } from './permissions/prompt.ts';
15
15
  import { CommandRegistry } from './input/command-registry.ts';
16
16
  import type { CommandContext } from './input/command-registry.ts';
17
17
  import { renderProcessIndicator } from './renderer/process-indicator.ts';
18
+ import { renderModelWorkspace } from './renderer/model-workspace.ts';
19
+ import { renderSettingsModal } from './renderer/settings-modal.ts';
18
20
  import { registerBuiltinCommands } from './input/commands.ts';
19
21
  import { ScheduleManager } from '@pellux/goodvibes-sdk/platform/tools';
20
22
  import { InputHistory } from './input/input-history.ts';
@@ -40,7 +42,7 @@ import {
40
42
  } from '@/runtime/index.ts';
41
43
  import type { SessionSnapshot } from '@/runtime/index.ts';
42
44
  import { handleBlockingShellInput, type PendingPermissionState } from './shell/blocking-input.ts';
43
- import { createAgentWorkspaceFullscreenComposite } from './shell/agent-workspace-fullscreen.ts';
45
+ import { createAgentWorkspaceFullscreenComposite, createFullscreenCompositeFromLines } from './shell/agent-workspace-fullscreen.ts';
44
46
  import { getTerminalSize } from './shell/terminal-size.ts';
45
47
  import { wireShellUiOpeners } from './shell/ui-openers.ts';
46
48
  import { deriveComposerState } from './core/composer-state.ts';
@@ -477,6 +479,14 @@ async function main() {
477
479
  input.setPanelMouseLayout(null);
478
480
  activeConversationWidth = width;
479
481
  conversation.setSplashSuppressed(true);
482
+ if (input.modelPicker.active) {
483
+ compositor.composite(createFullscreenCompositeFromLines(renderModelWorkspace(input.modelPicker, width, height), width, height));
484
+ return;
485
+ }
486
+ if (input.settingsModal.active) {
487
+ compositor.composite(createFullscreenCompositeFromLines(renderSettingsModal(input.settingsModal, width, height), width, height));
488
+ return;
489
+ }
480
490
  compositor.composite(createAgentWorkspaceFullscreenComposite(input.agentWorkspace, width, height));
481
491
  return;
482
492
  }
@@ -709,7 +719,6 @@ async function main() {
709
719
  streamTokenSpeed = elapsed > 0 ? streamDeltaCount / elapsed : 0;
710
720
  }));
711
721
 
712
- // --- Terminal setup ---
713
722
  stdin.setRawMode(true);
714
723
  stdin.resume();
715
724
  stdin.setEncoding('utf8');
@@ -748,11 +757,9 @@ async function main() {
748
757
  process.on('unhandledRejection', unhandledRejectionHandler);
749
758
  stdout.on('resize', resizeHandler);
750
759
 
751
- // Initial render
752
760
  conversation.rebuildHistory();
753
761
  render();
754
762
 
755
- // --- Crash recovery check ---
756
763
  const recoveryInfo = checkRecoveryFile({ workingDirectory: workingDir, homeDirectory });
757
764
  if (recoveryInfo) {
758
765
  systemMessageRouter.high(`[Recovery] Found unsaved session from ${new Date(recoveryInfo.timestamp).toLocaleString()}. Title: "${recoveryInfo.title}". Press Ctrl+R to restore, Esc to discard, or start typing to ignore it.`);
@@ -763,7 +770,6 @@ async function main() {
763
770
  recoveryPending = true;
764
771
  }
765
772
 
766
- // --- Auto-save to recovery file every 60s ---
767
773
  recoveryInterval = setInterval(() => {
768
774
  const snapshot = buildCurrentSessionSnapshot();
769
775
  writeRecoveryFile(
@@ -773,9 +779,7 @@ async function main() {
773
779
  { workingDirectory: workingDir, homeDirectory },
774
780
  );
775
781
  }, 60_000);
776
-
777
782
  }
778
-
779
783
  main().catch((err: unknown) => {
780
784
  const detail = formatFatalStartupErrorForLog(err);
781
785
  try {
@@ -55,12 +55,15 @@ function buildLeftRows(workspace: AgentWorkspace, height: number): WorkspaceRow[
55
55
  }
56
56
 
57
57
  function actionCommand(action: AgentWorkspaceAction): string {
58
- if (action.kind === 'workspace') return action.targetCategoryId ? `open ${action.targetCategoryId}` : '(workspace)';
59
- if (action.kind === 'editor') return action.editorKind ? `edit ${action.editorKind}` : '(editor)';
60
- if (action.kind === 'setting') return action.settingKey ? `set ${action.settingKey}` : 'set setting';
58
+ if (action.kind === 'workspace') return 'open area';
59
+ if (action.kind === 'editor') return action.editorKind ? `edit ${action.editorKind}` : 'edit form';
60
+ if (action.kind === 'setting') return action.settingKey ? `setting ${action.settingKey}` : 'setting';
61
61
  if (action.kind === 'settings-import') return 'import GoodVibes settings';
62
+ if (action.kind === 'model-picker') return action.modelPickerFlow === 'model' ? 'model picker' : 'provider/model picker';
63
+ if (action.kind === 'settings-modal') return action.settingsTarget ? `settings ${action.settingsTarget}` : 'settings';
62
64
  if (action.kind === 'local-selection') return action.selectionDelta && action.selectionDelta < 0 ? 'select previous' : 'select next';
63
65
  if (action.kind === 'local-operation') return action.localOperation ?? '(local action)';
66
+ if (action.kind === 'onboarding-complete') return 'apply and close';
64
67
  return action.command ?? '(guidance)';
65
68
  }
66
69
 
@@ -83,6 +86,11 @@ function setupStatusLabel(status: AgentWorkspaceRuntimeSnapshot['setupChecklist'
83
86
  : 'Optional';
84
87
  }
85
88
 
89
+ function formatMegabytes(bytes: number): string {
90
+ if (!Number.isFinite(bytes) || bytes <= 0) return '0 MB';
91
+ return `${Math.round(bytes / (1024 * 1024))} MB`;
92
+ }
93
+
86
94
  function compactText(text: string, maxWidth = 104): string {
87
95
  const normalized = text.replace(/\s+/g, ' ').trim();
88
96
  if (normalized.length === 0) return '';
@@ -291,6 +299,63 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
291
299
  base.push(
292
300
  ...setupOverviewLines(snapshot),
293
301
  );
302
+ } else if (category.id === 'account-model') {
303
+ base.push(
304
+ { text: `Chat route: ${snapshot.provider} / ${snapshot.modelDisplayName}`, fg: PALETTE.info },
305
+ { text: `Subscriptions: ${snapshot.activeSubscriptionCount} active; ${snapshot.pendingSubscriptionCount} pending; ${snapshot.availableSubscriptionProviderCount} available.`, fg: snapshot.activeSubscriptionCount > 0 ? PALETTE.good : snapshot.pendingSubscriptionCount > 0 ? PALETTE.warn : PALETTE.muted },
306
+ { text: `Embedding: ${snapshot.embeddingProvider}; reasoning ${snapshot.reasoningEffort}.`, fg: PALETTE.info },
307
+ { text: `Helper: ${snapshot.helperEnabled ? 'enabled' : 'disabled'}; Tool LLM: ${snapshot.toolLlmEnabled ? 'enabled' : 'disabled'}.`, fg: snapshot.helperEnabled || snapshot.toolLlmEnabled ? PALETTE.good : PALETTE.muted },
308
+ { text: `Cache: ${snapshot.cacheEnabled ? snapshot.cacheStableTtl : 'off'}; monitor ${snapshot.cacheMonitorHitRate ? snapshot.cacheHitRateWarningThreshold : 'off'}; failure hints ${snapshot.providerFailureHints ? 'on' : 'off'}.`, fg: snapshot.cacheEnabled ? PALETTE.info : PALETTE.muted },
309
+ );
310
+ } else if (category.id === 'assistant-behavior') {
311
+ base.push(
312
+ { text: `Interaction: ${snapshot.hitlMode}; guidance ${snapshot.guidanceMode}; history ${snapshot.saveHistory ? 'saved' : 'off'}.`, fg: PALETTE.info },
313
+ { text: `Context: compact at ${snapshot.autoCompactThreshold}; stale warnings ${snapshot.staleContextWarnings ? 'on' : 'off'}.`, fg: PALETTE.info },
314
+ { text: `Reasoning display: thinking ${snapshot.showThinking ? 'on' : 'off'}; summaries ${snapshot.showReasoningSummary ? 'on' : 'off'}.`, fg: PALETTE.muted },
315
+ );
316
+ } else if (category.id === 'tools-permissions') {
317
+ base.push(
318
+ { text: `Permission mode: ${snapshot.permissionMode}.`, fg: snapshot.permissionMode === 'allow-all' ? PALETTE.warn : PALETTE.info },
319
+ { text: `Auto-approve ${snapshot.autoApprove ? 'on' : 'off'}; tool auto-heal ${snapshot.toolAutoHeal ? 'on' : 'off'}; token budget ${snapshot.toolsDefaultTokenBudget}.`, fg: snapshot.autoApprove ? PALETTE.warn : PALETTE.info },
320
+ { text: `Artifact limit ${formatMegabytes(snapshot.artifactMaxBytes)}; raw prompt telemetry ${snapshot.rawPromptTelemetry ? 'on' : 'off'}.`, fg: snapshot.rawPromptTelemetry ? PALETTE.warn : PALETTE.muted },
321
+ { text: `MCP servers: ${snapshot.mcpConnectedServerCount}/${snapshot.mcpServerCount} connected; quarantined ${snapshot.mcpQuarantinedServerCount}.`, fg: snapshot.mcpQuarantinedServerCount > 0 ? PALETTE.warn : PALETTE.info },
322
+ { text: 'MCP and secret setup use forms; selecting a row does not run arbitrary tools.', fg: PALETTE.good },
323
+ );
324
+ } else if (category.id === 'onboarding-display') {
325
+ base.push(
326
+ { text: `Theme: ${snapshot.theme}; streaming ${snapshot.stream ? 'on' : 'off'}; line numbers ${snapshot.lineNumbers}.`, fg: PALETTE.info },
327
+ { text: `Messages: operational ${snapshot.operationalMessages}; system ${snapshot.systemMessages}.`, fg: PALETTE.info },
328
+ { text: `Release channel: ${snapshot.releaseChannel}.`, fg: PALETTE.muted },
329
+ );
330
+ } else if (category.id === 'onboarding-channels') {
331
+ const enabledCount = snapshot.channels.filter((channel) => channel.enabled).length;
332
+ const readyCount = snapshot.channels.filter((channel) => channel.ready).length;
333
+ const needsConfig = snapshot.channels.filter((channel) => channel.setupState === 'needs-config');
334
+ const needsTarget = snapshot.channels.filter((channel) => channel.setupState === 'needs-target');
335
+ base.push(
336
+ { text: `Channels: ${readyCount}/${snapshot.channels.length} ready; ${enabledCount} enabled.`, fg: enabledCount > 0 ? PALETTE.info : PALETTE.muted },
337
+ { text: `Needs config: ${needsConfig.length}; needs target: ${needsTarget.length}.`, fg: needsConfig.length > 0 || needsTarget.length > 0 ? PALETTE.warn : PALETTE.good },
338
+ { text: 'Enable only the channels you want; hidden channel fields appear after the channel is enabled.', fg: PALETTE.good },
339
+ );
340
+ } else if (category.id === 'onboarding-voice-media') {
341
+ const readiness = snapshot.voiceMediaReadiness;
342
+ base.push(
343
+ { text: `Voice: ${snapshot.voiceSurfaceEnabled ? 'enabled' : 'disabled'}; TTS ${snapshot.ttsProvider}; voice ${snapshot.ttsVoice}.`, fg: snapshot.voiceSurfaceEnabled ? PALETTE.good : PALETTE.info },
344
+ { text: `Media readiness: ${readiness.readyMediaProviderCount}/${snapshot.mediaProviderCount}; generation providers ${snapshot.mediaGenerationProviderCount}.`, fg: readiness.readyMediaProviderCount > 0 ? PALETTE.good : PALETTE.muted },
345
+ { text: `Telephony channel: ${snapshot.channels.find((channel) => channel.id === 'telephony')?.setupState ?? 'disabled'}.`, fg: PALETTE.info },
346
+ );
347
+ } else if (category.id === 'onboarding-context') {
348
+ base.push(
349
+ { text: `Local context: ${snapshot.localMemoryCount} memories, ${snapshot.localNoteCount} notes, ${snapshot.localPersonaCount} personas.`, fg: PALETTE.info },
350
+ { text: `Skills: ${snapshot.enabledSkillCount}/${snapshot.localSkillCount} enabled; routines ${snapshot.enabledRoutineCount}/${snapshot.localRoutineCount} enabled.`, fg: PALETTE.info },
351
+ { text: `Discovered files: personas ${snapshot.discoveredBehavior.personas.count}, skills ${snapshot.discoveredBehavior.skills.count}, routines ${snapshot.discoveredBehavior.routines.count}.`, fg: PALETTE.muted },
352
+ );
353
+ } else if (category.id === 'onboarding-automation') {
354
+ base.push(
355
+ { text: `Automation: ${snapshot.automationEnabled ? 'enabled' : 'disabled'}; max ${snapshot.automationMaxConcurrentRuns} concurrent; history ${snapshot.automationRunHistoryLimit}.`, fg: snapshot.automationEnabled ? PALETTE.good : PALETTE.muted },
356
+ { text: `Timeout ${snapshot.automationDefaultTimeoutMs} ms; catch-up ${snapshot.automationCatchUpWindowMinutes} min; cooldown ${snapshot.automationFailureCooldownMs} ms.`, fg: PALETTE.info },
357
+ { text: `Delete one-shot jobs after success: ${snapshot.automationDeleteAfterRun ? 'yes' : 'no'}.`, fg: snapshot.automationDeleteAfterRun ? PALETTE.info : PALETTE.muted },
358
+ );
294
359
  } else if (category.id === 'artifacts') {
295
360
  const mediaReady = snapshot.voiceMediaReadiness.readyMediaProviderCount;
296
361
  base.push(
@@ -16,8 +16,8 @@ function normalizeFullscreenViewport(lines: readonly Line[], width: number, heig
16
16
  return viewport;
17
17
  }
18
18
 
19
- export function createAgentWorkspaceFullscreenComposite(
20
- workspace: AgentWorkspace,
19
+ export function createFullscreenCompositeFromLines(
20
+ lines: readonly Line[],
21
21
  width: number,
22
22
  height: number,
23
23
  ): CompositeRequest {
@@ -25,9 +25,17 @@ export function createAgentWorkspaceFullscreenComposite(
25
25
  width,
26
26
  height,
27
27
  header: [],
28
- viewport: normalizeFullscreenViewport(renderAgentWorkspace(workspace, width, height), width, height),
28
+ viewport: normalizeFullscreenViewport(lines, width, height),
29
29
  footer: [],
30
30
  forceFullRedraw: true,
31
31
  panelWidth: 0,
32
32
  };
33
33
  }
34
+
35
+ export function createAgentWorkspaceFullscreenComposite(
36
+ workspace: AgentWorkspace,
37
+ width: number,
38
+ height: number,
39
+ ): CompositeRequest {
40
+ return createFullscreenCompositeFromLines(renderAgentWorkspace(workspace, width, height), width, height);
41
+ }
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '1.1.4';
9
+ let _version = '1.1.5';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
12
12
  readonly version?: unknown;