@pellux/goodvibes-agent 1.4.3 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +7 -7
  3. package/dist/package/main.js +7450 -12285
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +1 -1
  6. package/docs/getting-started.md +2 -2
  7. package/docs/release-and-publishing.md +1 -1
  8. package/docs/tools-and-commands.md +5 -5
  9. package/package.json +4 -2
  10. package/release/performance-snapshot.json +2 -2
  11. package/release/release-notes.md +11 -7
  12. package/release/release-readiness.json +6 -6
  13. package/src/agent/behavior-discovery-summary.ts +4 -18
  14. package/src/agent/calendar-registry.ts +322 -0
  15. package/src/agent/competitive-feature-inventory.ts +268 -130
  16. package/src/agent/document-registry.ts +5 -1
  17. package/src/agent/email/email-service.ts +350 -0
  18. package/src/agent/email/imap-client.ts +596 -0
  19. package/src/agent/email/smtp-client.ts +453 -0
  20. package/src/agent/ics-calendar.ts +662 -0
  21. package/src/agent/ics-timezone.ts +172 -0
  22. package/src/agent/markdown-frontmatter.ts +31 -0
  23. package/src/agent/memory-safety.ts +1 -1
  24. package/src/agent/note-registry.ts +3 -9
  25. package/src/agent/persona-discovery.ts +3 -15
  26. package/src/agent/persona-registry.ts +1 -10
  27. package/src/agent/research-source-registry.ts +5 -1
  28. package/src/agent/routine-discovery.ts +3 -15
  29. package/src/agent/runtime-profile.ts +3 -0
  30. package/src/agent/skill-discovery.ts +3 -15
  31. package/src/agent/skill-draft-proposer.ts +203 -0
  32. package/src/agent/skill-draft-runner.ts +201 -0
  33. package/src/agent/skill-registry.ts +36 -1
  34. package/src/agent/skill-standard.ts +99 -0
  35. package/src/agent/vibe-file.ts +7 -22
  36. package/src/cli/completion.ts +1 -1
  37. package/src/cli/config-overrides.ts +10 -1
  38. package/src/cli/redaction.ts +17 -5
  39. package/src/config/provider-model.ts +2 -1
  40. package/src/config/secret-config.ts +13 -6
  41. package/src/core/activity-feed.ts +97 -0
  42. package/src/core/away-digest.ts +161 -0
  43. package/src/core/conversation-rendering.ts +7 -3
  44. package/src/core/conversation.ts +22 -15
  45. package/src/core/hardware-profile.ts +362 -0
  46. package/src/core/last-seen-store.ts +78 -0
  47. package/src/core/plain-language.ts +52 -0
  48. package/src/core/setup-incomplete-hint.ts +90 -0
  49. package/src/core/system-message-router.ts +38 -87
  50. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  51. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  52. package/src/input/agent-workspace-categories.ts +63 -158
  53. package/src/input/agent-workspace-command-editor.ts +4 -0
  54. package/src/input/agent-workspace-host-category.ts +0 -5
  55. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  56. package/src/input/agent-workspace-navigation.ts +10 -3
  57. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  58. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  59. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  60. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  61. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  62. package/src/input/agent-workspace-profile-editors.ts +155 -0
  63. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  64. package/src/input/agent-workspace-types.ts +5 -1
  65. package/src/input/agent-workspace.ts +65 -39
  66. package/src/input/command-registry.ts +18 -5
  67. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  68. package/src/input/commands/agent-skills-runtime.ts +60 -3
  69. package/src/input/commands/calendar-runtime.ts +209 -0
  70. package/src/input/commands/channels-runtime.ts +1 -0
  71. package/src/input/commands/command-error.ts +9 -0
  72. package/src/input/commands/compat-runtime.ts +1 -0
  73. package/src/input/commands/config.ts +1 -0
  74. package/src/input/commands/conversation-runtime.ts +1 -1
  75. package/src/input/commands/delegation-runtime.ts +5 -6
  76. package/src/input/commands/email-runtime.ts +387 -0
  77. package/src/input/commands/experience-runtime.ts +17 -4
  78. package/src/input/commands/guidance-runtime.ts +1 -1
  79. package/src/input/commands/health-runtime.ts +6 -1
  80. package/src/input/commands/knowledge-format.ts +73 -0
  81. package/src/input/commands/knowledge.ts +9 -74
  82. package/src/input/commands/local-provider-runtime.ts +2 -1
  83. package/src/input/commands/local-runtime.ts +10 -4
  84. package/src/input/commands/mcp-runtime.ts +7 -0
  85. package/src/input/commands/notify-runtime.ts +17 -1
  86. package/src/input/commands/onboarding-runtime.ts +1 -0
  87. package/src/input/commands/operator-actions-runtime.ts +1 -0
  88. package/src/input/commands/operator-runtime.ts +3 -0
  89. package/src/input/commands/personas-runtime.ts +1 -0
  90. package/src/input/commands/platform-access-runtime.ts +40 -17
  91. package/src/input/commands/product-runtime.ts +6 -1
  92. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  93. package/src/input/commands/qrcode-runtime.ts +1 -0
  94. package/src/input/commands/routines-runtime.ts +1 -0
  95. package/src/input/commands/runtime-services.ts +0 -13
  96. package/src/input/commands/security-runtime.ts +1 -0
  97. package/src/input/commands/session-content.ts +1 -0
  98. package/src/input/commands/shell-core.ts +5 -2
  99. package/src/input/commands/subscription-runtime.ts +33 -9
  100. package/src/input/commands/support-bundle-runtime.ts +8 -1
  101. package/src/input/commands/tasks-runtime.ts +1 -0
  102. package/src/input/commands/tts-runtime.ts +1 -0
  103. package/src/input/commands/vibe-runtime.ts +1 -0
  104. package/src/input/commands.ts +4 -0
  105. package/src/input/feed-context-factory.ts +3 -12
  106. package/src/input/handler-command-route.ts +7 -60
  107. package/src/input/handler-feed-routes.ts +0 -194
  108. package/src/input/handler-feed.ts +2 -54
  109. package/src/input/handler-interactions.ts +0 -2
  110. package/src/input/handler-modal-stack.ts +0 -9
  111. package/src/input/handler-picker-routes.ts +24 -1
  112. package/src/input/handler-shortcuts.ts +11 -40
  113. package/src/input/handler-ui-state.ts +13 -0
  114. package/src/input/handler.ts +8 -35
  115. package/src/input/keybindings.ts +40 -17
  116. package/src/input/model-picker-local-fit.ts +130 -0
  117. package/src/input/submission-router.ts +0 -5
  118. package/src/main.ts +111 -89
  119. package/src/renderer/activity-sidebar.ts +186 -0
  120. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  121. package/src/renderer/agent-workspace-style.ts +1 -1
  122. package/src/renderer/agent-workspace.ts +14 -2
  123. package/src/renderer/compositor.ts +37 -125
  124. package/src/renderer/conversation-overlays.ts +3 -3
  125. package/src/renderer/help-overlay.ts +7 -5
  126. package/src/renderer/model-workspace.ts +101 -86
  127. package/src/{panels → renderer}/polish.ts +3 -3
  128. package/src/renderer/shell-surface.ts +4 -5
  129. package/src/renderer/status-token.ts +31 -11
  130. package/src/renderer/tab-strip.ts +1 -1
  131. package/src/renderer/tool-call.ts +7 -8
  132. package/src/renderer/tool-labels.ts +92 -0
  133. package/src/renderer/ui-factory.ts +48 -96
  134. package/src/runtime/bootstrap-command-context.ts +0 -5
  135. package/src/runtime/bootstrap-command-parts.ts +6 -15
  136. package/src/runtime/bootstrap-core.ts +34 -13
  137. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  138. package/src/runtime/bootstrap-shell.ts +3 -50
  139. package/src/runtime/bootstrap.ts +3 -4
  140. package/src/runtime/context.ts +1 -1
  141. package/src/runtime/diagnostics/panels/index.ts +0 -1
  142. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  143. package/src/runtime/execution-ledger.ts +16 -1
  144. package/src/runtime/index.ts +6 -1
  145. package/src/runtime/onboarding/index.ts +1 -0
  146. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  147. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  148. package/src/runtime/services.ts +16 -4
  149. package/src/runtime/terminal-output-guard.ts +7 -0
  150. package/src/runtime/tool-permission-safety.ts +1 -1
  151. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  152. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  153. package/src/runtime/ui/model-picker/types.ts +2 -2
  154. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  155. package/src/runtime/ui/provider-health/types.ts +2 -2
  156. package/src/runtime/ui-services.ts +0 -2
  157. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  158. package/src/shell/autonomy-surfacing.ts +272 -0
  159. package/src/shell/session-continuity-hints.ts +0 -6
  160. package/src/shell/startup-wiring.ts +221 -0
  161. package/src/shell/ui-openers.ts +18 -29
  162. package/src/tools/agent-context-policy.ts +1 -1
  163. package/src/tools/agent-harness-background-processes.ts +4 -4
  164. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  165. package/src/tools/agent-harness-command-runner.ts +6 -1
  166. package/src/tools/agent-harness-document-ops.ts +26 -53
  167. package/src/tools/agent-harness-execution-history.ts +1 -13
  168. package/src/tools/agent-harness-execution-posture.ts +0 -15
  169. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  170. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  171. package/src/tools/agent-harness-metadata.ts +16 -0
  172. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  173. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  174. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  175. package/src/tools/agent-harness-tool-schema.ts +13 -14
  176. package/src/tools/agent-harness-tool.ts +27 -23
  177. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  178. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  179. package/src/tools/agent-workspace-tool.ts +2 -33
  180. package/src/utils/terminal-width.ts +9 -3
  181. package/src/version.ts +1 -1
  182. package/src/input/agent-workspace-panel-route.ts +0 -44
  183. package/src/input/panel-integration-actions.ts +0 -26
  184. package/src/panels/approval-panel.ts +0 -149
  185. package/src/panels/automation-control-panel.ts +0 -212
  186. package/src/panels/base-panel.ts +0 -254
  187. package/src/panels/builtin/agent.ts +0 -58
  188. package/src/panels/builtin/knowledge.ts +0 -26
  189. package/src/panels/builtin/operations.ts +0 -121
  190. package/src/panels/builtin/session.ts +0 -138
  191. package/src/panels/builtin/shared.ts +0 -275
  192. package/src/panels/builtin/usage.ts +0 -21
  193. package/src/panels/builtin-panels.ts +0 -23
  194. package/src/panels/confirm-state.ts +0 -61
  195. package/src/panels/context-visualizer-panel.ts +0 -204
  196. package/src/panels/cost-tracker-panel.ts +0 -444
  197. package/src/panels/docs-panel.ts +0 -285
  198. package/src/panels/index.ts +0 -25
  199. package/src/panels/knowledge-panel.ts +0 -417
  200. package/src/panels/memory-panel.ts +0 -226
  201. package/src/panels/panel-list-panel.ts +0 -464
  202. package/src/panels/panel-manager.ts +0 -570
  203. package/src/panels/provider-accounts-panel.ts +0 -233
  204. package/src/panels/provider-health-domains.ts +0 -208
  205. package/src/panels/provider-health-panel.ts +0 -720
  206. package/src/panels/provider-health-tracker.ts +0 -115
  207. package/src/panels/provider-stats-panel.ts +0 -366
  208. package/src/panels/qr-panel.ts +0 -207
  209. package/src/panels/schedule-panel.ts +0 -321
  210. package/src/panels/scrollable-list-panel.ts +0 -491
  211. package/src/panels/search-focus.ts +0 -32
  212. package/src/panels/security-panel.ts +0 -295
  213. package/src/panels/session-browser-panel.ts +0 -395
  214. package/src/panels/session-maintenance.ts +0 -125
  215. package/src/panels/subscription-panel.ts +0 -263
  216. package/src/panels/system-messages-panel.ts +0 -230
  217. package/src/panels/tasks-panel.ts +0 -344
  218. package/src/panels/thinking-panel.ts +0 -304
  219. package/src/panels/token-budget-panel.ts +0 -475
  220. package/src/panels/tool-inspector-panel.ts +0 -436
  221. package/src/panels/types.ts +0 -54
  222. package/src/renderer/panel-composite.ts +0 -158
  223. package/src/renderer/panel-tab-bar.ts +0 -69
  224. package/src/renderer/panel-workspace-bar.ts +0 -42
  225. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  226. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  227. /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
@@ -4,7 +4,6 @@ import type { ConversationManager } from '../core/conversation';
4
4
  import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
5
5
  import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
6
6
  import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
7
- import type { PanelManager } from '../panels/panel-manager.ts';
8
7
  import type { ProviderApi } from '@pellux/goodvibes-sdk/platform/providers';
9
8
  import type { OpsApi } from '@/runtime/index.ts';
10
9
  import type { MutableRuntimeState } from '@/runtime/index.ts';
@@ -116,7 +115,6 @@ export type CreateBootstrapCommandContextOptions = {
116
115
  mcpApi?: McpApi;
117
116
  opsApi?: OpsApi;
118
117
  directTransport?: DirectTransport;
119
- panelManager: PanelManager;
120
118
  worktreeRegistry: WorktreeRegistry;
121
119
  sandboxSessionRegistry: SandboxSessionRegistry;
122
120
  loadSystemPrompt: () => string;
@@ -192,7 +190,6 @@ export function createBootstrapCommandContext(
192
190
  mcpApi,
193
191
  opsApi,
194
192
  directTransport,
195
- panelManager,
196
193
  worktreeRegistry,
197
194
  sandboxSessionRegistry,
198
195
  loadSystemPrompt,
@@ -247,7 +244,6 @@ export function createBootstrapCommandContext(
247
244
  const workspace = createBootstrapCommandWorkspaceSection({
248
245
  keybindingsManager,
249
246
  fileUndoManager,
250
- panelManager,
251
247
  processManager,
252
248
  profileManager,
253
249
  bookmarkManager,
@@ -277,7 +273,6 @@ export function createBootstrapCommandContext(
277
273
  conversation,
278
274
  runtime,
279
275
  requestRender,
280
- panelManager,
281
276
  loadSystemPrompt,
282
277
  activatePlan,
283
278
  requestPermission,
@@ -6,7 +6,6 @@ import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
6
6
  import type { AgentPromptContextReceiptStore } from '../agent/prompt-context-receipts.ts';
7
7
  import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
8
8
  import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
9
- import type { PanelManager } from '../panels/panel-manager.ts';
10
9
  import type { ProviderApi } from '@pellux/goodvibes-sdk/platform/providers';
11
10
  import type { OpsApi } from '@/runtime/index.ts';
12
11
  import type { MutableRuntimeState } from '@/runtime/index.ts';
@@ -62,7 +61,6 @@ export interface BootstrapCommandActionOptions {
62
61
  readonly conversation: ConversationManager;
63
62
  readonly runtime: MutableRuntimeState;
64
63
  readonly requestRender: () => void;
65
- readonly panelManager: PanelManager;
66
64
  readonly loadSystemPrompt: () => string;
67
65
  readonly activatePlan: (planId: string, task: string) => void;
68
66
  readonly requestPermission: PermissionRequestHandler;
@@ -76,7 +74,6 @@ export interface BootstrapCommandSectionOptions {
76
74
  readonly runtime: MutableRuntimeState;
77
75
  readonly keybindingsManager?: KeybindingsManager;
78
76
  readonly processManager?: import('@pellux/goodvibes-sdk/platform/tools').ProcessManager;
79
- readonly panelManager: PanelManager;
80
77
  readonly requestRender: () => void;
81
78
  readonly requestPermission: PermissionRequestHandler;
82
79
  readonly toolRegistry: ToolRegistry;
@@ -158,7 +155,6 @@ export function createBootstrapCommandActions(
158
155
  | 'print'
159
156
  | 'exit'
160
157
  | 'reloadSystemPrompt'
161
- | 'showPanel'
162
158
  | 'openMcpWorkspace'
163
159
  | 'openAgentWorkspace'
164
160
  | 'dismissAgentWorkspace'
@@ -172,17 +168,14 @@ export function createBootstrapCommandActions(
172
168
  conversation,
173
169
  runtime,
174
170
  requestRender,
175
- panelManager,
176
171
  loadSystemPrompt,
177
172
  activatePlan,
178
173
  requestPermission,
179
174
  completeModelSelectionSideEffect,
180
175
  } = options;
181
176
 
182
- const showPanel = (panelId: string, pane?: 'top' | 'bottom') => {
183
- void pane;
184
- panelManager.hide();
185
- conversation.log(`Panel route "${panelId}" is handled through Agent Workspace. Use /agent for current operator controls.`, { fg: '214' });
177
+ const pointToWorkspace = (what: string) => {
178
+ conversation.log(`${what} lives in the Agent workspace — press Ctrl+P or run /agent.`, { fg: '214' });
186
179
  requestRender();
187
180
  };
188
181
 
@@ -246,18 +239,17 @@ export function createBootstrapCommandActions(
246
239
  },
247
240
  exit: () => unwiredShellAction('exit'),
248
241
  reloadSystemPrompt: loadSystemPrompt,
249
- showPanel,
250
242
  openMcpWorkspace: () => unwiredShellAction('openMcpWorkspace'),
251
243
  openAgentWorkspace: () => unwiredShellAction('openAgentWorkspace'),
252
244
  dismissAgentWorkspace: () => unwiredShellAction('dismissAgentWorkspace'),
253
245
  openSecurityPanel: () => {
254
- showPanel('security');
246
+ pointToWorkspace('Security review');
255
247
  },
256
248
  openKnowledgePanel: () => {
257
- showPanel('knowledge');
249
+ pointToWorkspace('Knowledge');
258
250
  },
259
251
  openSubscriptionPanel: () => {
260
- showPanel('subscription');
252
+ pointToWorkspace('Provider subscriptions');
261
253
  },
262
254
  };
263
255
  }
@@ -295,7 +287,7 @@ export function createBootstrapCommandProviderSection(
295
287
  export function createBootstrapCommandWorkspaceSection(
296
288
  options: Pick<
297
289
  BootstrapCommandSectionOptions,
298
- 'keybindingsManager' | 'fileUndoManager' | 'panelManager' | 'profileManager' | 'bookmarkManager'
290
+ 'keybindingsManager' | 'fileUndoManager' | 'profileManager' | 'bookmarkManager'
299
291
  | 'processManager' | 'projectPlanningService' | 'projectPlanningProjectId' | 'workPlanStore'
300
292
  >,
301
293
  shellServices: BootstrapCommandShellServices,
@@ -303,7 +295,6 @@ export function createBootstrapCommandWorkspaceSection(
303
295
  return {
304
296
  keybindingsManager: options.keybindingsManager,
305
297
  fileUndoManager: options.fileUndoManager,
306
- panelManager: options.panelManager,
307
298
  processManager: options.processManager,
308
299
  profileManager: options.profileManager,
309
300
  bookmarkManager: options.bookmarkManager,
@@ -6,8 +6,8 @@ import { getProviderIdFromModel } from '../config/provider-model.ts';
6
6
  import { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
7
7
  import { registerAllTools } from '@pellux/goodvibes-sdk/platform/tools';
8
8
  import { PermissionManager, createPermissionConfigReader } from '@pellux/goodvibes-sdk/platform/permissions';
9
- import { Notifier } from '@pellux/goodvibes-sdk/platform/integrations';
10
- import { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
9
+ import { Notifier, WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
10
+
11
11
  import { Compositor } from '../renderer/compositor.ts';
12
12
  import type { PermissionRequestHandler } from '@pellux/goodvibes-sdk/platform/permissions';
13
13
  import type { SystemMessageRouter } from '../core/system-message-router.ts';
@@ -94,6 +94,26 @@ export interface BootstrapCoreState {
94
94
 
95
95
  export type CompanionMessagePayload = Extract<SessionEvent, { type: 'COMPANION_MESSAGE_RECEIVED' }>;
96
96
 
97
+ /**
98
+ * Registers the webhook notifier for the runtime session.
99
+ *
100
+ * Configures the provided WebhookNotifier with the given URL list, attaches it
101
+ * to the runtime bus so it receives SESSION_NOTIFICATION events, and pushes a
102
+ * detach() cleanup into runtimeUnsubs for shutdown. When webhookUrls is empty
103
+ * this function is a complete no-op.
104
+ */
105
+ export function registerWebhookNotifier(
106
+ webhookNotifier: WebhookNotifier,
107
+ webhookUrls: string[],
108
+ runtimeBus: RuntimeEventBus,
109
+ runtimeUnsubs: Array<() => void>,
110
+ ): void {
111
+ if (webhookUrls.length === 0) return;
112
+ webhookNotifier.setUrls(webhookUrls);
113
+ webhookNotifier.attachToRuntimeBus(runtimeBus);
114
+ runtimeUnsubs.push(() => webhookNotifier.detach());
115
+ }
116
+
97
117
  export function companionMessageToOrchestratorInputOptions(
98
118
  payload: CompanionMessagePayload,
99
119
  ): OrchestratorUserInputOptions {
@@ -161,7 +181,7 @@ export async function initializeBootstrapCore(
161
181
  sessionId: userSessionId,
162
182
  authenticatedAt: Date.now(),
163
183
  lastSeenAt: Date.now(),
164
- capabilities: ['session', 'panels', 'commands', 'automation'],
184
+ capabilities: ['session', 'commands', 'automation'],
165
185
  metadata: {
166
186
  product: 'goodvibes-agent',
167
187
  surfaceRoot: GOODVIBES_AGENT_SURFACE_ROOT,
@@ -176,7 +196,6 @@ export async function initializeBootstrapCore(
176
196
  hookDispatcher,
177
197
  hookWorkbench,
178
198
  memoryStore,
179
- panelManager,
180
199
  routeBindings,
181
200
  sessionBroker: sharedSessionBroker,
182
201
  surfaceRegistry,
@@ -215,13 +234,7 @@ export async function initializeBootstrapCore(
215
234
  getControlPlaneRecentEvents,
216
235
  });
217
236
 
218
- const conversation = new ConversationManager(() => {
219
- const width = getTerminalSize(stdout).width;
220
- if (panelManager.isVisible() && panelManager.getAllOpen().length > 0) {
221
- return Math.max(1, panelManager.getLeftWidth(width) - 1);
222
- }
223
- return width;
224
- });
237
+ const conversation = new ConversationManager(() => getTerminalSize(stdout).width);
225
238
  conversation.setConfigManager(configManager);
226
239
  getConversationTitle = () => conversation.title;
227
240
 
@@ -321,6 +334,11 @@ export async function initializeBootstrapCore(
321
334
  });
322
335
 
323
336
  const bootstrapUnsubs: Array<() => void> = [];
337
+ // D5 fix: stop the heartbeat watcher on shutdown so the setInterval is cleared.
338
+ // Registered here (after declaration) rather than inside the watcher block above.
339
+ if (configManager.get('watchers.enabled')) {
340
+ bootstrapUnsubs.push(() => watcherRegistry.stopWatcher('runtime-heartbeat'));
341
+ }
324
342
  await memoryStore.init();
325
343
  bootstrapUnsubs.push(() => {
326
344
  void memoryStore.save();
@@ -420,8 +438,11 @@ export async function initializeBootstrapCore(
420
438
 
421
439
  const webhookUrls = (configManager.getCategory('notifications') as { webhookUrls?: string[] }).webhookUrls ?? [];
422
440
  if (webhookUrls.length > 0) {
423
- const webhookNotifier = WebhookNotifier.fromConfig(webhookUrls);
424
- webhookNotifier.attachToRuntimeBus(runtimeBus);
441
+ // Reuse the services.webhookNotifier instance (constructed no-arg in services.ts).
442
+ // Configure it with the resolved URL list via setUrls(), attach it to the runtime
443
+ // bus so it receives SESSION_NOTIFICATION events, and register detach() in
444
+ // runtimeUnsubs so the bus subscription is cleaned up on shutdown.
445
+ registerWebhookNotifier(services.webhookNotifier, webhookUrls, runtimeBus, runtimeUnsubs);
425
446
  domainDispatch.syncIntegration({
426
447
  id: 'webhooks',
427
448
  displayName: 'Webhooks',
@@ -52,9 +52,11 @@ export function createResumeSessionHandler(options: ResumeSessionOptions): (sess
52
52
  options.writeLastSessionPointer(sessionId);
53
53
  void options.sharedSessionBroker.reopenSession(sessionId).catch((err) => { logger.debug('session broker reopen session failed', { err }); });
54
54
  options.conversation.log(`Resumed session: ${sessionId}`, { fg: '135' });
55
- const ignoredPanels = meta.returnContext?.openPanels?.slice(0, 4) ?? [];
56
55
  const returnContextMode = getReturnContextMode(options.configManager);
57
56
  if (returnContextMode !== 'off' && meta.returnContext) {
57
+ // N1 fix: compute ignoredPanels inside the guard so it is only evaluated
58
+ // when returnContext is present and the mode is not 'off'.
59
+ const ignoredPanels = meta.returnContext.openPanels?.slice(0, 4) ?? [];
58
60
  for (const line of formatReturnContextForDisplay(meta.returnContext)) {
59
61
  if (line.startsWith('Open panels:')) continue;
60
62
  options.conversation.log(`Resume: ${line}`, { fg: '244' });
@@ -13,8 +13,7 @@ import { CommandRegistry } from '../input/command-registry.ts';
13
13
  import { registerBuiltinCommands } from '../input/commands.ts';
14
14
  import { InputHistory } from '../input/input-history.ts';
15
15
  import type { PermissionRequestHandler } from '@pellux/goodvibes-sdk/platform/permissions';
16
- import { registerBuiltinPanels } from '../panels/builtin-panels.ts';
17
- import { SystemMessagesPanel } from '../panels/system-messages-panel.ts';
16
+ import { ActivityFeed } from '../core/activity-feed.ts';
18
17
  import { createSystemMessageRouter, type SystemMessageRouter } from '../core/system-message-router.ts';
19
18
  import { getConfigSnapshot } from '../config/index.ts';
20
19
  import { createBootstrapCommandContext } from './bootstrap-command-context.ts';
@@ -24,7 +23,6 @@ import { loadBootstrapSystemPrompt } from '@/runtime/index.ts';
24
23
  import { createShellPlanRuntime, createShellRemoteCommandService } from '@/runtime/index.ts';
25
24
  import { createRuntimeFoundationClients } from '@/runtime/index.ts';
26
25
  import type { ControlPlaneRecentEvent } from '@pellux/goodvibes-sdk/platform/control-plane';
27
- import type { BuiltinPanelDeps } from '../panels/builtin/shared.ts';
28
26
  import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
29
27
  import type { ForensicsRegistry } from '@/runtime/index.ts';
30
28
  import type { PolicyRuntimeState } from '@/runtime/index.ts';
@@ -125,7 +123,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
125
123
  completeModelSelectionSideEffect,
126
124
  } = options;
127
125
 
128
- const systemMessagesPanel = new SystemMessagesPanel(configManager, services.componentHealthMonitor);
126
+ const activityFeed = new ActivityFeed();
129
127
  const resumeSession = createResumeSessionHandler({
130
128
  runtimeBus,
131
129
  runtime,
@@ -141,54 +139,10 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
141
139
  });
142
140
 
143
141
  let commandContextRef: CommandContext | null = null;
144
- registerBuiltinPanels(services.panelManager, {
145
- configManager,
146
- getOrchestratorUsage: () => orchestrator.usage as { input: number; output: number; cacheRead: number; cacheWrite: number; model?: string },
147
- toolRegistry,
148
- providerRegistry: services.providerRegistry,
149
- contextWindow: services.providerRegistry.getContextWindowForModel(services.providerRegistry.getCurrentModel()),
150
- orchestrator,
151
- getCtxWindow: () => services.providerRegistry.getContextWindowForModel(services.providerRegistry.getCurrentModel()),
152
- resumeSession,
153
- requestRender,
154
- submitPlanningAnswer: (answer) => {
155
- submitPlanningAnswerWithShellFallback(answer, {
156
- getSubmitInput: () => commandContextRef?.submitInput,
157
- addSystemMessage: (message) => conversation.addSystemMessage(message),
158
- requestRender,
159
- });
160
- },
161
- dismissPlanning: () => {
162
- services.panelManager.close('project-planning');
163
- commandContextRef?.focusPrompt?.();
164
- requestRender();
165
- },
166
- forensicsRegistry,
167
- policyRuntimeState,
168
- approvalBroker: services.approvalBroker,
169
- sessionBroker: services.sessionBroker,
170
- automationManager: services.automationManager,
171
- getControlPlaneRecentEvents,
172
- tokenAuditor: services.tokenAuditor,
173
- componentHealthMonitor: services.componentHealthMonitor,
174
- worktreeRegistry: services.worktreeRegistry,
175
- sandboxSessionRegistry: services.sandboxSessionRegistry,
176
- systemMessagesPanel,
177
- memoryRegistry: services.memoryRegistry,
178
- agentKnowledgeService: services.agentKnowledgeService,
179
- uiServices,
180
- pluginManager: services.pluginManager,
181
- hookDispatcher: services.hookDispatcher,
182
- hookActivityTracker: services.hookActivityTracker,
183
- hookWorkbench: services.hookWorkbench,
184
- mcpRegistry: services.mcpRegistry,
185
- connectedHostTokenDir: join(services.homeDirectory, '.goodvibes', 'daemon'),
186
- });
187
- services.panelManager.prewarmRegistered();
188
142
 
189
143
  const systemMessageRouter = createSystemMessageRouter(
190
144
  conversation,
191
- systemMessagesPanel,
145
+ activityFeed,
192
146
  (kind) => {
193
147
  const ui = getConfigSnapshot(configManager).ui;
194
148
  if (kind === 'wrfc') return ui.wrfcMessages;
@@ -287,7 +241,6 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
287
241
  mcpApi,
288
242
  opsApi,
289
243
  directTransport,
290
- panelManager: services.panelManager,
291
244
  worktreeRegistry: services.worktreeRegistry,
292
245
  sandboxSessionRegistry: services.sandboxSessionRegistry,
293
246
  loadSystemPrompt: () => loadBootstrapSystemPrompt(configManager),
@@ -69,10 +69,10 @@ const GOODVIBES_AGENT_OPERATOR_POLICY = [
69
69
  '- Act as one user-facing autonomous assistant. Prefer the lowest-friction safe path that completes the user outcome; do not expose internal package or host ownership unless it is needed for diagnosis, setup, or safety.',
70
70
  '- Work serially in the main conversation by default for ordinary chat, research, planning, setup, local context, and short tool work. Use visible schedules, work plans, operator actions, or delegated/remote routes for durable or long-running autonomy.',
71
71
  '- Connected-host lifecycle is not ambient. If the host is unavailable, explain the shortest user action to make the assistant reachable; do not pretend a missing host route worked.',
72
- '- Read tools: `route action:"plan|status"`, `schedule action:"list"`, `setup action:"status|item|repair|checkpoint"`, `settings action:"list|get"`, `vibe action:"status|show"`, `context action:"status|files|file|prompt|receipts|receipt"`, `memory action:"status|provider|curator|candidate|list|search|get"`, `channels action:"status|channel|setup|triage|deliveries"`, `models action:"status|route|local|providers|provider"`, `personal_ops action:"briefing|status|queue|intake|lane"`, `autonomy action:"intake|queue|item|status"`, `delegation action:"status|routes|route"`, `execution action:"status|route|history|record|processes|process_capabilities|process|recovery"`, `security action:"status|finding|explain"`, `support action:"status|bundle"`, `sessions action:"list|get"`, `audit action:"readiness|item|evidence|artifact"`, `computer action:"status|plan|control|browser|setup|mcp"`, `research action:"plan|search|runner|runs|run|sources|source|bundle|reports|report_artifact"`, `device action:"status|capability|browser|control|voice|provider"`, `workspace action:"status|actions|action|surfaces|surface|panels|panel|shortcuts|keybindings|keybinding|commands|command|cli_commands|cli_command"`, `host action:"status|capabilities|capability|services|service|methods|method"`, `import_goodvibes_settings action:"preview"`, and `agent_operator_briefing` for connected work/approvals/automation/schedules, `agent_knowledge` for isolated Agent Knowledge, and `agent_harness` for harness catalogs/status/capability discovery. Use `agent_artifacts` for saved artifact list/preview/export/package/archive; write modes are confirmation-gated.',
73
- '- Harness access: use `settings action:"set|reset|import"` and `workspace action:"run|run_command|open|open_panel|run_keybinding|set_keybinding|reset_keybinding"` to use the same surfaces the user can use; lower-level `agent_harness` modes remain for detail inspection and compatibility.',
72
+ '- Read tools: `route action:"plan|status"`, `schedule action:"list"`, `setup action:"status|item|repair|checkpoint"`, `settings action:"list|get"`, `vibe action:"status|show"`, `context action:"status|files|file|prompt|receipts|receipt"`, `memory action:"status|provider|curator|candidate|list|search|get"`, `channels action:"status|channel|setup|triage|deliveries"`, `models action:"status|route|local|providers|provider"`, `personal_ops action:"briefing|status|queue|intake|lane"`, `autonomy action:"intake|queue|item|status"`, `delegation action:"status|routes|route"`, `execution action:"status|route|history|record|processes|process_capabilities|process|recovery"`, `security action:"status|finding|explain"`, `support action:"status|bundle"`, `sessions action:"list|get"`, `audit action:"readiness|item|evidence|artifact"`, `computer action:"status|plan|control|browser|setup|mcp"`, `research action:"plan|search|runner|runs|run|sources|source|bundle|reports|report_artifact"`, `device action:"status|capability|browser|control|voice|provider"`, `workspace action:"status|actions|action|surfaces|surface|shortcuts|keybindings|keybinding|commands|command|cli_commands|cli_command"`, `host action:"status|capabilities|capability|services|service|methods|method"`, `import_goodvibes_settings action:"preview"`, and `agent_operator_briefing` for connected work/approvals/automation/schedules, `agent_knowledge` for isolated Agent Knowledge, and `agent_harness` for harness catalogs/status/capability discovery. Use `agent_artifacts` for saved artifact list/preview/export/package/archive; write modes are confirmation-gated.',
73
+ '- Harness access: use `settings action:"set|reset|import"` and `workspace action:"run|run_command|open|run_keybinding|set_keybinding|reset_keybinding"` to use the same surfaces the user can use; lower-level `agent_harness` modes remain for detail inspection and compatibility.',
74
74
  '- State tools: `agent_work_plan` for visible local work items; `agent_local_registry` for Agent-local notes, memory, personas, skills, bundles, and routines; `agent_learning_consolidation` for confirmed duplicate cleanup phases; `agent_documents` for versioned Agent document drafts. Keep records non-secret, sourced, and reviewable.',
75
- '- Confirmed tools: use `schedule`, `setup action:"save_checkpoint|clear_checkpoint|token|smoke|finish|import_settings"`, `vibe action:"init|import_persona"`, `models action:"smoke"` for local model server checks, `personal_ops action:"read"` for one live read-only inbox/calendar connector operation, `research action:"create_run|start_run|checkpoint|pause|resume|cancel|complete|fail|delete_run|add_source|review_source|reject_source|use_source|delete_source|report"`, `computer action:"open_browser"`, `device action:"open_browser|open_tts_provider|open_tts_voice"`, `workspace action:"run|run_command|open|open_panel|run_keybinding|set_keybinding|reset_keybinding"`, `import_goodvibes_settings`, `agent_operator_action`, `agent_artifacts` export/package/archive, `agent_documents`, `agent_review_packet_presets` save/refresh, `agent_review_packet_share`, `agent_knowledge_ingest`, `agent_learning_consolidation`, `agent_media_generate`, `agent_model_compare`, `agent_research_runs`, `agent_research_sources`, `agent_research_report`, `agent_notify`, `agent_channel_send`, `agent_autonomy_schedule`, `agent_reminder_schedule`, and `agent_schedule_edit` only for explicit user requests with confirm:true and explicitUserRequest.',
75
+ '- Confirmed tools: use `schedule`, `setup action:"save_checkpoint|clear_checkpoint|token|smoke|finish|import_settings"`, `vibe action:"init|import_persona"`, `models action:"smoke"` for local model server checks, `personal_ops action:"read"` for one live read-only inbox/calendar connector operation, `research action:"create_run|start_run|checkpoint|pause|resume|cancel|complete|fail|delete_run|add_source|review_source|reject_source|use_source|delete_source|report"`, `computer action:"open_browser"`, `device action:"open_browser|open_tts_provider|open_tts_voice"`, `workspace action:"run|run_command|open|run_keybinding|set_keybinding|reset_keybinding"`, `import_goodvibes_settings`, `agent_operator_action`, `agent_artifacts` export/package/archive, `agent_documents`, `agent_review_packet_presets` save/refresh, `agent_review_packet_share`, `agent_knowledge_ingest`, `agent_learning_consolidation`, `agent_media_generate`, `agent_model_compare`, `agent_research_runs`, `agent_research_sources`, `agent_research_report`, `agent_notify`, `agent_channel_send`, `agent_autonomy_schedule`, `agent_reminder_schedule`, and `agent_schedule_edit` only for explicit user requests with confirm:true and explicitUserRequest.',
76
76
  '- Agent Knowledge must use only `/api/goodvibes-agent/knowledge/*` and fail closed. Do not use default knowledge or non-Agent knowledge spaces.',
77
77
  '- External delivery, media generation, reminders, settings writes, slash-command mirrors, workspace action mirrors, and destructive local changes require explicit user intent and the owning tool/command confirmation.',
78
78
  '- Autonomous work must be visible, reviewable, and cancellable. Never create silent hidden jobs; when work should continue later, create or use an explicit schedule, reminder, work-plan item, operator action, or delegated/remote task route.',
@@ -237,7 +237,6 @@ export async function bootstrapRuntime(
237
237
  const {
238
238
  automationManager,
239
239
  hookDispatcher,
240
- panelManager,
241
240
  pluginManager,
242
241
  } = services;
243
242
 
@@ -19,7 +19,7 @@ import type { FeatureFlagManager } from '@/runtime/index.ts';
19
19
  import type { MutableRuntimeState } from '@/runtime/index.ts';
20
20
  import type { SessionSnapshot } from '@/runtime/index.ts';
21
21
  import type { RuntimeServices } from './services.ts';
22
- import type { ComponentHealthMonitor } from './perf/panel-health-monitor.ts';
22
+ import type { ComponentHealthMonitor } from './perf/component-health.ts';
23
23
  import type { WorktreeRegistry } from '@/runtime/index.ts';
24
24
  import type { SandboxSessionRegistry } from '@/runtime/index.ts';
25
25
 
@@ -19,6 +19,5 @@ export { TransportPanel } from '@/runtime/index.ts';
19
19
  export type { TransportPanelSnapshot } from '@/runtime/index.ts';
20
20
  export { OpsPanel } from './ops.ts';
21
21
  export type { OpsAuditEntry } from './ops.ts';
22
- export { PanelResourcesPanel } from './panel-resources.ts';
23
22
  export { SecurityPanel } from '@/runtime/index.ts';
24
23
  export type { SecurityPanelSnapshot } from '@/runtime/index.ts';
@@ -155,7 +155,7 @@ export class PolicyDiagnosticsPanel {
155
155
  */
156
156
  public subscribe(callback: () => void): () => void {
157
157
  this._subscribers.add(callback);
158
- return () => this._subscribers.delete(callback);
158
+ return () => { this._subscribers.delete(callback); };
159
159
  }
160
160
 
161
161
  /**
@@ -98,6 +98,21 @@ function stringArg(args: Record<string, unknown>, keys: readonly string[]): stri
98
98
  return undefined;
99
99
  }
100
100
 
101
+ /**
102
+ * Classify a tool call into a route kind for display and filtering.
103
+ *
104
+ * Keyword precedence (first match wins):
105
+ * 1. browser — browser/desktop/screenshot keywords
106
+ * 2. delegation — delegate/agent keywords
107
+ * 3. shell — exec/shell/bash keywords OR args.command is a string
108
+ * 4. write — write/edit/patch/delete keywords
109
+ * 5. network — fetch/web/http/url keywords
110
+ * 6. read — read/find/inspect/grep/search keywords
111
+ * 7. other — fallback for unrecognized tool names
112
+ *
113
+ * N2: precedence is intentional — shell beats write/read so that bash-exec tools
114
+ * that also match 'exec' are not misclassified as filesystem writes.
115
+ */
101
116
  function routeKindForTool(tool: string, args: Record<string, unknown>): AgentExecutionRouteKind {
102
117
  const name = tool.toLowerCase();
103
118
  if (name.includes('browser') || name.includes('desktop') || name.includes('screenshot')) return 'browser';
@@ -162,7 +177,7 @@ export class AgentExecutionLedger {
162
177
 
163
178
  public subscribe(callback: () => void): () => void {
164
179
  this.subscribers.add(callback);
165
- return () => this.subscribers.delete(callback);
180
+ return () => { this.subscribers.delete(callback); };
166
181
  }
167
182
 
168
183
  public dispose(): void {
@@ -325,6 +325,11 @@ export const getDistributedNodeHostContract = operations.getDistributedNodeHostC
325
325
  export const CURRENT_PROTOCOL_VERSION = operations.CURRENT_PROTOCOL_VERSION;
326
326
  export const VersionMismatchError = operations.VersionMismatchError;
327
327
  export const negotiateProtocolVersion = operations.negotiateProtocolVersion;
328
+ // Protocol version types — re-exported for transport compatibility tests.
329
+ // ProtocolVersion, VersionNegotiationResult, NegotiatedProtocol are available
330
+ // via operations namespace (operations.ProtocolVersion etc.) but cannot be
331
+ // re-exported here without a registered subpath. Tests that need these types
332
+ // should import from the operations namespace types directly.
328
333
  export const createTaskManager = operations.createTaskManager;
329
334
  export const PhasedToolExecutor = operations.PhasedToolExecutor;
330
335
  export const budgetPhase = operations.budgetPhase;
@@ -614,7 +619,7 @@ export type BundleProvenance = Security.BundleProvenance;
614
619
  export type DecisionReason = Security.DecisionReason;
615
620
  export type DivergenceReport = Security.DivergenceReport;
616
621
  export type EnforceGateResult = Security.EnforceGateResult;
617
- export type SignedPolicyBundle = Security.SignedPolicyBundle;
622
+ export type SignedPolicyBundle<T = unknown> = Security.SignedPolicyBundle<T>;
618
623
 
619
624
  export interface InspectableDomain {
620
625
  readonly name: string;
@@ -5,3 +5,4 @@ export * from './apply.ts';
5
5
  export * from './verify.ts';
6
6
  export * from './markers.ts';
7
7
  export * from './state.ts';
8
+ export * from './onboarding-state.ts';
@@ -0,0 +1,200 @@
1
+ import type { SetupPlanItem, SetupPlanStatus } from '../../tools/agent-harness-setup-posture-types.ts';
2
+ import type { OnboardingCheckMarkerState } from './types.ts';
3
+
4
+ /**
5
+ * Maps each setup plan item id to the Agent Workspace category that resolves it.
6
+ * This is the single sequencing table — every onboarding surface reads from here.
7
+ */
8
+ const PLAN_ITEM_CATEGORY_MAP: Readonly<Record<string, string>> = {
9
+ 'connected-host-readiness': 'setup',
10
+ 'connected-host-auth': 'setup',
11
+ 'goodvibes-settings-import': 'setup',
12
+ 'provider-access': 'account-model',
13
+ 'install-smoke': 'setup',
14
+ 'local-model-readiness': 'account-model',
15
+ 'agent-knowledge': 'setup',
16
+ 'vibe-personality': 'assistant-behavior',
17
+ 'local-behavior': 'onboarding-context',
18
+ 'communication-channels': 'onboarding-channels',
19
+ 'automation-review': 'tools-permissions',
20
+ 'browser-desktop-control': 'tools-permissions',
21
+ 'sudo-execution-posture': 'tools-permissions',
22
+ 'build-delegation': 'tools-permissions',
23
+ 'finish-onboarding': 'setup',
24
+ };
25
+
26
+ const DEFAULT_CATEGORY_ID = 'setup';
27
+
28
+ function planItemCategoryId(planItemId: string): string {
29
+ return PLAN_ITEM_CATEGORY_MAP[planItemId] ?? DEFAULT_CATEGORY_ID;
30
+ }
31
+
32
+ export interface OnboardingStep {
33
+ readonly id: string;
34
+ readonly label: string;
35
+ readonly status: SetupPlanStatus;
36
+ readonly blocksAutonomy: boolean;
37
+ /** Human-readable hint shown in the UI for the next action. */
38
+ readonly nextLabel: string;
39
+ /** Agent Workspace category id that resolves this step. */
40
+ readonly categoryId: string;
41
+ }
42
+
43
+ export interface OnboardingRecap {
44
+ /** Single headline sentence describing overall readiness. */
45
+ readonly headline: string;
46
+ /** One line per ready capability plus optional trailing lines. */
47
+ readonly lines: readonly string[];
48
+ }
49
+
50
+ export type OnboardingPhase = 'fresh' | 'in-progress' | 'complete';
51
+
52
+ export interface OnboardingState {
53
+ /**
54
+ * Phase derived from markers:
55
+ * - 'complete' completion marker present
56
+ * - 'in-progress' check marker present but no completion marker
57
+ * - 'fresh' neither marker present
58
+ */
59
+ readonly phase: OnboardingPhase;
60
+ /** All setup plan items converted to lightweight steps. */
61
+ readonly steps: readonly OnboardingStep[];
62
+ /** First non-ready step that blocks autonomy, or first non-ready step if none, or null. */
63
+ readonly currentStepId: string | null;
64
+ /** Human-readable progress, e.g. "2 of 5 ready". */
65
+ readonly progressLabel: string;
66
+ /** Steps that are not ready and block autonomy. */
67
+ readonly blockers: readonly OnboardingStep[];
68
+ /**
69
+ * True when a usable model route exists:
70
+ * the provider-access plan item is 'ready', or local-model-readiness is 'ready'.
71
+ */
72
+ readonly readyToChat: boolean;
73
+ /** Summary shown at the end of onboarding or as a re-entry summary. */
74
+ readonly recap: OnboardingRecap;
75
+ }
76
+
77
+ export interface DeriveOnboardingStateContext {
78
+ /** Fully-built setup plan (from buildSetupPlan). */
79
+ readonly plan: readonly SetupPlanItem[];
80
+ /** User-scope check marker (from readOnboardingCheckMarker). */
81
+ readonly checkMarker: OnboardingCheckMarkerState;
82
+ /** User-scope completion marker (from readOnboardingCompletionMarker). */
83
+ readonly completionMarker: OnboardingCheckMarkerState;
84
+ }
85
+
86
+ function derivePhase(
87
+ checkMarker: OnboardingCheckMarkerState,
88
+ completionMarker: OnboardingCheckMarkerState,
89
+ ): OnboardingPhase {
90
+ if (completionMarker.exists && completionMarker.payload !== null) return 'complete';
91
+ if (checkMarker.exists) return 'in-progress';
92
+ return 'fresh';
93
+ }
94
+
95
+ function planItemToStep(item: SetupPlanItem): OnboardingStep {
96
+ return {
97
+ id: item.id,
98
+ label: item.label,
99
+ status: item.status,
100
+ blocksAutonomy: item.blocksAutonomy,
101
+ nextLabel: item.nextAction,
102
+ categoryId: planItemCategoryId(item.id),
103
+ };
104
+ }
105
+
106
+ function deriveCurrentStepId(steps: readonly OnboardingStep[]): string | null {
107
+ // Priority: first non-ready blocker, then first non-ready step overall.
108
+ const firstBlocker = steps.find((s) => s.status !== 'ready' && s.blocksAutonomy);
109
+ if (firstBlocker) return firstBlocker.id;
110
+ const firstNonReady = steps.find((s) => s.status !== 'ready');
111
+ return firstNonReady?.id ?? null;
112
+ }
113
+
114
+ function deriveProgressLabel(plan: readonly SetupPlanItem[]): string {
115
+ const ready = plan.filter((item) => item.status === 'ready').length;
116
+ const total = plan.length;
117
+ return `${ready} of ${total} ready`;
118
+ }
119
+
120
+ function deriveReadyToChat(plan: readonly SetupPlanItem[]): boolean {
121
+ const providerItem = plan.find((item) => item.id === 'provider-access');
122
+ if (providerItem?.status === 'ready') return true;
123
+ const localModelItem = plan.find((item) => item.id === 'local-model-readiness');
124
+ return localModelItem?.status === 'ready';
125
+ }
126
+
127
+ function deriveRecap(
128
+ phase: OnboardingPhase,
129
+ steps: readonly OnboardingStep[],
130
+ plan: readonly SetupPlanItem[],
131
+ ): OnboardingRecap {
132
+ const readySteps = steps.filter((s) => s.status === 'ready');
133
+ const optionalCount = plan.filter(
134
+ (item) => item.status !== 'ready' && !item.blocksAutonomy,
135
+ ).length;
136
+
137
+ const headline =
138
+ phase === 'complete'
139
+ ? "You're set up. Here's what you can do now."
140
+ : phase === 'in-progress'
141
+ ? "Setup is in progress. Here's what's ready so far."
142
+ : 'Getting started — a few things to set up before the full experience.';
143
+
144
+ const lines: string[] = readySteps.map((s) => s.label);
145
+
146
+ // Capability examples: show what the user can actually do right now.
147
+ // Always-safe examples (research/draft/summarize) are shown whenever a model route is ready.
148
+ // Channel-dependent examples (reminders/messaging) only appear when that lane is configured.
149
+ const modelReady = deriveReadyToChat(plan);
150
+ if (modelReady && phase !== 'fresh') {
151
+ const channelsReady = plan.some(
152
+ (item) => item.id === 'communication-channels' && item.status === 'ready',
153
+ );
154
+ const capabilityLines: string[] = [
155
+ 'Ask me to research a topic, draft an email, summarize a file, or compare models.',
156
+ ];
157
+ if (channelsReady) {
158
+ capabilityLines.push('You can also set reminders and send messages through your connected channels.');
159
+ }
160
+ lines.push(...capabilityLines);
161
+ }
162
+
163
+ if (optionalCount > 0 && phase !== 'fresh') {
164
+ lines.push(`Still optional: ${optionalCount} item${optionalCount === 1 ? '' : 's'} available`);
165
+ }
166
+
167
+ if (phase !== 'fresh') {
168
+ lines.push('Try: "What can you help me with today?"');
169
+ }
170
+
171
+ return { headline, lines };
172
+ }
173
+
174
+ /**
175
+ * Pure selector: derives the full OnboardingState from an already-built plan and
176
+ * the current marker files. No side effects, no Date.now() calls.
177
+ */
178
+ export function deriveOnboardingState(
179
+ context: DeriveOnboardingStateContext,
180
+ ): OnboardingState {
181
+ const { plan, checkMarker, completionMarker } = context;
182
+
183
+ const phase = derivePhase(checkMarker, completionMarker);
184
+ const steps = plan.map(planItemToStep);
185
+ const currentStepId = deriveCurrentStepId(steps);
186
+ const progressLabel = deriveProgressLabel(plan);
187
+ const blockers = steps.filter((s) => s.status !== 'ready' && s.blocksAutonomy);
188
+ const readyToChat = deriveReadyToChat(plan);
189
+ const recap = deriveRecap(phase, steps, plan);
190
+
191
+ return {
192
+ phase,
193
+ steps,
194
+ currentStepId,
195
+ progressLabel,
196
+ blockers,
197
+ readyToChat,
198
+ recap,
199
+ };
200
+ }