@pellux/goodvibes-agent 1.3.0 → 1.4.1

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.
@@ -7,7 +7,7 @@ export interface AgentWorkspaceSetupChecklistItem {
7
7
  readonly label: string;
8
8
  readonly status: AgentWorkspaceSetupStatus;
9
9
  readonly detail: string;
10
- readonly command?: string;
10
+ readonly breadcrumb?: string;
11
11
  }
12
12
 
13
13
  export interface AgentWorkspaceSetupChecklistInput {
@@ -42,10 +42,6 @@ export interface AgentWorkspaceSetupChecklistInput {
42
42
  readonly readyChannelCount: number;
43
43
  readonly voiceProviderCount: number;
44
44
  readonly mediaProviderCount: number;
45
- readonly installSmokeReceiptReady?: boolean;
46
- readonly browserPwaEnabled: boolean;
47
- readonly browserPwaPublicBaseUrl: string;
48
- readonly browserPwaFirstRunReceiptStatus: 'published' | 'not-published';
49
45
  readonly runtimeProfileCount: number;
50
46
  readonly runtimeStarterTemplateCount: number;
51
47
  }
@@ -69,20 +65,15 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
69
65
  : tokenPathKnown
70
66
  ? 'blocked'
71
67
  : 'recommended';
72
- const installSmokePrerequisitesReady = providerReady && (input.connectedHostTokenReadable || connectedHostAuthReceiptReady);
73
- const installSmokeReceiptReady = input.installSmokeReceiptReady === true;
74
68
  const hasActivePersona = input.activePersonaName !== '(none)' && input.activePersonaName !== '(unavailable)';
75
69
  const discoveredBehaviorCount = input.discoveredPersonas.count + input.discoveredSkills.count + input.discoveredRoutines.count;
76
- const browserPwaUrl = input.browserPwaPublicBaseUrl.trim();
77
- const browserPwaHasPublicUrl = browserPwaUrl.length > 0 && browserPwaUrl !== '(not configured)';
78
- const browserPwaReceiptPublished = input.browserPwaFirstRunReceiptStatus === 'published';
79
70
  return [
80
71
  {
81
72
  id: 'runtime',
82
73
  label: 'Connected host',
83
74
  status: 'ready',
84
75
  detail: `Agent will connect to ${input.runtimeBaseUrl}; protected host routes also need the Agent companion token below.`,
85
- command: 'Home -> Review health',
76
+ breadcrumb: 'Home -> Review health',
86
77
  },
87
78
  {
88
79
  id: 'connected-host-auth',
@@ -97,7 +88,7 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
97
88
  : tokenPathKnown
98
89
  ? `Provision Agent's local connected-host operator token at ${input.connectedHostTokenPath} before pairing channels, Knowledge, schedules, or protected daemon routes.`
99
90
  : 'Shell paths are unavailable in this runtime, so connected-host auth cannot be verified from the workspace snapshot.',
100
- command: 'Host -> Connected-host auth owner',
91
+ breadcrumb: 'Host -> Connected-host auth owner',
101
92
  },
102
93
  {
103
94
  id: 'provider-model',
@@ -106,18 +97,7 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
106
97
  detail: providerReady
107
98
  ? `Current chat route is ${input.provider} / ${input.model}.`
108
99
  : 'Choose a provider and model before relying on assistant turns.',
109
- command: 'Setup -> Provider and model',
110
- },
111
- {
112
- id: 'install-smoke',
113
- label: 'Install smoke',
114
- status: installSmokeReceiptReady ? 'ready' : installSmokePrerequisitesReady ? 'recommended' : tokenPathKnown ? 'blocked' : 'recommended',
115
- detail: installSmokeReceiptReady
116
- ? 'Durable setup smoke receipt is ready for release closeout; rerun smoke only after install, host, auth, or model changes.'
117
- : installSmokePrerequisitesReady
118
- ? 'Run setup smoke after install or migration to prove package start, connected host, auth, model route, setup posture, and first assistant turn.'
119
- : 'Resolve connected-host auth and provider/model setup before treating the first assistant turn as install-ready.',
120
- command: 'Start -> Install smoke',
100
+ breadcrumb: 'Setup -> Provider and model',
121
101
  },
122
102
  {
123
103
  id: 'subscriptions',
@@ -134,14 +114,14 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
134
114
  : input.availableSubscriptionProviderCount > 0
135
115
  ? `${input.availableSubscriptionProviderCount} subscription-capable provider(s) are available. Start login if you want subscription routing.`
136
116
  : 'No subscription-capable providers are available yet. Use API keys or add an OAuth provider service.',
137
- command: 'Start -> Start subscription login',
117
+ breadcrumb: 'Start -> Start subscription login',
138
118
  },
139
119
  {
140
120
  id: 'agent-knowledge',
141
121
  label: 'Agent Knowledge',
142
122
  status: 'recommended',
143
123
  detail: 'Check isolated Agent Knowledge status, then ingest source-backed material into the Agent segment only.',
144
- command: 'Knowledge',
124
+ breadcrumb: 'Knowledge',
145
125
  },
146
126
  {
147
127
  id: 'profile',
@@ -152,7 +132,7 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
152
132
  : discoveredBehaviorCount > 0
153
133
  ? `${discoveredBehaviorCount} discovered behavior file(s) can seed an isolated Agent profile from the Profiles workspace.`
154
134
  : `${input.runtimeStarterTemplateCount} starter template(s) are available if this machine needs separate operator identities.`,
155
- command: 'Profiles',
135
+ breadcrumb: 'Profiles',
156
136
  },
157
137
  {
158
138
  id: 'persona',
@@ -163,7 +143,7 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
163
143
  : input.discoveredPersonas.count > 0
164
144
  ? `${input.discoveredPersonas.count} discovered persona file(s) can be imported into the Agent-local registry.${sampleNames(input.discoveredPersonas)}`
165
145
  : 'Create or choose a persona to make the assistant voice and policy explicit.',
166
- command: 'Personas',
146
+ breadcrumb: 'Personas',
167
147
  },
168
148
  {
169
149
  id: 'skills',
@@ -180,7 +160,7 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
180
160
  : input.discoveredSkills.count > 0
181
161
  ? `${input.discoveredSkills.count} discovered skill file(s) can be imported as local reusable procedures.${sampleNames(input.discoveredSkills)}`
182
162
  : 'Create reusable local skills and bundles for repeated workflows.',
183
- command: 'Skills',
163
+ breadcrumb: 'Skills',
184
164
  },
185
165
  {
186
166
  id: 'routines',
@@ -193,7 +173,7 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
193
173
  : input.discoveredRoutines.count > 0
194
174
  ? `${input.discoveredRoutines.count} discovered routine file(s) can be imported as main-conversation workflows.${sampleNames(input.discoveredRoutines)}`
195
175
  : 'Create local routines first; promote schedules only with explicit confirmation.',
196
- command: 'Routines',
176
+ breadcrumb: 'Routines',
197
177
  },
198
178
  {
199
179
  id: 'memory',
@@ -202,7 +182,7 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
202
182
  detail: input.localMemoryCount > 0
203
183
  ? `${input.localMemoryCount} Agent memory record(s) are available; ${input.localMemoryReviewQueueCount} need review.`
204
184
  : 'Memory starts empty; durable facts should be stored deliberately and never include secrets.',
205
- command: 'Memory',
185
+ breadcrumb: 'Memory',
206
186
  },
207
187
  {
208
188
  id: 'notes',
@@ -211,7 +191,7 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
211
191
  detail: input.localNoteCount > 0
212
192
  ? `${input.localNoteCount} Agent scratchpad note(s) are available; ${input.localNoteReviewQueueCount} need review.`
213
193
  : 'Notes start empty; use them for source triage, temporary decisions, and handoff before promoting anything durable.',
214
- command: 'Notes',
194
+ breadcrumb: 'Notes',
215
195
  },
216
196
  {
217
197
  id: 'channels',
@@ -220,27 +200,14 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
220
200
  detail: input.readyChannelCount > 0
221
201
  ? `${input.readyChannelCount} external channel(s) are ready.`
222
202
  : 'Pair or review channels only when you want the assistant reachable outside this terminal.',
223
- command: 'Channels',
224
- },
225
- {
226
- id: 'browser-pwa',
227
- label: 'Browser/PWA',
228
- status: browserPwaReceiptPublished ? 'ready' : 'recommended',
229
- detail: browserPwaReceiptPublished
230
- ? `Connected-host browser/PWA first-run receipt is published${browserPwaHasPublicUrl ? ` for ${browserPwaUrl}` : ''}.`
231
- : input.browserPwaEnabled
232
- ? browserPwaHasPublicUrl
233
- ? `Browser cockpit is openable at ${browserPwaUrl}, but the connected-host browser/PWA first-run completion receipt is not published yet. Terminal Agent remains primary until that receipt exists.`
234
- : 'Browser cockpit is enabled through the connected-host web endpoint, but the connected-host browser/PWA first-run completion receipt is not published yet.'
235
- : 'Enable the connected-host web endpoint and publish the browser/PWA first-run completion receipt before treating browser access as setup-ready.',
236
- command: 'Voice & Media -> Browser/PWA readiness',
203
+ breadcrumb: 'Channels',
237
204
  },
238
205
  {
239
206
  id: 'voice-media',
240
207
  label: 'Voice and media',
241
208
  status: input.voiceProviderCount > 0 || input.mediaProviderCount > 0 ? 'ready' : 'optional',
242
209
  detail: `${input.voiceProviderCount} voice provider(s), ${input.mediaProviderCount} media provider(s). Configure these only when useful.`,
243
- command: 'Voice & Media',
210
+ breadcrumb: 'Voice & Media',
244
211
  },
245
212
  ];
246
213
  }
@@ -463,9 +463,6 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
463
463
  const browserPwaEnabled = readConfigBoolean(context, 'web.enabled', false);
464
464
  const browserPwaPublicBaseUrl = readConfigString(context, 'web.publicBaseUrl', '(not configured)');
465
465
  const connectedHostAuthReceiptReady = setupStepHasSatisfyingReceipt(durableSetupReceipts, 'connected-host-auth');
466
- const installSmokeReceiptReady = setupSmokeHistory.latestResult === 'ready-for-user-run'
467
- || setupStepHasSatisfyingReceipt(durableSetupReceipts, 'install-smoke');
468
- const browserPwaReceiptReady = setupStepHasSatisfyingReceipt(durableSetupReceipts, 'browser-pwa');
469
466
  const setupChecklist = buildAgentWorkspaceSetupChecklist({
470
467
  provider,
471
468
  model,
@@ -498,10 +495,6 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
498
495
  readyChannelCount: channels.filter((channel) => channel.ready).length,
499
496
  voiceProviderCount: voiceProviders.length,
500
497
  mediaProviderCount: mediaProviders.length,
501
- installSmokeReceiptReady,
502
- browserPwaEnabled,
503
- browserPwaPublicBaseUrl,
504
- browserPwaFirstRunReceiptStatus: browserPwaReceiptReady ? 'published' : 'not-published',
505
498
  runtimeProfileCount: runtimeProfiles.length,
506
499
  runtimeStarterTemplateCount: runtimeStarterTemplates.length,
507
500
  });
@@ -7,6 +7,8 @@ import type { AgentSetupWizard } from '../agent/setup-wizard.ts';
7
7
 
8
8
  export const AGENT_WORKSPACE_MODAL_NAME = 'agentWorkspace';
9
9
 
10
+ export type AgentWorkspaceCategoryGroup = 'START' | 'ONBOARDING' | 'DAY-TO-DAY' | 'CAPABILITIES' | 'LOCAL BEHAVIOR' | 'OPERATIONS' | 'FINISH';
11
+
10
12
  export type AgentWorkspaceFocusPane = 'categories' | 'actions';
11
13
 
12
14
  export const AGENT_WORKSPACE_CATEGORY_IDS = [
@@ -310,11 +312,12 @@ export interface AgentWorkspaceAction {
310
312
  readonly localOperation?: AgentWorkspaceLocalOperation;
311
313
  readonly kind: AgentWorkspaceActionKind;
312
314
  readonly safety: 'safe' | 'read-only' | 'delegates' | 'blocked';
315
+ readonly commandBehavior?: 'inline' | 'compose' | 'exit';
313
316
  }
314
317
 
315
318
  export interface AgentWorkspaceCategory {
316
319
  readonly id: AgentWorkspaceCategoryId;
317
- readonly group: string;
320
+ readonly group: AgentWorkspaceCategoryGroup;
318
321
  readonly label: string;
319
322
  readonly summary: string;
320
323
  readonly detail: string;
@@ -328,7 +331,7 @@ export interface AgentWorkspaceActionSearchResult {
328
331
  readonly actionIndex: number;
329
332
  }
330
333
 
331
- export type AgentWorkspaceCommandDispatcher = (command: string) => void;
334
+ export type AgentWorkspaceCommandDispatcher = (command: string, behavior?: 'inline' | 'compose' | 'exit') => void;
332
335
  export type AgentWorkspacePromptDispatcher = (prompt: string) => void;
333
336
 
334
337
  export type AgentWorkspaceActionResultKind = 'guidance' | 'blocked' | 'dispatched' | 'refreshed' | 'error';
@@ -19,10 +19,11 @@ import { deleteAgentWorkspaceMemoryEditor, submitAgentWorkspaceMemoryEditor } fr
19
19
  import { jumpAgentWorkspaceSelection, moveAgentWorkspaceSelection, selectAgentWorkspaceCategory } from './agent-workspace-navigation.ts';
20
20
  import { appendAgentWorkspaceActionSearchText, backspaceAgentWorkspaceActionSearch, beginAgentWorkspaceActionSearch, clearAgentWorkspaceActionSearch, commitAgentWorkspaceActionSearchSelection, searchAgentWorkspaceActions } from './agent-workspace-search.ts';
21
21
  import { applyAgentWorkspaceSetupCheckpointAction } from './agent-workspace-setup-checkpoint-action.ts';
22
- import { agentWorkspaceSettingSchema, applyAgentWorkspaceSettingValue, buildAgentWorkspaceSettingActionEffect, buildAgentWorkspaceSettingActionPreview, importAgentWorkspaceTuiSettings, isAgentWorkspaceActionVisible } from './agent-workspace-settings.ts';
22
+ import { agentWorkspaceSettingSchema, applyAgentWorkspaceSettingValue, buildAgentWorkspaceSettingActionDisplay, buildAgentWorkspaceSettingActionEffect, importAgentWorkspaceTuiSettings, isAgentWorkspaceActionVisible, type AgentWorkspaceSettingActionDisplay } from './agent-workspace-settings.ts';
23
23
  import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
24
24
  import { submitAgentWorkspaceSubscriptionLoginFinishEditor, submitAgentWorkspaceSubscriptionLoginStartEditor, submitAgentWorkspaceSubscriptionLogoutEditor } from './agent-workspace-subscription-editor.ts';
25
- import type { AgentWorkspaceAction, AgentWorkspaceActionResult, AgentWorkspaceActionSearchResult, AgentWorkspaceCategory, AgentWorkspaceCommandDispatcher, AgentWorkspaceEditorField, AgentWorkspaceFocusPane, AgentWorkspaceLocalEditor, AgentWorkspaceLocalEditorKind, AgentWorkspaceLocalLibraryItem, AgentWorkspaceLocalOperation, AgentWorkspacePromptDispatcher, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
25
+ import type { AgentWorkspaceAction, AgentWorkspaceActionResult, AgentWorkspaceActionSearchResult, AgentWorkspaceCategory, AgentWorkspaceCategoryGroup, AgentWorkspaceCommandDispatcher, AgentWorkspaceEditorField, AgentWorkspaceFocusPane, AgentWorkspaceLocalEditor, AgentWorkspaceLocalEditorKind, AgentWorkspaceLocalLibraryItem, AgentWorkspaceLocalOperation, AgentWorkspacePromptDispatcher, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
26
+ import { ONBOARDING_COMPLETE_SYNTHETIC_ACTION, shouldShowOnboardingFinishFooter } from './agent-workspace-onboarding-finish.ts';
26
27
  import { writeOnboardingCheckMarker, writeOnboardingCompletionMarker } from '../runtime/onboarding/index.ts';
27
28
 
28
29
  export type { AgentWorkspaceChannelRisk, AgentWorkspaceChannelStatus } from './agent-workspace-channels.ts';
@@ -46,11 +47,13 @@ export class AgentWorkspace {
46
47
  private context: CommandContext | null = null;
47
48
  private dispatchCommand: AgentWorkspaceCommandDispatcher | null = null;
48
49
  private dispatchPrompt: AgentWorkspacePromptDispatcher | null = null;
50
+ private _onlyGroup: AgentWorkspaceCategoryGroup | null = null;
49
51
 
50
- open(context: CommandContext, dispatchCommand: AgentWorkspaceCommandDispatcher, categoryId?: string, dispatchPrompt?: AgentWorkspacePromptDispatcher): void {
52
+ open(context: CommandContext, dispatchCommand: AgentWorkspaceCommandDispatcher, categoryId?: string, dispatchPrompt?: AgentWorkspacePromptDispatcher, onlyGroup?: AgentWorkspaceCategoryGroup): void {
51
53
  this.context = context;
52
54
  this.dispatchCommand = dispatchCommand;
53
55
  this.dispatchPrompt = dispatchPrompt ?? null;
56
+ this._onlyGroup = onlyGroup ?? null;
54
57
  this.runtimeSnapshot = buildAgentWorkspaceRuntimeSnapshot(context);
55
58
  this.active = true;
56
59
  this.focusPane = 'actions';
@@ -85,9 +88,13 @@ export class AgentWorkspace {
85
88
  this.localEditor = null;
86
89
  this.actionSearchActive = false;
87
90
  this.actionSearchQuery = '';
91
+ this._onlyGroup = null;
88
92
  }
89
93
 
90
94
  get categories(): readonly AgentWorkspaceCategory[] {
95
+ if (this._onlyGroup) {
96
+ return AGENT_WORKSPACE_CATEGORIES.filter((category) => category.group === this._onlyGroup);
97
+ }
91
98
  return AGENT_WORKSPACE_CATEGORIES;
92
99
  }
93
100
 
@@ -97,7 +104,11 @@ export class AgentWorkspace {
97
104
 
98
105
  get actions(): readonly AgentWorkspaceAction[] {
99
106
  if (this.actionSearchActive) return this.actionSearchResults.map((result) => result.action);
100
- return this.selectedCategory.actions.filter((action) => isAgentWorkspaceActionVisible(this.context, action));
107
+ const base = this.selectedCategory.actions.filter((action) => isAgentWorkspaceActionVisible(this.context, action));
108
+ if (shouldShowOnboardingFinishFooter(this.selectedCategory, base)) {
109
+ return [...base, ONBOARDING_COMPLETE_SYNTHETIC_ACTION];
110
+ }
111
+ return base;
101
112
  }
102
113
 
103
114
  get selectedAction(): AgentWorkspaceAction | null {
@@ -142,8 +153,8 @@ export class AgentWorkspace {
142
153
  return selectedAgentWorkspaceLocalLibraryItem(this.runtimeSnapshot, this.selectedLibraryItemIndexes, kind);
143
154
  }
144
155
 
145
- settingActionPreview(action: AgentWorkspaceAction): string | null {
146
- return buildAgentWorkspaceSettingActionPreview(this.context, action);
156
+ settingActionDisplay(action: AgentWorkspaceAction): AgentWorkspaceSettingActionDisplay | null {
157
+ return buildAgentWorkspaceSettingActionDisplay(this.context, action);
147
158
  }
148
159
 
149
160
  focusCategories(): void {
@@ -279,8 +290,8 @@ export class AgentWorkspace {
279
290
  return Boolean(this.context?.submitInput && this.dispatchPrompt);
280
291
  }
281
292
 
282
- dispatchWorkspaceCommand(command: string): void {
283
- this.dispatchCommand?.(command);
293
+ dispatchWorkspaceCommand(command: string, behavior?: 'inline' | 'compose' | 'exit'): void {
294
+ this.dispatchCommand?.(command, behavior);
284
295
  }
285
296
 
286
297
  dispatchWorkspacePrompt(prompt: string): void {
@@ -366,15 +366,16 @@ export class InputHandler {
366
366
  this.mcpWorkspace.open(context);
367
367
  this.requestRender();
368
368
  }
369
- public openAgentWorkspace(context: CommandContext, categoryId?: string): void {
369
+ public openAgentWorkspace(context: CommandContext, categoryId?: string, onlyGroup?: string): void {
370
370
  this.panelFocused = false;
371
371
  this.indicatorFocused = false;
372
372
  this.modalOpened('agentWorkspace');
373
373
  this.agentWorkspace.open(
374
374
  context,
375
- (command) => this.dispatchAgentWorkspaceCommand(command, context),
375
+ (command, behavior) => this.dispatchAgentWorkspaceCommand(command, context, behavior),
376
376
  categoryId,
377
377
  (prompt) => this.dispatchAgentWorkspacePrompt(prompt, context),
378
+ onlyGroup as import('./agent-workspace-types.ts').AgentWorkspaceCategoryGroup | undefined,
378
379
  );
379
380
  this.requestRender();
380
381
  }
@@ -395,10 +396,71 @@ export class InputHandler {
395
396
  return true;
396
397
  }
397
398
 
398
- private dispatchAgentWorkspaceCommand(command: string, context: CommandContext): void {
399
- this.closeAgentWorkspaceModal();
399
+ public dispatchAgentWorkspaceCommand(command: string, context: CommandContext, behavior?: 'inline' | 'compose' | 'exit'): void {
400
+ const resolved = behavior ?? 'compose';
400
401
  const { name, args } = parseSlashCommand(command);
401
402
  if (!name) return;
403
+ if (resolved === 'exit') {
404
+ this.closeAgentWorkspaceModal();
405
+ void context.executeCommand?.(name, [...args]).catch((error: unknown) => {
406
+ context.print(`Agent workspace command failed: ${error instanceof Error ? error.message : String(error)}`);
407
+ this.requestRender();
408
+ });
409
+ this.requestRender();
410
+ return;
411
+ }
412
+ if (resolved === 'inline') {
413
+ const capturedLines: string[] = [];
414
+ const originalPrint = context.print;
415
+ if (typeof context.executeCommand !== 'function') {
416
+ this.agentWorkspace.lastActionResult = {
417
+ kind: 'error',
418
+ title: `Command unavailable: ${command}`,
419
+ detail: 'No command dispatcher is configured for this runtime.',
420
+ command,
421
+ safety: 'safe',
422
+ };
423
+ this.requestRender();
424
+ return;
425
+ }
426
+ context.print = (text: string): void => {
427
+ capturedLines.push(text);
428
+ };
429
+ const buildCapturedDetail = (): string => {
430
+ const MAX_CHARS = 1500;
431
+ const MAX_LINES = 12;
432
+ const lines = capturedLines.slice(-MAX_LINES);
433
+ const joined = lines.join('\n');
434
+ if (joined.length > MAX_CHARS) {
435
+ return joined.slice(0, MAX_CHARS) + '\n… (output truncated)';
436
+ }
437
+ const omitted = capturedLines.length - lines.length;
438
+ return omitted > 0 ? `… (${omitted} line(s) truncated)\n${joined}` : joined || '(no output)';
439
+ };
440
+ void context.executeCommand(name, [...args]).then(() => {
441
+ context.print = originalPrint;
442
+ this.agentWorkspace.lastActionResult = {
443
+ kind: 'dispatched',
444
+ title: `Result: ${command}`,
445
+ detail: buildCapturedDetail(),
446
+ command,
447
+ safety: 'safe',
448
+ };
449
+ this.requestRender();
450
+ }).catch((error: unknown) => {
451
+ context.print = originalPrint;
452
+ this.agentWorkspace.lastActionResult = {
453
+ kind: 'error',
454
+ title: `${command} failed`,
455
+ detail: error instanceof Error ? error.message : String(error),
456
+ command,
457
+ };
458
+ this.requestRender();
459
+ });
460
+ return;
461
+ }
462
+ // compose (default): close workspace then execute
463
+ this.closeAgentWorkspaceModal();
402
464
  void context.executeCommand?.(name, [...args]).catch((error: unknown) => {
403
465
  context.print(`Agent workspace command failed: ${error instanceof Error ? error.message : String(error)}`);
404
466
  this.requestRender();
@@ -674,7 +736,7 @@ export class InputHandler {
674
736
  this.modalOpened('agentWorkspace');
675
737
  this.agentWorkspace.open(
676
738
  context,
677
- (command) => this.dispatchAgentWorkspaceCommand(command, context),
739
+ (command, behavior) => this.dispatchAgentWorkspaceCommand(command, context, behavior),
678
740
  undefined,
679
741
  (prompt) => this.dispatchAgentWorkspacePrompt(prompt, context),
680
742
  );
@@ -46,58 +46,27 @@ function setupAttentionItems(snapshot: AgentWorkspaceRuntimeSnapshot, limit: num
46
46
 
47
47
  function setupOverviewLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
48
48
  const counts = setupCounts(snapshot);
49
- const nextItems = setupAttentionItems(snapshot, 3);
50
- const wizard = snapshot.setupWizard;
49
+ const attentionItems = setupAttentionItems(snapshot, 1);
50
+ const doneCount = counts.ready;
51
+ const totalCount = snapshot.setupChecklist.length;
52
+ const attentionCount = counts.blocked + counts.recommended;
53
+ const progressDetail = attentionCount > 0
54
+ ? `${doneCount} of ${totalCount} done — ${attentionCount} need attention`
55
+ : `${doneCount} of ${totalCount} done`;
51
56
  const lines: ContextLine[] = [
52
57
  { text: 'Onboarding', fg: PALETTE.title, bold: true },
53
- { text: `${counts.ready}/${snapshot.setupChecklist.length} ready; ${counts.recommended} recommended; ${counts.optional} optional; ${counts.blocked} blocked.`, fg: counts.blocked > 0 ? PALETTE.warn : PALETTE.info },
54
- { text: `Setup wizard: ${wizard.completedSteps}/${wizard.totalSteps} done; ${wizard.currentStepLabel ? `current ${wizard.currentStepLabel}` : wizard.status}.`, fg: wizard.status === 'complete' ? PALETTE.good : wizard.status === 'blocked' ? PALETTE.warn : PALETTE.info },
55
- { text: `Wizard next: ${compactText(wizard.next, 112)}`, fg: wizard.status === 'complete' ? PALETTE.good : wizard.status === 'blocked' ? PALETTE.warn : PALETTE.info },
56
- { text: `Setup closeout: ${wizard.closeout.label}; ${compactText(wizard.closeout.nextAction, 104)}`, fg: wizard.closeout.status === 'complete' || wizard.closeout.status === 'ready-to-finish' ? PALETTE.good : wizard.closeout.status === 'blocked' ? PALETTE.warn : PALETTE.info },
58
+ { text: progressDetail, fg: counts.blocked > 0 ? PALETTE.warn : doneCount === totalCount ? PALETTE.good : PALETTE.info },
57
59
  { text: `Chat: ${snapshot.provider} / ${snapshot.modelDisplayName}.`, fg: PALETTE.info },
58
60
  { text: `Local: ${snapshot.localPersonaCount} personas, ${snapshot.localSkillCount} skills, ${snapshot.localRoutineCount} routines, ${snapshot.localMemoryCount} memories.`, fg: PALETTE.info },
59
61
  ];
60
- if (nextItems.length > 0) {
61
- const item = nextItems[0]!;
62
+ if (attentionItems.length > 0) {
63
+ const item = attentionItems[0]!;
62
64
  lines.push({
63
65
  text: `Next: ${item.label} (${setupStatusLabel(item.status).toLowerCase()})`,
64
66
  fg: setupStatusColor(item.status),
65
67
  bold: item.status === 'blocked',
66
68
  });
67
69
  }
68
- if (wizard.repeatedBlocker) {
69
- lines.push({
70
- text: `Repeated blocker: ${wizard.repeatedBlocker.checkId} in ${wizard.repeatedBlocker.count} saved smoke run(s).`,
71
- fg: PALETTE.warn,
72
- });
73
- }
74
- if (wizard.checkpoint.status !== 'none') {
75
- lines.push({
76
- text: `Setup checkpoint: ${compactText(wizard.checkpoint.summary, 112)}`,
77
- fg: wizard.checkpoint.status === 'stale' || wizard.checkpoint.status === 'unavailable' ? PALETTE.warn : PALETTE.info,
78
- });
79
- }
80
- if (wizard.smokeHistory.status === 'available') {
81
- lines.push({
82
- text: `Smoke history: ${wizard.smokeHistory.total} run(s); trend ${wizard.smokeHistory.trend}; latest ${wizard.smokeHistory.latestResult ?? 'unknown'}.`,
83
- fg: wizard.smokeHistory.latestResult === 'blocked' ? PALETTE.warn : PALETTE.info,
84
- });
85
- }
86
- if (wizard.stepHistory.length > 0) {
87
- const latest = wizard.stepHistory[0]!;
88
- lines.push({
89
- text: `Step history: ${wizard.stepHistory.length} recorded; latest ${latest.stepLabel} ${latest.kind} at ${latest.recordedAt}.`,
90
- fg: PALETTE.good,
91
- });
92
- }
93
- if (wizard.receiptGaps.length > 0) {
94
- const labels = wizard.receiptGaps.slice(0, 3).map((gap) => gap.stepLabel).join(', ');
95
- const suffix = wizard.receiptGaps.length > 3 ? `, +${wizard.receiptGaps.length - 3} more` : '';
96
- lines.push({
97
- text: `Receipt gaps: ${labels}${suffix} still need durable setup receipt ids.`,
98
- fg: PALETTE.warn,
99
- });
100
- }
101
70
  return lines;
102
71
  }
103
72
 
@@ -551,7 +520,7 @@ export function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspac
551
520
  companionAccessLine(snapshot),
552
521
  { text: `Channels: ${readyCount}/${snapshot.channels.length} ready; ${enabledCount} enabled; ${configuredDefaults} target(s).`, fg: PALETTE.info },
553
522
  { text: `Setup guide: ${guide.progressLabel}; ${guide.currentChannelLabel ?? 'choose a channel'}.`, fg: guide.status === 'ready' ? PALETTE.good : PALETTE.warn },
554
- { text: `Next: ${currentGuideStep ? `${currentGuideStep.label} - ${compactText(currentGuideStep.userRoute)}` : 'All enabled channels ready.'}`, fg: currentGuideStep ? PALETTE.warn : PALETTE.good },
523
+ { text: `Next: ${currentGuideStep ? currentGuideStep.label : 'All enabled channels ready.'}`, fg: currentGuideStep ? PALETTE.warn : PALETTE.good },
555
524
  { text: 'Guide checks setup schema, accounts, allowlist policy, live status, and explicit test sends.', fg: PALETTE.good },
556
525
  { text: 'Triage: /channels triage shows blockers, delivery retries, surface messages, route bindings, and receipts.', fg: PALETTE.good },
557
526
  { text: 'Secrets hidden; sends require explicit action.', fg: PALETTE.warn },