@pellux/goodvibes-agent 1.3.0 → 1.4.2

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 (37) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +0 -1
  3. package/dist/package/{ast-grep-napi.linux-x64-gnu-swtppvy9.node → ast-grep-napi.linux-x64-gnu-mkk8xwww.node} +0 -0
  4. package/dist/package/{ast-grep-napi.linux-x64-musl-ttfcdtap.node → ast-grep-napi.linux-x64-musl-ryqtgdv6.node} +0 -0
  5. package/dist/package/main.js +55623 -55535
  6. package/docs/README.md +0 -1
  7. package/docs/tools-and-commands.md +0 -3
  8. package/package.json +1 -1
  9. package/release/release-notes.md +7 -156
  10. package/src/agent/competitive-feature-inventory.ts +1 -1
  11. package/src/agent/setup-wizard.ts +28 -21
  12. package/src/cli/tui-startup.ts +2 -2
  13. package/src/input/agent-workspace-activation.ts +2 -2
  14. package/src/input/agent-workspace-categories.ts +3 -20
  15. package/src/input/agent-workspace-onboarding-finish.ts +21 -0
  16. package/src/input/agent-workspace-settings.ts +14 -19
  17. package/src/input/agent-workspace-setup-snapshot.ts +2 -10
  18. package/src/input/agent-workspace-setup.ts +14 -47
  19. package/src/input/agent-workspace-snapshot.ts +0 -7
  20. package/src/input/agent-workspace-types.ts +5 -2
  21. package/src/input/agent-workspace.ts +19 -8
  22. package/src/input/commands.ts +0 -4
  23. package/src/input/handler.ts +70 -5
  24. package/src/main.ts +1 -40
  25. package/src/panels/builtin/agent.ts +0 -28
  26. package/src/renderer/agent-workspace-context-lines.ts +11 -42
  27. package/src/renderer/agent-workspace.ts +414 -31
  28. package/src/renderer/help-overlay.ts +0 -2
  29. package/src/tools/agent-harness-setup-posture.ts +7 -7
  30. package/src/version.ts +1 -1
  31. package/docs/project-planning.md +0 -81
  32. package/src/input/commands/planning-runtime.ts +0 -215
  33. package/src/input/commands/work-plan-runtime.ts +0 -191
  34. package/src/panels/plan-dashboard-panel.ts +0 -274
  35. package/src/panels/project-planning-panel.ts +0 -721
  36. package/src/panels/work-plan-panel.ts +0 -175
  37. package/src/planning/project-planning-coordinator.ts +0 -543
@@ -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 },
@@ -5,6 +5,8 @@ import type {
5
5
  AgentWorkspaceLocalEditor,
6
6
  AgentWorkspaceRuntimeSnapshot,
7
7
  } from '../input/agent-workspace.ts';
8
+ import type { AgentWorkspaceEditorKind } from '../input/agent-workspace-types.ts';
9
+ import type { AgentWorkspaceSetupChecklistItem } from '../input/agent-workspace-setup.ts';
8
10
  import type { Line } from '../types/grid.ts';
9
11
  import { wrapText } from '../utils/terminal-width.ts';
10
12
  import { GLYPHS } from './ui-primitives.ts';
@@ -18,11 +20,36 @@ import {
18
20
  } from './fullscreen-workspace.ts';
19
21
  import { actionResultColor, type AgentWorkspaceContextLine as ContextLine } from './agent-workspace-style.ts';
20
22
  import { compactText, reviewerReadinessContextLines, snapshotLines } from './agent-workspace-context-lines.ts';
23
+ import { ONBOARDING_COMPLETE_SYNTHETIC_ACTION, ONBOARDING_CRITICAL_STEP_IDS } from '../input/agent-workspace-onboarding-finish.ts';
24
+
25
+ function onboardingCategoryReadiness(
26
+ category: AgentWorkspaceCategory,
27
+ checklist: readonly AgentWorkspaceSetupChecklistItem[],
28
+ ): 'ready' | 'attention' | 'optional' {
29
+ const CATEGORY_CHECKLIST_MAP: Record<string, readonly string[]> = {
30
+ 'setup': ['provider-model', 'connected-host-auth', 'runtime'],
31
+ 'account-model': ['provider-model', 'subscriptions'],
32
+ 'assistant-behavior': [],
33
+ 'tools-permissions': [],
34
+ 'onboarding-display': [],
35
+ 'onboarding-channels': ['channels'],
36
+ 'onboarding-voice-media': ['voice-media'],
37
+ 'onboarding-context': ['agent-knowledge', 'persona', 'skills', 'routines', 'memory', 'notes', 'profile'],
38
+ 'onboarding-automation': [],
39
+ };
40
+ const itemIds = CATEGORY_CHECKLIST_MAP[category.id];
41
+ if (!itemIds || itemIds.length === 0) return 'optional';
42
+ const mapped = itemIds.map((id) => checklist.find((item) => item.id === id));
43
+ if (mapped.every((item) => item?.status === 'ready')) return 'ready';
44
+ if (mapped.some((item) => item?.status === 'blocked' || item?.status === 'recommended')) return 'attention';
45
+ return 'optional';
46
+ }
21
47
 
22
48
  function buildLeftRows(workspace: AgentWorkspace, height: number): WorkspaceRow[] {
23
49
  const rows: WorkspaceRow[] = [];
24
50
  let selectedRenderedIndex = 0;
25
51
  let lastGroup = '';
52
+ const checklist = workspace.runtimeSnapshot?.setupChecklist ?? [];
26
53
 
27
54
  workspace.categories.forEach((category, index) => {
28
55
  if (category.group !== lastGroup) {
@@ -32,11 +59,24 @@ function buildLeftRows(workspace: AgentWorkspace, height: number): WorkspaceRow[
32
59
  const selected = index === workspace.selectedCategoryIndex;
33
60
  if (selected) selectedRenderedIndex = rows.length;
34
61
  const marker = selected ? GLYPHS.navigation.selected : ' ';
62
+ let readinessGlyph = '';
63
+ let readinessFg: string | undefined;
64
+ if (isOnboardingCategory(category)) {
65
+ const readiness = onboardingCategoryReadiness(category, checklist);
66
+ if (readiness === 'ready') {
67
+ readinessGlyph = GLYPHS.status.success;
68
+ readinessFg = PALETTE.good;
69
+ } else if (readiness === 'attention') {
70
+ readinessGlyph = '!';
71
+ readinessFg = PALETTE.warn;
72
+ }
73
+ }
74
+ const readinessMark = readinessGlyph ? `${readinessGlyph} ` : ' ';
35
75
  rows.push({
36
- text: ` ${marker} ${category.label}`,
76
+ text: ` ${marker} ${readinessMark}${category.label}`,
37
77
  selected: selected && workspace.focusPane === 'categories',
38
78
  kind: 'item',
39
- fg: selected ? PALETTE.text : PALETTE.muted,
79
+ fg: selected ? PALETTE.text : readinessFg ?? PALETTE.muted,
40
80
  bold: selected,
41
81
  });
42
82
  });
@@ -72,41 +112,323 @@ function isOnboardingCategory(category: AgentWorkspaceCategory): boolean {
72
112
  return category.group === 'ONBOARDING';
73
113
  }
74
114
 
75
- function onboardingActionChange(workspace: AgentWorkspace, action: AgentWorkspaceAction): string {
76
- if (action.kind === 'setting') {
77
- return workspace.settingActionPreview(action) ?? 'Update setting';
115
+ interface OnboardingActionColumns {
116
+ readonly setting: string;
117
+ readonly defaultValue: string;
118
+ readonly currentValue: string;
119
+ }
120
+
121
+ function editorPurposeLabel(editorKind: AgentWorkspaceEditorKind): string {
122
+ switch (editorKind) {
123
+ case 'mcp-server': return 'Edit MCP server';
124
+ case 'mcp-tools-server': return 'Edit MCP tools server';
125
+ case 'mcp-repair': return 'Repair MCP connection';
126
+ case 'secret-link': return 'Link secret reference';
127
+ case 'secret-set': return 'Store secret';
128
+ case 'secret-test': return 'Test secret reference';
129
+ case 'secret-delete': return 'Delete secret';
130
+ case 'subscription-login-start': return 'Sign in to provider';
131
+ case 'subscription-login-finish': return 'Finish provider sign-in';
132
+ case 'subscription-logout': return 'Sign out of provider';
133
+ case 'subscription-inspect': return 'Inspect subscription';
134
+ case 'provider-add': return 'Add custom provider';
135
+ case 'provider-remove': return 'Remove custom provider';
136
+ case 'provider-use': return 'Choose provider';
137
+ case 'provider-inspect': return 'Inspect provider';
138
+ case 'provider-routes': return 'Edit provider routes';
139
+ case 'provider-account-repair': return 'Repair provider account';
140
+ case 'memory': return 'Edit memory';
141
+ case 'memory-search': return 'Search memory';
142
+ case 'memory-get': return 'Get memory record';
143
+ case 'memory-explain': return 'Explain memory';
144
+ case 'memory-promote': return 'Promote memory';
145
+ case 'memory-link': return 'Link memory';
146
+ case 'memory-export': return 'Export memory';
147
+ case 'memory-import': return 'Import memory';
148
+ case 'memory-handoff-export': return 'Export memory handoff';
149
+ case 'memory-handoff-inspect': return 'Inspect memory handoff';
150
+ case 'memory-handoff-import': return 'Import memory handoff';
151
+ case 'memory-vector-rebuild': return 'Rebuild memory vectors';
152
+ case 'note': return 'Edit note';
153
+ case 'persona': return 'Edit persona';
154
+ case 'persona-search': return 'Search personas';
155
+ case 'persona-show': return 'Show persona';
156
+ case 'persona-discovery-import': return 'Import persona files';
157
+ case 'skill': return 'Edit skill';
158
+ case 'skill-search': return 'Search skills';
159
+ case 'skill-show': return 'Show skill';
160
+ case 'skill-discovery-import': return 'Import skill files';
161
+ case 'routine': return 'Edit routine';
162
+ case 'routine-search': return 'Search routines';
163
+ case 'routine-show': return 'Show routine';
164
+ case 'routine-discovery-import': return 'Import routine files';
165
+ case 'profile': return 'Edit profile';
166
+ case 'profile-from-discovered': return 'Create profile from files';
167
+ case 'profile-show': return 'Show profile';
168
+ case 'profile-default': return 'Set default profile';
169
+ case 'profile-default-clear': return 'Clear default profile';
170
+ case 'profile-delete': return 'Delete profile';
171
+ case 'profile-template-export': return 'Export profile template';
172
+ case 'profile-template-import': return 'Import profile template';
173
+ case 'profile-template-show': return 'Show profile template';
174
+ case 'profile-template-from-discovered': return 'Create template from files';
175
+ case 'knowledge-url': return 'Ingest URL';
176
+ case 'knowledge-urls': return 'Ingest URLs';
177
+ case 'knowledge-file': return 'Ingest file';
178
+ case 'knowledge-bookmarks': return 'Import bookmarks';
179
+ case 'knowledge-browser-history': return 'Import browser history';
180
+ case 'knowledge-connector-ingest': return 'Run connector ingest';
181
+ case 'knowledge-connector-show': return 'Show connector';
182
+ case 'knowledge-connector-doctor': return 'Check connector health';
183
+ case 'knowledge-reindex': return 'Reindex knowledge';
184
+ case 'knowledge-search': return 'Search knowledge';
185
+ case 'knowledge-ask': return 'Ask knowledge';
186
+ case 'knowledge-get': return 'Get knowledge record';
187
+ case 'knowledge-map': return 'Map knowledge';
188
+ case 'knowledge-review-issue': return 'Review knowledge issue';
189
+ case 'knowledge-consolidate': return 'Consolidate knowledge';
190
+ case 'knowledge-packet': return 'Build knowledge packet';
191
+ case 'knowledge-explain': return 'Explain knowledge';
192
+ case 'delegate-task': return 'Delegate build task';
193
+ case 'document-browse': return 'Browse documents';
194
+ case 'document-show': return 'Show document';
195
+ case 'document-create': return 'Create document';
196
+ case 'document-update': return 'Revise document';
197
+ case 'document-review': return 'Review document';
198
+ case 'document-comment': return 'Add comment';
199
+ case 'document-resolve-comment': return 'Resolve comment';
200
+ case 'document-suggest': return 'Propose suggestion';
201
+ case 'document-accept-suggestion': return 'Accept suggestion';
202
+ case 'document-reject-suggestion': return 'Reject suggestion';
203
+ case 'document-insert-artifact': return 'Insert artifact';
204
+ case 'document-attach-artifact': return 'Attach artifact';
205
+ case 'document-export': return 'Export document';
206
+ case 'document-reviewer-readiness': return 'Review readiness preflight';
207
+ case 'document-review-packet-wizard': return 'Run packet wizard';
208
+ case 'document-review-packet-preset': return 'Save packet preset';
209
+ case 'document-review-packet-preset-refresh': return 'Refresh packet preset';
210
+ case 'document-review-packet-share': return 'Share review packet';
211
+ case 'model-compare': return 'Compare models';
212
+ case 'model-compare-review': return 'Review comparison';
213
+ case 'model-compare-handoff-diff': return 'Diff reviewer handoffs';
214
+ case 'model-compare-judge': return 'Judge comparison';
215
+ case 'model-compare-apply': return 'Apply comparison winner';
216
+ case 'model-compare-route-decision': return 'Record route decision';
217
+ case 'model-compare-export': return 'Export comparison';
218
+ case 'model-compare-analytics': return 'View comparison analytics';
219
+ case 'local-model-benchmark': return 'Run local benchmark';
220
+ case 'auth-show': return 'Show auth status';
221
+ case 'auth-repair': return 'Repair auth';
222
+ case 'auth-bundle-export': return 'Export auth bundle';
223
+ case 'auth-bundle-inspect': return 'Inspect auth bundle';
224
+ case 'trust-bundle-export': return 'Export trust bundle';
225
+ case 'trust-bundle-inspect': return 'Inspect trust bundle';
226
+ case 'support-bundle-export': return 'Export support bundle';
227
+ case 'support-bundle-inspect': return 'Inspect support bundle';
228
+ case 'support-bundle-import': return 'Import support bundle';
229
+ case 'subscription-bundle-export': return 'Export subscription bundle';
230
+ case 'subscription-bundle-inspect': return 'Inspect subscription bundle';
231
+ case 'voice-enable': return 'Enable voice';
232
+ case 'voice-disable': return 'Disable voice';
233
+ case 'voice-bundle-export': return 'Export voice bundle';
234
+ case 'voice-bundle-inspect': return 'Inspect voice bundle';
235
+ case 'tts-prompt': return 'Test TTS prompt';
236
+ case 'image-input': return 'Attach image';
237
+ case 'artifact-browser': return 'Browse artifacts';
238
+ case 'artifact-show': return 'Show artifact';
239
+ case 'artifact-export-file': return 'Export artifact to file';
240
+ case 'artifact-export-package': return 'Export artifact package';
241
+ case 'artifact-promote-knowledge': return 'Promote artifact to knowledge';
242
+ case 'media-generate': return 'Generate media';
243
+ case 'conversation-export': return 'Export conversation';
244
+ case 'conversation-events': return 'Show conversation events';
245
+ case 'conversation-groups': return 'Show conversation groups';
246
+ case 'conversation-find': return 'Find conversation';
247
+ case 'effort-level': return 'Set effort level';
248
+ case 'channel-show': return 'Show channel';
249
+ case 'channel-doctor': return 'Check channel health';
250
+ case 'channel-setup': return 'Set up channel';
251
+ case 'channel-send': return 'Send to channel';
252
+ case 'session-save': return 'Save session';
253
+ case 'session-load': return 'Load session';
254
+ case 'session-rename': return 'Rename session';
255
+ case 'session-resume': return 'Resume session';
256
+ case 'session-info': return 'Inspect session';
257
+ case 'session-export-saved': return 'Export saved session';
258
+ case 'session-search': return 'Search sessions';
259
+ case 'session-delete': return 'Delete session';
260
+ case 'session-fork': return 'Fork session';
261
+ case 'session-graph': return 'Inspect session graph';
262
+ case 'task-list-filter': return 'Filter task list';
263
+ case 'task-show': return 'Show task';
264
+ case 'task-output': return 'View task output';
265
+ case 'plan-seed': return 'Seed plan';
266
+ case 'plan-show': return 'Show plan';
267
+ case 'plan-approve': return 'Approve plan';
268
+ case 'plan-override': return 'Override plan';
269
+ case 'plan-clear': return 'Clear plan';
270
+ case 'health-repair': return 'Repair health issue';
271
+ case 'approval-review': return 'Review approval';
272
+ case 'approval-approve': return 'Approve request';
273
+ case 'approval-deny': return 'Deny request';
274
+ case 'approval-cancel': return 'Cancel approval';
275
+ case 'automation-job-run': return 'Run automation job';
276
+ case 'automation-job-pause': return 'Pause automation job';
277
+ case 'automation-job-resume': return 'Resume automation job';
278
+ case 'automation-run-cancel': return 'Cancel automation run';
279
+ case 'automation-run-retry': return 'Retry automation run';
280
+ case 'schedule-run': return 'Run schedule';
281
+ case 'schedule-edit': return 'Edit schedule';
282
+ case 'routine-receipt': return 'View routine receipt';
283
+ case 'schedule-receipt': return 'View schedule receipt';
284
+ case 'mode-preset': return 'Set mode preset';
285
+ case 'mode-domain': return 'Set mode domain';
286
+ case 'setting-set': return 'Set setting';
287
+ case 'model-pin': return 'Pin model';
288
+ case 'model-unpin': return 'Unpin model';
289
+ case 'workplan-add': return 'Add work item';
290
+ case 'workplan-show': return 'Show work plan';
291
+ case 'workplan-status': return 'Check work plan status';
292
+ case 'workplan-delete': return 'Delete work item';
293
+ case 'workplan-clear-completed': return 'Clear completed items';
294
+ case 'routine-schedule': return 'Schedule routine';
295
+ case 'reminder-schedule': return 'Schedule reminder';
296
+ case 'skill-bundle': return 'Edit skill bundle';
297
+ case 'skill-bundle-search': return 'Search skill bundles';
298
+ case 'skill-bundle-show': return 'Show skill bundle';
299
+ case 'skill-bundle-update': return 'Update skill bundle';
300
+ case 'skill-bundle-enable': return 'Enable skill bundle';
301
+ case 'skill-bundle-disable': return 'Disable skill bundle';
302
+ case 'skill-bundle-review': return 'Review skill bundle';
303
+ case 'skill-bundle-stale': return 'Mark bundle stale';
304
+ case 'skill-bundle-delete': return 'Delete skill bundle';
305
+ case 'learned-behavior': return 'Review learned behavior';
306
+ case 'web-research': return 'Run web research';
307
+ case 'web-fetch': return 'Fetch from web';
308
+ case 'research-run': return 'Run research';
309
+ case 'research-source': return 'Manage research source';
310
+ case 'research-report': return 'Show research report';
311
+ case 'notify-webhook': return 'Add notification webhook';
312
+ case 'notify-webhook-remove': return 'Remove notification webhook';
313
+ case 'notify-webhook-clear': return 'Clear notification webhooks';
314
+ case 'notify-webhook-test': return 'Test notification webhook';
315
+ case 'notify-send': return 'Send notification';
316
+ default: return `Edit ${editorKind}`;
317
+ }
318
+ }
319
+
320
+ function localOperationLabel(localOperation: string): string {
321
+ switch (localOperation) {
322
+ case 'routine-start': return 'Start routine';
323
+ case 'persona-activate':
324
+ case 'persona-use': return 'Activate persona';
325
+ case 'persona-clear': return 'Deactivate persona';
326
+ case 'persona-edit': return 'Edit persona';
327
+ case 'persona-review': return 'Review persona';
328
+ case 'persona-delete': return 'Delete persona';
329
+ case 'memory-edit': return 'Edit memory';
330
+ case 'memory-review': return 'Review memory';
331
+ case 'memory-stale': return 'Mark memory stale';
332
+ case 'memory-delete': return 'Delete memory';
333
+ case 'note-edit': return 'Edit note';
334
+ case 'note-review': return 'Review note';
335
+ case 'note-stale': return 'Mark note stale';
336
+ case 'note-delete': return 'Delete note';
337
+ case 'note-promote-memory': return 'Promote note to memory';
338
+ case 'note-promote-persona': return 'Promote note to persona';
339
+ case 'note-promote-skill': return 'Promote note to skill';
340
+ case 'note-promote-routine': return 'Promote note to routine';
341
+ case 'note-promote-knowledge-url': return 'Promote note to knowledge';
342
+ case 'skill-edit': return 'Edit skill';
343
+ case 'skill-enable': return 'Enable skill';
344
+ case 'skill-disable': return 'Disable skill';
345
+ case 'skill-review': return 'Review skill';
346
+ case 'skill-delete': return 'Delete skill';
347
+ case 'routine-edit': return 'Edit routine';
348
+ case 'routine-enable': return 'Enable routine';
349
+ case 'routine-disable': return 'Disable routine';
350
+ case 'routine-review': return 'Review routine';
351
+ case 'routine-delete': return 'Delete routine';
352
+ default: return 'Apply local action';
78
353
  }
79
- if (action.kind === 'settings-import') return 'Import compatible GoodVibes preferences';
354
+ }
355
+
356
+ function onboardingActionLabel(workspace: AgentWorkspace, action: AgentWorkspaceAction): string {
357
+ if (action.kind === 'settings-import') return 'Import GoodVibes preferences';
80
358
  if (action.kind === 'setup-checkpoint') {
81
- if (action.setupCheckpointOperation === 'show') return 'Show saved setup progress';
82
- if (action.setupCheckpointOperation === 'mark-current') return 'Save current setup progress';
83
- if (action.setupCheckpointOperation === 'clear') return 'Clear saved setup progress';
359
+ if (action.setupCheckpointOperation === 'show') return 'Review saved progress';
360
+ if (action.setupCheckpointOperation === 'mark-current') return 'Save progress';
361
+ if (action.setupCheckpointOperation === 'clear') return 'Clear saved progress';
84
362
  return 'Review setup progress';
85
363
  }
86
- if (action.kind === 'model-picker') return action.modelPickerFlow === 'model' ? 'Choose a model' : 'Choose provider and model';
364
+ if (action.kind === 'model-picker') return action.modelPickerFlow === 'model' ? 'Choose model' : 'Choose provider and model';
87
365
  if (action.kind === 'settings-modal') return 'Open settings';
88
- if (action.kind === 'workspace') return 'Open setup area';
89
- if (action.kind === 'editor') return 'Open guided form';
366
+ if (action.kind === 'workspace') {
367
+ if (action.targetCategoryId) {
368
+ const target = workspace.categories.find((c) => c.id === action.targetCategoryId);
369
+ const label = target ? target.label : action.targetCategoryId;
370
+ return `Switch to ${label}`;
371
+ }
372
+ return 'Switch to setup area';
373
+ }
374
+ if (action.kind === 'editor') return action.editorKind ? editorPurposeLabel(action.editorKind) : 'Open form';
90
375
  if (action.kind === 'local-selection') return 'Move selection';
91
- if (action.kind === 'local-operation') return 'Apply selected library action';
92
- if (action.kind === 'onboarding-complete') return 'Save setup completion';
93
- if (action.safety === 'read-only') return 'Review readiness';
376
+ if (action.kind === 'local-operation') return action.localOperation ? localOperationLabel(action.localOperation) : 'Apply local action';
377
+ if (action.kind === 'onboarding-complete') return 'Finish setup';
378
+ if (action.kind === 'command') {
379
+ const cmd = action.command ?? '';
380
+ return action.commandBehavior === 'inline' ? `Run: ${cmd}` : `Open: ${cmd}`;
381
+ }
382
+ if (action.safety === 'read-only') return 'Review';
94
383
  if (action.safety === 'blocked') return 'Unavailable in this setup step';
95
- return 'Open option';
384
+ return 'Open';
385
+ }
386
+
387
+ function onboardingActionColumns(workspace: AgentWorkspace, action: AgentWorkspaceAction): OnboardingActionColumns {
388
+ if (action.kind === 'setting') {
389
+ return workspace.settingActionDisplay(action) ?? {
390
+ setting: action.label,
391
+ defaultValue: '(unknown)',
392
+ currentValue: '(unknown)',
393
+ };
394
+ }
395
+ // Non-setting rows keep the 3-column layout but use placeholders for Default and Current,
396
+ // since those concepts only make sense for actual settings. The action label carries
397
+ // the meaning of the row.
398
+ return {
399
+ setting: action.label,
400
+ defaultValue: '—',
401
+ currentValue: '—',
402
+ };
96
403
  }
97
404
 
98
405
  function actionChange(workspace: AgentWorkspace, category: AgentWorkspaceCategory, action: AgentWorkspaceAction): string {
99
- return isOnboardingCategory(category) ? onboardingActionChange(workspace, action) : actionCommand(action);
406
+ return isOnboardingCategory(category) ? onboardingActionColumns(workspace, action).setting : actionCommand(action);
100
407
  }
101
408
 
102
409
  function actionMetaLine(workspace: AgentWorkspace, category: AgentWorkspaceCategory, action: AgentWorkspaceAction): ContextLine {
103
410
  const onboarding = isOnboardingCategory(category);
411
+ if (onboarding) {
412
+ return {
413
+ text: `About: ${compactText(action.detail, 100)}`,
414
+ fg: action.safety === 'blocked' ? PALETTE.warn : PALETTE.muted,
415
+ };
416
+ }
104
417
  return {
105
- text: `${onboarding ? 'Change' : 'Does'}: ${actionChange(workspace, category, action)}`,
106
- fg: action.safety === 'blocked' ? PALETTE.warn : !onboarding && action.kind === 'command' ? PALETTE.info : PALETTE.muted,
418
+ text: `Does: ${actionChange(workspace, category, action)}`,
419
+ fg: action.safety === 'blocked' ? PALETTE.warn : action.kind === 'command' ? PALETTE.info : PALETTE.muted,
107
420
  };
108
421
  }
109
422
 
423
+ function shouldRenderOnboardingSettingsTable(actions: readonly AgentWorkspaceAction[]): boolean {
424
+ // Always use the Setting/Default/Current 3-column layout on ONBOARDING pages so the
425
+ // user gets a consistent visual structure across every category, even ones that mix
426
+ // settings with editors, guidance, or pickers. Non-setting rows fill Default/Current
427
+ // with placeholders via onboardingActionColumns().
428
+ const nonFinish = actions.filter((a) => a.kind !== 'onboarding-complete');
429
+ return nonFinish.length > 0;
430
+ }
431
+
110
432
  function editorContextLines(editor: AgentWorkspaceLocalEditor, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
111
433
  const selected = editor.fields[editor.selectedFieldIndex];
112
434
  const lines: ContextLine[] = [
@@ -244,12 +566,25 @@ function buildEditorFieldRows(editor: AgentWorkspaceLocalEditor, index: number,
244
566
  return rows;
245
567
  }
246
568
 
569
+ function onboardingFinishPrerequisitesMet(workspace: AgentWorkspace): boolean {
570
+ const checklist = workspace.runtimeSnapshot?.setupChecklist ?? [];
571
+ return ONBOARDING_CRITICAL_STEP_IDS.every((id) => checklist.find((item) => item.id === id)?.status === 'ready');
572
+ }
573
+
247
574
  function buildActionRows(workspace: AgentWorkspace, width: number, height: number): WorkspaceRow[] {
248
575
  if (workspace.localEditor) return buildEditorRows(workspace.localEditor, width, height);
249
576
  const category = workspace.selectedActionCategory;
250
577
  const onboarding = isOnboardingCategory(category);
578
+ const settingTable = onboarding
579
+ && !workspace.actionSearchActive
580
+ && shouldRenderOnboardingSettingsTable(workspace.actions);
251
581
  const rows: WorkspaceRow[] = [];
252
- const labelWidth = Math.min(34, Math.max(18, Math.floor(width * 0.38)));
582
+ const valueWidth = settingTable
583
+ ? Math.min(22, Math.max(10, Math.floor(width * 0.18)))
584
+ : 0;
585
+ const labelWidth = settingTable
586
+ ? Math.max(18, width - (valueWidth * 2) - 6)
587
+ : Math.min(34, Math.max(18, Math.floor(width * 0.38)));
253
588
  const commandWidth = Math.max(10, width - labelWidth - 6);
254
589
  if (workspace.actionSearchActive) {
255
590
  rows.push({
@@ -259,32 +594,80 @@ function buildActionRows(workspace: AgentWorkspace, width: number, height: numbe
259
594
  });
260
595
  }
261
596
  rows.push({
262
- text: ` ${padDisplay(workspace.actionSearchActive ? 'Result' : onboarding ? 'Option' : 'Action', labelWidth)} ${padDisplay(onboarding ? 'Change' : 'Does', commandWidth)}`,
597
+ text: settingTable
598
+ ? ` ${padDisplay(workspace.actionSearchActive ? 'Result' : 'Setting', labelWidth)} ${padDisplay('Default', valueWidth)} ${padDisplay('Current', valueWidth)}`
599
+ : ` ${padDisplay(workspace.actionSearchActive ? 'Result' : onboarding ? 'Option' : 'Action', labelWidth)} ${padDisplay('Does', commandWidth)}`,
263
600
  fg: PALETTE.muted,
264
601
  bold: true,
265
602
  });
266
603
 
267
- const actions = workspace.actions;
604
+ // The finish footer row is appended to workspace.actions by the input layer
605
+ // (shouldShowOnboardingFinishFooter). Detect it here by action id for special styling.
606
+ const allActions = workspace.actions;
607
+ const finishActionIndex = allActions.findIndex((a) => a.id === ONBOARDING_COMPLETE_SYNTHETIC_ACTION.id);
608
+
268
609
  const visible = Math.max(1, height - (workspace.actionSearchActive ? 3 : 2));
269
- const window = stableWindow(actions.length, workspace.selectedActionIndex, visible);
610
+ const window = stableWindow(allActions.length, workspace.selectedActionIndex, visible);
270
611
  if (window.start > 0) rows.push({ text: `${GLYPHS.navigation.moreAbove} ${window.start} more action(s) above`, kind: 'more', fg: PALETTE.dim, dim: true });
271
612
 
272
613
  for (let index = window.start; index < window.end; index += 1) {
273
- const action = actions[index]!;
614
+ const action = allActions[index]!;
615
+ const isFinishFooterRow = finishActionIndex >= 0 && index === finishActionIndex;
274
616
  const selected = index === workspace.selectedActionIndex;
275
617
  const searchResult = workspace.actionSearchActive ? workspace.actionSearchResults[index] : null;
276
618
  const actionCategory = searchResult?.category ?? category;
277
619
  const label = searchResult ? `${searchResult.category.label} / ${action.label}` : action.label;
278
620
  const marker = selected ? GLYPHS.navigation.selected : ' ';
279
- rows.push({
280
- text: `${marker} ${padDisplay(label, labelWidth)} ${padDisplay(actionChange(workspace, actionCategory, action), commandWidth)}`,
281
- selected: selected && workspace.focusPane === 'actions',
282
- fg: action.safety === 'blocked' ? PALETTE.warn : selected ? PALETTE.text : PALETTE.info,
283
- bold: selected,
284
- });
621
+
622
+ if (isFinishFooterRow) {
623
+ // Sticky Finish setup footer row
624
+ const prereqsMet = onboardingFinishPrerequisitesMet(workspace);
625
+ const sep = `${GLYPHS.frame.horizontal.repeat(3)} Finish setup ${GLYPHS.frame.horizontal.repeat(3)}`;
626
+ if (!selected) {
627
+ rows.push({
628
+ text: ` ${sep}`,
629
+ kind: 'item',
630
+ fg: PALETTE.dim,
631
+ dim: true,
632
+ });
633
+ }
634
+ const finishDetail = prereqsMet
635
+ ? 'Finish setup'
636
+ : (() => {
637
+ const checklist = workspace.runtimeSnapshot?.setupChecklist ?? [];
638
+ const unmet = [...ONBOARDING_CRITICAL_STEP_IDS]
639
+ .filter((id) => checklist.find((item) => item.id === id)?.status !== 'ready')
640
+ .map((id) => checklist.find((item) => item.id === id)?.label ?? id)
641
+ .join(', ');
642
+ return `Finish needs: ${unmet}`;
643
+ })();
644
+ const text = settingTable
645
+ ? `${marker} ${padDisplay('Finish setup', labelWidth)} ${padDisplay('', valueWidth)} ${padDisplay('', valueWidth)}`
646
+ : `${marker} ${padDisplay('Finish setup', labelWidth)} ${padDisplay(finishDetail, commandWidth)}`;
647
+ rows.push({
648
+ text,
649
+ selected: selected && workspace.focusPane === 'actions',
650
+ fg: prereqsMet ? PALETTE.good : PALETTE.warn,
651
+ bold: selected,
652
+ kind: 'item',
653
+ });
654
+ } else {
655
+ const text = settingTable
656
+ ? (() => {
657
+ const columns = onboardingActionColumns(workspace, action);
658
+ return `${marker} ${padDisplay(columns.setting, labelWidth)} ${padDisplay(columns.defaultValue, valueWidth)} ${padDisplay(columns.currentValue, valueWidth)}`;
659
+ })()
660
+ : `${marker} ${padDisplay(label, labelWidth)} ${padDisplay(actionChange(workspace, actionCategory, action), commandWidth)}`;
661
+ rows.push({
662
+ text,
663
+ selected: selected && workspace.focusPane === 'actions',
664
+ fg: action.safety === 'blocked' ? PALETTE.warn : selected ? PALETTE.text : PALETTE.info,
665
+ bold: selected,
666
+ });
667
+ }
285
668
  }
286
669
 
287
- if (window.end < actions.length) rows.push({ text: `${GLYPHS.navigation.moreBelow} ${actions.length - window.end} more action(s) below`, kind: 'more', fg: PALETTE.dim, dim: true });
670
+ if (window.end < allActions.length) rows.push({ text: `${GLYPHS.navigation.moreBelow} ${allActions.length - window.end} more action(s) below`, kind: 'more', fg: PALETTE.dim, dim: true });
288
671
  rows.push({ text: '' });
289
672
  rows.push({ text: `Status: ${workspace.status}`, fg: PALETTE.muted });
290
673
  if (workspace.lastActionResult) {
@@ -22,7 +22,6 @@ const FEATURED_HELP_COMMANDS: Array<[name: string, argHint: string, desc: string
22
22
  ['personas', '', 'Manage serial Agent operating personas'],
23
23
  ['skills', '', 'Manage Agent-local skills and bundles'],
24
24
  ['routines', '', 'Manage reusable main-conversation routines'],
25
- ['workplan', '', 'Inspect shared work-plan state'],
26
25
  ['approval', '', 'Review and explicitly act on approvals'],
27
26
  ['automation', '', 'Run confirmed connected-host automation actions'],
28
27
  ['schedule', 'remind', 'Create confirmed reminders or inspect schedules'],
@@ -202,7 +201,6 @@ export function renderHelpOverlay(
202
201
  'personas',
203
202
  'skills',
204
203
  'routines',
205
- 'workplan',
206
204
  'approval',
207
205
  'schedule',
208
206
  'delegate',