@pellux/goodvibes-agent 1.4.4 → 1.5.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.
Files changed (230) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +52 -7
  3. package/dist/package/main.js +7491 -12253
  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/live-verification/live-verification.json +13 -13
  11. package/release/live-verification/live-verification.md +15 -15
  12. package/release/performance-snapshot.json +2 -2
  13. package/release/release-notes.md +5 -7
  14. package/release/release-readiness.json +6 -6
  15. package/src/agent/behavior-discovery-summary.ts +4 -18
  16. package/src/agent/calendar-registry.ts +322 -0
  17. package/src/agent/competitive-feature-inventory.ts +268 -130
  18. package/src/agent/document-registry.ts +5 -1
  19. package/src/agent/email/email-service.ts +350 -0
  20. package/src/agent/email/imap-client.ts +596 -0
  21. package/src/agent/email/smtp-client.ts +453 -0
  22. package/src/agent/ics-calendar.ts +662 -0
  23. package/src/agent/ics-timezone.ts +172 -0
  24. package/src/agent/markdown-frontmatter.ts +31 -0
  25. package/src/agent/memory-safety.ts +1 -1
  26. package/src/agent/note-registry.ts +3 -9
  27. package/src/agent/persona-discovery.ts +3 -15
  28. package/src/agent/persona-registry.ts +1 -10
  29. package/src/agent/research-source-registry.ts +5 -1
  30. package/src/agent/routine-discovery.ts +3 -15
  31. package/src/agent/runtime-profile.ts +3 -0
  32. package/src/agent/skill-discovery.ts +3 -15
  33. package/src/agent/skill-draft-proposer.ts +203 -0
  34. package/src/agent/skill-draft-runner.ts +201 -0
  35. package/src/agent/skill-registry.ts +36 -1
  36. package/src/agent/skill-standard.ts +99 -0
  37. package/src/agent/vibe-file.ts +7 -22
  38. package/src/cli/completion.ts +1 -1
  39. package/src/cli/config-overrides.ts +10 -1
  40. package/src/cli/redaction.ts +17 -5
  41. package/src/config/provider-model.ts +2 -1
  42. package/src/config/secret-config.ts +13 -6
  43. package/src/core/activity-feed.ts +97 -0
  44. package/src/core/away-digest.ts +161 -0
  45. package/src/core/conversation-rendering.ts +7 -3
  46. package/src/core/conversation.ts +22 -15
  47. package/src/core/hardware-profile.ts +362 -0
  48. package/src/core/last-seen-store.ts +78 -0
  49. package/src/core/plain-language.ts +52 -0
  50. package/src/core/setup-incomplete-hint.ts +90 -0
  51. package/src/core/system-message-router.ts +38 -87
  52. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  53. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  54. package/src/input/agent-workspace-categories.ts +40 -125
  55. package/src/input/agent-workspace-command-editor.ts +4 -0
  56. package/src/input/agent-workspace-host-category.ts +0 -5
  57. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  58. package/src/input/agent-workspace-navigation.ts +10 -3
  59. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  60. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  61. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  62. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  63. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  64. package/src/input/agent-workspace-profile-editors.ts +155 -0
  65. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  66. package/src/input/agent-workspace-types.ts +5 -1
  67. package/src/input/agent-workspace.ts +65 -39
  68. package/src/input/command-registry.ts +18 -5
  69. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  70. package/src/input/commands/agent-skills-runtime.ts +60 -3
  71. package/src/input/commands/calendar-runtime.ts +209 -0
  72. package/src/input/commands/channels-runtime.ts +1 -0
  73. package/src/input/commands/command-error.ts +9 -0
  74. package/src/input/commands/compat-runtime.ts +1 -0
  75. package/src/input/commands/config.ts +1 -0
  76. package/src/input/commands/conversation-runtime.ts +1 -1
  77. package/src/input/commands/delegation-runtime.ts +5 -6
  78. package/src/input/commands/email-runtime.ts +387 -0
  79. package/src/input/commands/experience-runtime.ts +17 -4
  80. package/src/input/commands/guidance-runtime.ts +1 -1
  81. package/src/input/commands/health-runtime.ts +6 -1
  82. package/src/input/commands/knowledge-format.ts +73 -0
  83. package/src/input/commands/knowledge.ts +9 -74
  84. package/src/input/commands/local-provider-runtime.ts +2 -1
  85. package/src/input/commands/local-runtime.ts +10 -4
  86. package/src/input/commands/mcp-runtime.ts +7 -0
  87. package/src/input/commands/notify-runtime.ts +17 -1
  88. package/src/input/commands/onboarding-runtime.ts +1 -0
  89. package/src/input/commands/operator-actions-runtime.ts +1 -0
  90. package/src/input/commands/operator-runtime.ts +3 -0
  91. package/src/input/commands/personas-runtime.ts +1 -0
  92. package/src/input/commands/platform-access-runtime.ts +40 -17
  93. package/src/input/commands/product-runtime.ts +6 -1
  94. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  95. package/src/input/commands/qrcode-runtime.ts +1 -0
  96. package/src/input/commands/routines-runtime.ts +1 -0
  97. package/src/input/commands/runtime-services.ts +0 -13
  98. package/src/input/commands/security-runtime.ts +1 -0
  99. package/src/input/commands/session-content.ts +1 -0
  100. package/src/input/commands/shell-core.ts +5 -2
  101. package/src/input/commands/subscription-runtime.ts +33 -9
  102. package/src/input/commands/support-bundle-runtime.ts +8 -1
  103. package/src/input/commands/tasks-runtime.ts +1 -0
  104. package/src/input/commands/tts-runtime.ts +1 -0
  105. package/src/input/commands/vibe-runtime.ts +1 -0
  106. package/src/input/commands.ts +4 -0
  107. package/src/input/feed-context-factory.ts +3 -12
  108. package/src/input/handler-command-route.ts +7 -60
  109. package/src/input/handler-feed-routes.ts +0 -194
  110. package/src/input/handler-feed.ts +2 -54
  111. package/src/input/handler-interactions.ts +0 -2
  112. package/src/input/handler-modal-stack.ts +0 -9
  113. package/src/input/handler-picker-routes.ts +24 -1
  114. package/src/input/handler-shortcuts.ts +11 -40
  115. package/src/input/handler-ui-state.ts +13 -0
  116. package/src/input/handler.ts +8 -35
  117. package/src/input/keybindings.ts +40 -17
  118. package/src/input/model-picker-local-fit.ts +130 -0
  119. package/src/input/shell-passthrough.ts +58 -0
  120. package/src/input/submission-router.ts +0 -5
  121. package/src/main.ts +129 -90
  122. package/src/renderer/activity-sidebar.ts +186 -0
  123. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  124. package/src/renderer/agent-workspace-style.ts +1 -1
  125. package/src/renderer/agent-workspace.ts +14 -2
  126. package/src/renderer/compositor.ts +37 -125
  127. package/src/renderer/conversation-overlays.ts +3 -3
  128. package/src/renderer/help-overlay.ts +7 -5
  129. package/src/renderer/model-workspace.ts +101 -86
  130. package/src/{panels → renderer}/polish.ts +3 -3
  131. package/src/renderer/shell-surface.ts +4 -5
  132. package/src/renderer/status-token.ts +31 -11
  133. package/src/renderer/tab-strip.ts +1 -1
  134. package/src/renderer/tool-call.ts +7 -8
  135. package/src/renderer/tool-labels.ts +92 -0
  136. package/src/renderer/ui-factory.ts +48 -96
  137. package/src/runtime/bootstrap-command-context.ts +0 -5
  138. package/src/runtime/bootstrap-command-parts.ts +6 -15
  139. package/src/runtime/bootstrap-core.ts +34 -13
  140. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  141. package/src/runtime/bootstrap-shell.ts +3 -50
  142. package/src/runtime/bootstrap.ts +3 -4
  143. package/src/runtime/context.ts +1 -1
  144. package/src/runtime/diagnostics/panels/index.ts +0 -1
  145. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  146. package/src/runtime/execution-ledger.ts +16 -1
  147. package/src/runtime/index.ts +6 -1
  148. package/src/runtime/onboarding/index.ts +1 -0
  149. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  150. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  151. package/src/runtime/services.ts +16 -4
  152. package/src/runtime/terminal-output-guard.ts +7 -0
  153. package/src/runtime/tool-permission-safety.ts +1 -1
  154. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  155. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  156. package/src/runtime/ui/model-picker/types.ts +2 -2
  157. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  158. package/src/runtime/ui/provider-health/types.ts +2 -2
  159. package/src/runtime/ui-services.ts +0 -2
  160. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  161. package/src/shell/autonomy-surfacing.ts +272 -0
  162. package/src/shell/session-continuity-hints.ts +0 -6
  163. package/src/shell/startup-wiring.ts +221 -0
  164. package/src/shell/ui-openers.ts +18 -29
  165. package/src/tools/agent-context-policy.ts +1 -1
  166. package/src/tools/agent-harness-background-processes.ts +4 -4
  167. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  168. package/src/tools/agent-harness-command-runner.ts +6 -1
  169. package/src/tools/agent-harness-document-ops.ts +26 -53
  170. package/src/tools/agent-harness-execution-history.ts +1 -13
  171. package/src/tools/agent-harness-execution-posture.ts +0 -15
  172. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  173. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  174. package/src/tools/agent-harness-metadata.ts +16 -0
  175. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  176. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  177. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  178. package/src/tools/agent-harness-tool-schema.ts +13 -14
  179. package/src/tools/agent-harness-tool.ts +27 -23
  180. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  181. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  182. package/src/tools/agent-workspace-tool.ts +2 -33
  183. package/src/utils/terminal-width.ts +9 -3
  184. package/src/version.ts +1 -1
  185. package/src/input/agent-workspace-panel-route.ts +0 -44
  186. package/src/input/panel-integration-actions.ts +0 -26
  187. package/src/panels/approval-panel.ts +0 -149
  188. package/src/panels/automation-control-panel.ts +0 -212
  189. package/src/panels/base-panel.ts +0 -254
  190. package/src/panels/builtin/agent.ts +0 -58
  191. package/src/panels/builtin/knowledge.ts +0 -26
  192. package/src/panels/builtin/operations.ts +0 -121
  193. package/src/panels/builtin/session.ts +0 -138
  194. package/src/panels/builtin/shared.ts +0 -275
  195. package/src/panels/builtin/usage.ts +0 -21
  196. package/src/panels/builtin-panels.ts +0 -23
  197. package/src/panels/confirm-state.ts +0 -61
  198. package/src/panels/context-visualizer-panel.ts +0 -204
  199. package/src/panels/cost-tracker-panel.ts +0 -444
  200. package/src/panels/docs-panel.ts +0 -285
  201. package/src/panels/index.ts +0 -25
  202. package/src/panels/knowledge-panel.ts +0 -417
  203. package/src/panels/memory-panel.ts +0 -226
  204. package/src/panels/panel-list-panel.ts +0 -464
  205. package/src/panels/panel-manager.ts +0 -570
  206. package/src/panels/provider-accounts-panel.ts +0 -233
  207. package/src/panels/provider-health-domains.ts +0 -208
  208. package/src/panels/provider-health-panel.ts +0 -720
  209. package/src/panels/provider-health-tracker.ts +0 -115
  210. package/src/panels/provider-stats-panel.ts +0 -366
  211. package/src/panels/qr-panel.ts +0 -207
  212. package/src/panels/schedule-panel.ts +0 -321
  213. package/src/panels/scrollable-list-panel.ts +0 -491
  214. package/src/panels/search-focus.ts +0 -32
  215. package/src/panels/security-panel.ts +0 -295
  216. package/src/panels/session-browser-panel.ts +0 -395
  217. package/src/panels/session-maintenance.ts +0 -125
  218. package/src/panels/subscription-panel.ts +0 -263
  219. package/src/panels/system-messages-panel.ts +0 -230
  220. package/src/panels/tasks-panel.ts +0 -344
  221. package/src/panels/thinking-panel.ts +0 -304
  222. package/src/panels/token-budget-panel.ts +0 -475
  223. package/src/panels/tool-inspector-panel.ts +0 -436
  224. package/src/panels/types.ts +0 -54
  225. package/src/renderer/panel-composite.ts +0 -158
  226. package/src/renderer/panel-tab-bar.ts +0 -69
  227. package/src/renderer/panel-workspace-bar.ts +0 -42
  228. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  229. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  230. /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
package/src/main.ts CHANGED
@@ -20,25 +20,23 @@ import { renderSettingsModal } from './renderer/settings-modal.ts';
20
20
  import { registerBuiltinCommands } from './input/commands.ts';
21
21
  import { ScheduleManager } from '@pellux/goodvibes-sdk/platform/tools';
22
22
  import { InputHistory } from './input/input-history.ts';
23
+ import { ShellPassthrough, SHELL_USAGE_HINT } from './input/shell-passthrough.ts';
23
24
  import { getTierPromptSupplement, getTierForContextWindow } from '@pellux/goodvibes-sdk/platform/providers';
24
25
  import { createShellLayout } from './renderer/layout-engine.ts';
25
26
  import { buildShellFooter, estimateShellFooterHeight } from './renderer/shell-surface.ts';
26
27
  import { buildConversationViewport } from './renderer/conversation-layout.ts';
27
28
  import { applyConversationOverlays } from './renderer/conversation-overlays.ts';
28
- import { buildPanelCompositeData } from './renderer/panel-composite.ts';
29
+ import { buildActivitySidebarLines, resolveActivitySidebarWidth } from './renderer/activity-sidebar.ts';
29
30
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
30
- import { registerBuiltinPanels } from './panels/builtin-panels.ts';
31
- import { renderPanelTabBar } from './renderer/panel-tab-bar.ts';
32
31
  import { bootstrapRuntime } from './runtime/bootstrap.ts';
33
32
  import type { BootstrapContext } from './runtime/bootstrap.ts';
34
33
  import type { HITLMode } from '@pellux/goodvibes-sdk/platform/state';
35
- import type { HookPhase, HookCategory, HookEventPath } from '@pellux/goodvibes-sdk/platform/hooks';
34
+ import { wireSessionPersistenceAndRecovery, wireSetupIncompleteHint } from './shell/startup-wiring.ts';
35
+ import { localModelCookbook } from './tools/agent-harness-model-routing.ts';
36
+ import { localModelSetupStatus } from './tools/agent-harness-setup-model-helpers.ts';
36
37
  import {
37
- checkRecoveryFile,
38
38
  deleteRecoveryFile,
39
39
  loadRecoveryConversation,
40
- persistConversation,
41
- writeRecoveryFile,
42
40
  } from '@/runtime/index.ts';
43
41
  import type { SessionSnapshot } from '@/runtime/index.ts';
44
42
  import { handleBlockingShellInput, type PendingPermissionState } from './shell/blocking-input.ts';
@@ -47,7 +45,7 @@ import { getTerminalSize } from './shell/terminal-size.ts';
47
45
  import { buildShellSessionContinuityHints } from './shell/session-continuity-hints.ts';
48
46
  import { wireShellUiOpeners } from './shell/ui-openers.ts';
49
47
  import { deriveComposerState } from './core/composer-state.ts';
50
- import { buildPersistedSessionContext, formatReturnContextForDisplay, getReturnContextMode, maybeAssistReturnContextSummary } from '@/runtime/index.ts';
48
+ import { buildPersistedSessionContext } from '@/runtime/index.ts';
51
49
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
52
50
  import { prepareShellCliRuntime } from './cli/entrypoint.ts';
53
51
  import { applyInitialTuiCliState, formatFatalStartupErrorForLog, formatFatalStartupErrorForUser, getInteractiveTerminalLaunchError } from './cli/tui-startup.ts';
@@ -56,6 +54,8 @@ import { attachSpokenTurnModelRouting, createSpokenTurnInputOptions } from './au
56
54
  import { allowTerminalWrite, installTuiTerminalOutputGuard } from './runtime/terminal-output-guard.ts';
57
55
  import { buildCommandArgsHint } from './input/command-args-hint.ts';
58
56
  import { GOODVIBES_AGENT_PAIRING_SURFACE } from './config/surface.ts';
57
+ import { createAutonomySurfacing, buildCalendarEventsLister, buildSkillDraftProposer } from './shell/autonomy-surfacing.ts';
58
+ import { startHardwareProbe } from './core/hardware-profile.ts';
59
59
 
60
60
  const ALT_SCREEN_ENTER = '\x1b[?1049h', ALT_SCREEN_EXIT = '\x1b[?1049l', MOUSE_ENABLE = '\x1b[?1000h\x1b[?1002h\x1b[?1006h', MOUSE_DISABLE = '\x1b[?1006l\x1b[?1002l\x1b[?1000l', CURSOR_HIDE = '\x1b[?25l', CURSOR_SHOW = '\x1b[?25h', CLEAR_SCREEN = '\x1b[2J\x1b[3J\x1b[H';
61
61
  const KEYBOARD_EXT_ENABLE = '\x1b[>4;2m' + '\x1b[?1u', KEYBOARD_EXT_DISABLE = '\x1b[>4;0m' + '\x1b[?1l', PASTE_ENABLE = '\x1b[?2004h', PASTE_DISABLE = '\x1b[?2004l';
@@ -127,12 +127,10 @@ async function main() {
127
127
  }
128
128
  }
129
129
 
130
- const panelManager = ctx.services.panelManager;
131
130
  const buildSessionContinuityHints = () => buildShellSessionContinuityHints(
132
131
  uiServices.readModels.session.getSnapshot(),
133
132
  uiServices.readModels.tasks.getSnapshot(),
134
133
  uiServices.readModels.remote.getSnapshot(),
135
- panelManager.getAllOpen(),
136
134
  );
137
135
  const buildCurrentSessionSnapshot = (): SessionSnapshot => {
138
136
  const messages = conversation.getMessageSnapshot();
@@ -154,13 +152,39 @@ async function main() {
154
152
  render();
155
153
  });
156
154
 
157
- let streamStartTime = 0;
158
- let streamDeltaCount = 0;
159
155
  let streamTokenSpeed = 0;
160
156
 
161
157
  let scrollTop = 0;
162
158
  let scrollLocked = true;
163
159
 
160
+ const shellPassthrough = new ShellPassthrough();
161
+
162
+ // Ambient autonomy surfacing: away digest at launch + sidebar Coming up.
163
+ const autonomy = createAutonomySurfacing({
164
+ shellPaths: ctx.services.shellPaths,
165
+ listAutomationJobs: () => ctx.services.automationManager.listJobs(),
166
+ listApprovals: () => ctx.services.approvalBroker.listApprovals(),
167
+ getTasksSnapshot: () => uiServices.readModels.tasks.getSnapshot().tasks,
168
+ router: {
169
+ high: (message) => systemMessageRouter.high(message),
170
+ getFeed: () => systemMessageRouter.getFeed(),
171
+ },
172
+ render: () => render(),
173
+ listCalendarEvents: buildCalendarEventsLister(ctx.services.shellPaths),
174
+ onAwayDigest: buildSkillDraftProposer(ctx.services.shellPaths, commandContext),
175
+ });
176
+
177
+ // Activity sidebar: shows ambient status on wide terminals. null = automatic
178
+ // (visible when the terminal is wide enough); the user can toggle it with
179
+ // Ctrl+O, which pins an explicit on/off override for the session.
180
+ let sidebarOverride: boolean | null = null;
181
+ const sidebarWidthFor = (width: number): number => {
182
+ const auto = resolveActivitySidebarWidth(width);
183
+ if (sidebarOverride === null) return auto;
184
+ if (!sidebarOverride) return 0;
185
+ return auto > 0 ? auto : Math.min(36, Math.max(28, Math.floor(width * 0.3)));
186
+ };
187
+
164
188
  const getPromptContentWidth = () => {
165
189
  const w = getTerminalSize(stdout).width;
166
190
  const boxMargin = 2;
@@ -228,6 +252,8 @@ async function main() {
228
252
  const exitApp = (): void => {
229
253
  stopSpokenOutputForExit?.();
230
254
  unsubs.forEach(fn => fn());
255
+ // Persist last-seen before shutdown so the next launch can compute the digest.
256
+ autonomy.stop();
231
257
  const snapshot = buildCurrentSessionSnapshot();
232
258
  ctx.shutdown(snapshot).catch((err) => {
233
259
  logger.debug('ctx.shutdown error during exitApp (non-fatal)', { error: summarizeError(err) });
@@ -292,14 +318,28 @@ async function main() {
292
318
  systemMessageRouter.high(`[Memory] Pinned: "${memoryText}" (${memId})`);
293
319
  processedText = memoryText;
294
320
  }
321
+ } else if (processedText.startsWith('!')) {
322
+ const command = processedText.slice(1).trim();
323
+ if (!command) {
324
+ systemMessageRouter.high(SHELL_USAGE_HINT);
325
+ render();
326
+ return;
327
+ }
328
+ systemMessageRouter.high(`[Shell] $ ${command}`); render();
329
+ void shellPassthrough.run(command, workingDir)
330
+ .then((result) => systemMessageRouter.high(result.display))
331
+ .catch((shellErr: unknown) => systemMessageRouter.high(`[Shell] Failed to run: ${summarizeError(shellErr)}`))
332
+ .finally(() => render());
333
+ return;
295
334
  }
296
335
  if (processedText || content) {
297
336
  void (async () => {
298
337
  const inputOptions = options.spokenOutput ? createSpokenTurnInputOptions() : undefined;
338
+ const outgoing = shellPassthrough.consumeContext(processedText);
299
339
  if (options.spokenOutput && processedText) {
300
340
  spokenTurns.submitNextTurn(processedText);
301
341
  }
302
- orchestrator.handleUserInput(processedText, content, inputOptions).catch((err: unknown) => {
342
+ orchestrator.handleUserInput(outgoing, content, inputOptions).catch((err: unknown) => {
303
343
  logger.debug('handleUserInput safety catch (already handled by runTurn)', { error: summarizeError(err) });
304
344
  });
305
345
  })();
@@ -349,6 +389,11 @@ async function main() {
349
389
  allowTerminalWrite(() => stdout.write(CLEAR_SCREEN));
350
390
  render();
351
391
  };
392
+ commandContext.toggleActivitySidebar = () => {
393
+ const width = getTerminalSize(stdout).width;
394
+ sidebarOverride = !(sidebarWidthFor(width) > 0);
395
+ render();
396
+ };
352
397
  permissionPromptRef.requestPermission = (request) =>
353
398
  new Promise((resolve) => {
354
399
  pendingPermission = {
@@ -389,7 +434,6 @@ async function main() {
389
434
  shell: {
390
435
  bookmarkManager: ctx.services.bookmarkManager,
391
436
  keybindingsManager: ctx.services.keybindingsManager,
392
- panelManager,
393
437
  processManager,
394
438
  profileManager: ctx.services.profileManager,
395
439
  },
@@ -430,8 +474,10 @@ async function main() {
430
474
  const render = () => {
431
475
  const { width, height } = getTerminalSize(stdout);
432
476
 
477
+ // Fire-and-forget refresh for the 'Coming up' sidebar section.
478
+ autonomy.refreshComingUp();
479
+
433
480
  if (input.agentWorkspace.active) {
434
- input.setPanelMouseLayout(null);
435
481
  activeConversationWidth = width;
436
482
  conversation.setSplashSuppressed(true);
437
483
  if (input.modelPicker.active) {
@@ -463,7 +509,6 @@ async function main() {
463
509
  const composerState = deriveComposerState({
464
510
  text: input.prompt,
465
511
  commandMode: input.commandMode,
466
- panelFocused: input.panelFocused,
467
512
  pendingApproval: pendingPermission !== null,
468
513
  hasAttachments: input.getImageAttachments().size > 0,
469
514
  turnState: sessionSnapshot.turnState,
@@ -513,31 +558,18 @@ async function main() {
513
558
 
514
559
  const shellHeaderLines = headerLines;
515
560
  const shellFooterLines = footerLines;
516
- const panelWidth = panelManager.isVisible() && panelManager.getAllOpen().length > 0
517
- ? panelManager.getRightWidth(width)
518
- : 0;
561
+ const sidebarWidth = sidebarWidthFor(width);
519
562
  const shellLayout = createShellLayout({
520
563
  width,
521
564
  height,
522
565
  headerHeight: shellHeaderLines.length,
523
566
  footerHeight: shellFooterLines.length,
524
- panelWidth,
567
+ panelWidth: sidebarWidth,
525
568
  });
526
- input.setPanelMouseLayout(shellLayout.panel
527
- ? {
528
- x: shellLayout.panel.x,
529
- y: shellLayout.panel.y,
530
- width: shellLayout.panel.width,
531
- height: shellLayout.panel.height,
532
- hasBottomPane: panelManager.isBottomPaneVisible() && panelManager.getBottomPane().panels.length > 0,
533
- verticalSplitRatio: panelManager.getVerticalSplitRatio(),
534
- }
535
- : null);
536
569
  const vHeight = shellLayout.body.height;
537
570
  const conversationWidth = shellLayout.conversation.width;
538
571
  activeConversationWidth = conversationWidth;
539
- const hasPanelWorkspace = panelManager.isVisible() && panelManager.getAllOpen().length > 0;
540
- conversation.setSplashSuppressed(hasPanelWorkspace);
572
+ conversation.setSplashSuppressed(false);
541
573
 
542
574
  // Flush pending renders after updating the width provider and splash posture
543
575
  // so the transcript and splash rebuild against the current shell layout.
@@ -599,13 +631,27 @@ async function main() {
599
631
  contextWindow: currentModel.contextWindow,
600
632
  });
601
633
 
602
- // Panel composite data
603
- const panelComposite = buildPanelCompositeData(
604
- panelManager,
605
- input,
606
- shellLayout.panel?.width ?? 0,
607
- shellLayout.panel?.height ?? vHeight,
608
- );
634
+ // Activity sidebar (ambient status on wide terminals)
635
+ const sidebar = sidebarWidth > 0
636
+ ? {
637
+ lines: buildActivitySidebarLines({
638
+ now: {
639
+ busy: orchestrator.isThinking,
640
+ label: sessionSnapshot.streamToolPreview?.trim() || undefined,
641
+ agents: activeAgents.slice(0, 3).map((agent) => ({
642
+ label: agent.label,
643
+ progress: agent.latestProgress?.trim() || undefined,
644
+ })),
645
+ processes: runningProcessCount,
646
+ },
647
+ needsYou: pendingPermission
648
+ ? ['Approval needed — answer the prompt under the conversation.']
649
+ : [],
650
+ comingUp: [...autonomy.comingUpItems()],
651
+ recent: systemMessageRouter.getFeed()?.latest(Math.max(4, vHeight - 8)) ?? [],
652
+ }, sidebarWidth, vHeight),
653
+ }
654
+ : undefined;
609
655
 
610
656
  compositor.composite({
611
657
  width, height,
@@ -622,8 +668,8 @@ async function main() {
622
668
  scrollTop,
623
669
  viewportStartY: shellHeaderLines.length,
624
670
  } : undefined,
625
- panel: panelComposite.panelData,
626
- panelWidth: panelComposite.panelWidth,
671
+ sidebar,
672
+ sidebarWidth,
627
673
  });
628
674
  };
629
675
  const terminalOutputGuard = installTuiTerminalOutputGuard({ stdout, stderr: process.stderr, notify: (message) => { systemMessageRouter.low(message); render(); } });
@@ -634,7 +680,6 @@ async function main() {
634
680
  wireShellUiOpeners({
635
681
  commandContext,
636
682
  input,
637
- panelManager,
638
683
  conversation,
639
684
  configManager,
640
685
  providerRegistry,
@@ -651,35 +696,6 @@ async function main() {
651
696
  render,
652
697
  });
653
698
 
654
- // --- Streaming speed + tool preview wiring ---
655
- unsubs.push(uiServices.events.turns.on('TURN_COMPLETED', () => {
656
- // Auto-save after every LLM turn so kills don't lose the session
657
- try {
658
- const snapshot = buildCurrentSessionSnapshot();
659
- persistConversation(
660
- runtime.sessionId,
661
- snapshot,
662
- runtime.model,
663
- runtime.provider,
664
- conversation.title || '',
665
- { workingDirectory: workingDir, homeDirectory, sessionManager: ctx.services.sessionManager },
666
- );
667
- hookDispatcher.fire({ path: 'Lifecycle:session:save' as HookEventPath, phase: 'Lifecycle' as HookPhase, category: 'session' as HookCategory, specific: 'save', sessionId: runtime.sessionId, timestamp: Date.now(), payload: { sessionId: runtime.sessionId } }).catch((err: unknown) => logger.debug('hook fire error', { error: summarizeError(err) }));
668
- } catch (e) { logger.debug('auto-save on turn:complete failed', { error: summarizeError(e) }); }
669
- }));
670
-
671
- unsubs.push(uiServices.events.turns.on('STREAM_START', () => {
672
- streamStartTime = Date.now();
673
- streamDeltaCount = 0;
674
- streamTokenSpeed = 0;
675
- }));
676
- unsubs.push(uiServices.events.turns.on('STREAM_DELTA', () => {
677
- streamDeltaCount++;
678
- const elapsed = (Date.now() - streamStartTime) / 1000;
679
- // Note: counts stream deltas, not actual tokens. ~1 delta per token for most providers.
680
- streamTokenSpeed = elapsed > 0 ? streamDeltaCount / elapsed : 0;
681
- }));
682
-
683
699
  stdin.setRawMode(true);
684
700
  stdin.resume();
685
701
  stdin.setEncoding('utf8');
@@ -721,25 +737,48 @@ async function main() {
721
737
  conversation.rebuildHistory();
722
738
  render();
723
739
 
724
- const recoveryInfo = checkRecoveryFile({ workingDirectory: workingDir, homeDirectory });
725
- if (recoveryInfo) {
726
- systemMessageRouter.high(`[Recovery] Found unsaved session from ${new Date(recoveryInfo.timestamp).toLocaleString()}. Title: "${recoveryInfo.title}". Press Ctrl+R to restore, Esc to discard, or start typing to ignore it.`);
727
- for (const line of formatReturnContextForDisplay(recoveryInfo.returnContext)) {
728
- systemMessageRouter.low(`[Recovery] ${line}`);
729
- }
730
- render();
731
- recoveryPending = true;
732
- }
740
+ // Async GPU probe runs off the render frame — nvidia-smi result will populate
741
+ // the module cache and appear on the next render cycle after it completes.
742
+ startHardwareProbe();
733
743
 
734
- recoveryInterval = setInterval(() => {
735
- const snapshot = buildCurrentSessionSnapshot();
736
- writeRecoveryFile(
737
- snapshot,
738
- runtime.sessionId,
739
- conversation.title ?? '',
740
- { workingDirectory: workingDir, homeDirectory },
741
- );
742
- }, 60_000);
744
+ // Away digest runs after the first render so it lands as ambient context,
745
+ // never a startup blocker.
746
+ autonomy.announceAwayDigest();
747
+
748
+ // If setup is in-progress (user has opened /agent but not finished), show a
749
+ // gentle plain-language reminder and point them back to /agent.
750
+ wireSetupIncompleteHint({
751
+ shellPaths: ctx.services.shellPaths,
752
+ providerReady: (() => {
753
+ try { return Boolean(providerRegistry.getCurrentModel()?.id); } catch { return false; }
754
+ })(),
755
+ // localReady mirrors the 'local-model-readiness' plan item from buildSetupPlan:
756
+ // cookbook status === 'detected-local-route'. Best-effort — never blocks render.
757
+ localReady: (() => {
758
+ try { return localModelSetupStatus(localModelCookbook(commandContext, false) as Record<string, unknown>) === 'ready'; } catch { return false; }
759
+ })(),
760
+ // hostReady is intentionally omitted: at startup the external-services stub
761
+ // always reports mode='external', which would produce a misleading 'active'
762
+ // line before any real probe has run. Omitting it keeps the hint honest.
763
+ systemMessageRouter,
764
+ });
765
+
766
+ // Wire streaming-speed metrics, auto-save, and recovery — all run after the
767
+ // first render so they land as ambient context, never startup blockers.
768
+ ({ recoveryInterval, recoveryPending } = wireSessionPersistenceAndRecovery({
769
+ buildCurrentSessionSnapshot,
770
+ runtime,
771
+ conversation,
772
+ workingDir,
773
+ homeDirectory,
774
+ systemMessageRouter,
775
+ render,
776
+ unsubs,
777
+ uiServicesTurns: uiServices.events.turns,
778
+ hookDispatcher,
779
+ sessionManager: ctx.services.sessionManager,
780
+ onStreamSpeedUpdate: (speed) => { streamTokenSpeed = speed; },
781
+ }));
743
782
  }
744
783
  main().catch((err: unknown) => {
745
784
  const detail = formatFatalStartupErrorForLog(err);
@@ -758,4 +797,4 @@ main().catch((err: unknown) => {
758
797
  // Ignore secondary stderr failures during process teardown.
759
798
  }
760
799
  process.exit(1);
761
- });
800
+ });
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Activity sidebar — the ambient right-hand surface of the shell.
3
+ *
4
+ * One glanceable column with at most four sections:
5
+ *
6
+ * Now what the assistant is doing this second (only while busy)
7
+ * Needs you approvals waiting on the user (only when non-empty)
8
+ * Coming up next scheduled work, when known (only when non-empty)
9
+ * Recent the activity feed, newest first
10
+ *
11
+ * Deliberately display-only: no focus, no tabs, no selection. Interactions
12
+ * stay in the conversation and the Agent workspace.
13
+ */
14
+
15
+ import type { Line } from '../types/grid.ts';
16
+ import { createEmptyLine } from '../types/grid.ts';
17
+ import type { ActivityEntry, ActivityKind } from '../core/activity-feed.ts';
18
+ import {
19
+ DEFAULT_PANEL_PALETTE,
20
+ buildPanelLine,
21
+ buildSectionHeader,
22
+ buildBodyText,
23
+ } from './polish.ts';
24
+ import { getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
25
+
26
+ export interface ActivitySidebarNow {
27
+ /** True while a turn is streaming or tools are running. */
28
+ readonly busy: boolean;
29
+ /** Short human label for the current work, e.g. "Searching the web…". */
30
+ readonly label?: string;
31
+ /** Background agents with their latest progress lines. */
32
+ readonly agents: ReadonlyArray<{ readonly label: string; readonly progress?: string }>;
33
+ /** Count of running background processes. */
34
+ readonly processes: number;
35
+ }
36
+
37
+ export interface ActivitySidebarView {
38
+ readonly now: ActivitySidebarNow;
39
+ /** Plain-language items waiting on the user (approvals, prompts). */
40
+ readonly needsYou: readonly string[];
41
+ /** Plain-language upcoming scheduled work, soonest first. */
42
+ readonly comingUp: readonly string[];
43
+ /** Activity feed entries, newest first. */
44
+ readonly recent: readonly ActivityEntry[];
45
+ }
46
+
47
+ const C = DEFAULT_PANEL_PALETTE;
48
+
49
+ const KIND_GLYPHS: Record<ActivityKind, string> = {
50
+ status: '·',
51
+ tool: '·',
52
+ agent: '»',
53
+ schedule: '◷',
54
+ delivery: '↗',
55
+ security: '!',
56
+ system: '·',
57
+ };
58
+
59
+ const KIND_COLORS: Record<ActivityKind, string> = {
60
+ status: C.dim,
61
+ tool: C.dim,
62
+ agent: C.info,
63
+ schedule: C.accent,
64
+ delivery: C.good,
65
+ security: C.warn,
66
+ system: C.dim,
67
+ };
68
+
69
+ function fmtClock(at: number): string {
70
+ const d = new Date(at);
71
+ return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
72
+ }
73
+
74
+ function entryLine(width: number, entry: ActivityEntry): Line {
75
+ const time = fmtClock(entry.at);
76
+ const glyph = KIND_GLYPHS[entry.kind];
77
+ const color = KIND_COLORS[entry.kind];
78
+ // Strip the leading "[Tag]" — the glyph and color already carry the kind,
79
+ // and horizontal space is the scarcest resource in the sidebar.
80
+ const text = entry.text.replace(/^\[[^\]]+\]\s*/, '');
81
+ const room = Math.max(4, width - time.length - 5);
82
+ return buildPanelLine(width, [
83
+ [` ${time} `, C.dim],
84
+ [`${glyph} `, color],
85
+ [truncateDisplay(text, room), entry.priority === 'high' ? C.value : C.dim],
86
+ ]);
87
+ }
88
+
89
+ function wrappedItemLines(width: number, text: string, fg: string, bullet: string, bulletFg: string): Line[] {
90
+ const bodyWidth = Math.max(4, width - 3);
91
+ const body = buildBodyText(bodyWidth, text, C, fg);
92
+ return body.map((line, index) => {
93
+ const prefix = index === 0 ? ` ${bullet} ` : ' ';
94
+ const cells = buildPanelLine(width, [[prefix, bulletFg]]);
95
+ for (let i = 0; i < bodyWidth; i++) {
96
+ const cell = line[i];
97
+ if (cell !== undefined && 3 + i < width) cells[3 + i] = cell;
98
+ }
99
+ return cells;
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Render the sidebar to exactly `height` lines of exactly `width` cells.
105
+ */
106
+ export function buildActivitySidebarLines(
107
+ view: ActivitySidebarView,
108
+ width: number,
109
+ height: number,
110
+ ): Line[] {
111
+ const lines: Line[] = [];
112
+ const push = (line: Line) => {
113
+ if (lines.length < height) lines.push(line);
114
+ };
115
+ const blank = () => push(createEmptyLine(width));
116
+
117
+ // ── Now ──
118
+ if (view.now.busy || view.now.agents.length > 0 || view.now.processes > 0) {
119
+ push(buildSectionHeader(width, 'Now', C));
120
+ if (view.now.busy) {
121
+ push(buildPanelLine(width, [
122
+ [' ● ', C.info],
123
+ [truncateDisplay(view.now.label ?? 'Working…', Math.max(4, width - 4)), C.value],
124
+ ]));
125
+ }
126
+ for (const agent of view.now.agents.slice(0, 3)) {
127
+ const text = agent.progress ? `${agent.label} — ${agent.progress}` : agent.label;
128
+ push(buildPanelLine(width, [
129
+ [' » ', C.info],
130
+ [truncateDisplay(text, Math.max(4, width - 4)), C.dim],
131
+ ]));
132
+ }
133
+ if (view.now.processes > 0) {
134
+ push(buildPanelLine(width, [
135
+ [' ▸ ', C.dim],
136
+ [`${view.now.processes} background ${view.now.processes === 1 ? 'process' : 'processes'}`, C.dim],
137
+ ]));
138
+ }
139
+ blank();
140
+ }
141
+
142
+ // ── Needs you ──
143
+ if (view.needsYou.length > 0) {
144
+ push(buildSectionHeader(width, 'Needs you', C));
145
+ for (const item of view.needsYou.slice(0, 4)) {
146
+ for (const line of wrappedItemLines(width, item, C.value, '!', C.warn)) push(line);
147
+ }
148
+ blank();
149
+ }
150
+
151
+ // ── Coming up ──
152
+ if (view.comingUp.length > 0) {
153
+ push(buildSectionHeader(width, 'Coming up', C));
154
+ for (const item of view.comingUp.slice(0, 4)) {
155
+ for (const line of wrappedItemLines(width, item, C.dim, '◷', C.accent)) push(line);
156
+ }
157
+ blank();
158
+ }
159
+
160
+ // ── Recent ──
161
+ push(buildSectionHeader(width, 'Recent', C));
162
+ const remaining = Math.max(0, height - lines.length);
163
+ if (view.recent.length === 0) {
164
+ push(buildPanelLine(width, [[' Nothing yet — activity will show up here.', C.dim]]));
165
+ } else {
166
+ for (const entry of view.recent.slice(0, remaining)) {
167
+ push(entryLine(width, entry));
168
+ }
169
+ }
170
+
171
+ while (lines.length < height) lines.push(createEmptyLine(width));
172
+ return lines.slice(0, height);
173
+ }
174
+
175
+ /**
176
+ * Pick the sidebar width for a terminal width, or 0 when it should not render.
177
+ * The sidebar only earns its space on wide terminals; the conversation always
178
+ * keeps at least ~80 usable columns.
179
+ */
180
+ export function resolveActivitySidebarWidth(terminalWidth: number): number {
181
+ if (terminalWidth < 120) return 0;
182
+ return Math.min(44, Math.max(32, Math.floor(terminalWidth * 0.24)));
183
+ }
184
+
185
+ /** True when `getDisplayWidth` would matter; exported for tests. */
186
+ export const __test__ = { fmtClock, entryLine, KIND_GLYPHS, getDisplayWidth };
@@ -464,12 +464,6 @@ export function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspac
464
464
  ...promptReceiptTimelineLines(snapshot),
465
465
  { text: 'Context controls: prompt receipts, project files, one-file inspection, and VIBE.md review.', fg: PALETTE.good },
466
466
  );
467
- } else if (category.id === 'onboarding-automation') {
468
- base.push(
469
- { text: `Automation: ${snapshot.automationEnabled ? 'enabled' : 'disabled'}; max ${snapshot.automationMaxConcurrentRuns} concurrent; history ${snapshot.automationRunHistoryLimit}.`, fg: snapshot.automationEnabled ? PALETTE.good : PALETTE.muted },
470
- { text: `Timeout ${snapshot.automationDefaultTimeoutMs} ms; catch-up ${snapshot.automationCatchUpWindowMinutes} min; cooldown ${snapshot.automationFailureCooldownMs} ms.`, fg: PALETTE.info },
471
- { text: `Delete one-shot jobs after success: ${snapshot.automationDeleteAfterRun ? 'yes' : 'no'}.`, fg: snapshot.automationDeleteAfterRun ? PALETTE.info : PALETTE.muted },
472
- );
473
467
  } else if (category.id === 'research') {
474
468
  const runnerContract = snapshot.researchBrowserRunnerContract;
475
469
  const visualContract = snapshot.researchVisualReportContract;
@@ -500,31 +494,6 @@ export function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspac
500
494
  { text: 'Email/calendar: connector setup needed before inbox triage or agenda workflows are first-class.', fg: PALETTE.warn },
501
495
  { text: 'Model route: personal_ops action:"briefing|status|queue|intake|lane|read".', fg: PALETTE.muted },
502
496
  );
503
- } else if (category.id === 'artifacts') {
504
- const mediaReady = snapshot.voiceMediaReadiness.readyMediaProviderCount;
505
- base.push(
506
- { text: `Chat: ${snapshot.provider} / ${snapshot.modelDisplayName}; Knowledge: ${snapshot.knowledgeRoute}`, fg: PALETTE.info },
507
- { text: `Media: ${mediaReady}/${snapshot.mediaProviderCount} ready; generation ${snapshot.mediaGenerationProviderCount}.`, fg: mediaReady > 0 ? PALETTE.good : PALETTE.warn },
508
- reviewerHandoffArtifactLine(snapshot),
509
- { text: 'Files: attach, export, inspect, ingest reviewed sources, or generate media.', fg: PALETTE.good },
510
- { text: 'Knowledge ingest and media generation require explicit actions.', fg: PALETTE.warn },
511
- );
512
- } else if (category.id === 'channels') {
513
- const enabledCount = snapshot.channels.filter((channel) => channel.enabled).length;
514
- const readyCount = snapshot.channels.filter((channel) => channel.ready).length;
515
- const configuredDefaults = snapshot.channels.filter((channel) => channel.defaultTarget === 'configured').length;
516
- const guide = snapshot.channelSetupGuide;
517
- const currentGuideStep = guide.steps.find((step) => step.status === 'current') ?? null;
518
- base.push(
519
- { text: `API: ${snapshot.runtimeBaseUrl}`, fg: PALETTE.info },
520
- companionAccessLine(snapshot),
521
- { text: `Channels: ${readyCount}/${snapshot.channels.length} ready; ${enabledCount} enabled; ${configuredDefaults} target(s).`, fg: PALETTE.info },
522
- { text: `Setup guide: ${guide.progressLabel}; ${guide.currentChannelLabel ?? 'choose a channel'}.`, fg: guide.status === 'ready' ? PALETTE.good : PALETTE.warn },
523
- { text: `Next: ${currentGuideStep ? currentGuideStep.label : 'All enabled channels ready.'}`, fg: currentGuideStep ? PALETTE.warn : PALETTE.good },
524
- { text: 'Guide checks setup schema, accounts, allowlist policy, live status, and explicit test sends.', fg: PALETTE.good },
525
- { text: 'Triage: /channels triage shows blockers, delivery retries, surface messages, route bindings, and receipts.', fg: PALETTE.good },
526
- { text: 'Secrets hidden; sends require explicit action.', fg: PALETTE.warn },
527
- );
528
497
  } else if (category.id === 'knowledge') {
529
498
  base.push(
530
499
  { text: `Route: ${snapshot.knowledgeRoute}; isolation ${snapshot.knowledgeIsolation}.`, fg: PALETTE.info },
@@ -554,17 +523,6 @@ export function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspac
554
523
  { text: 'Add/update/reload and trust changes require confirmation.', fg: PALETTE.good },
555
524
  { text: 'Start: /mcp review, /mcp tools, /mcp config, Add MCP server.', fg: PALETTE.muted },
556
525
  );
557
- } else if (category.id === 'voice-media') {
558
- const readiness = snapshot.voiceMediaReadiness;
559
- base.push(
560
- { text: `Voice: ${readiness.readyVoiceProviderCount}/${snapshot.voiceProviderCount} ready; TTS ${snapshot.ttsProvider}; voice ${snapshot.ttsVoice}.`, fg: readiness.readyVoiceProviderCount > 0 ? PALETTE.good : PALETTE.warn },
561
- { text: `Media: ${readiness.readyMediaProviderCount}/${snapshot.mediaProviderCount} ready; generation ${snapshot.mediaGenerationProviderCount}.`, fg: readiness.readyMediaProviderCount > 0 ? PALETTE.good : PALETTE.warn },
562
- { text: `Browser: ${readiness.browserToolState}; public URL ${snapshot.browserToolPublicBaseUrl}.`, fg: snapshot.browserToolExposureEnabled ? PALETTE.warn : PALETTE.muted },
563
- { text: 'Model route: device action:"voice|status"; computer action:"plan|browser|open_browser".', fg: PALETTE.info },
564
- { text: 'Device map: pairing, mobile/PWA, notifications, browser/desktop, camera/location via device.', fg: PALETTE.info },
565
- { text: readiness.nextSteps[0] ? `Next: ${compactText(readiness.nextSteps[0])}` : 'Next: voice/media setup is ready.', fg: readiness.nextSteps.length > 0 ? PALETTE.info : PALETTE.good },
566
- { text: 'Secrets hidden; voice, browser, and media side effects require explicit action.', fg: PALETTE.warn },
567
- );
568
526
  } else if (category.id === 'profiles') {
569
527
  const defaultProfile = snapshot.selectedRuntimeProfile
570
528
  ? `${snapshot.selectedRuntimeProfile}${snapshot.selectedRuntimeProfileExists ? '' : ' (missing)'}`
@@ -642,6 +600,11 @@ export function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspac
642
600
  { text: 'Approval actions require id plus typed confirmation.', fg: PALETTE.warn },
643
601
  );
644
602
  } else if (category.id === 'automation') {
603
+ base.push(
604
+ { text: `Automation: ${snapshot.automationEnabled ? 'enabled' : 'disabled'}; max ${snapshot.automationMaxConcurrentRuns} concurrent; history ${snapshot.automationRunHistoryLimit}.`, fg: snapshot.automationEnabled ? PALETTE.good : PALETTE.muted },
605
+ { text: `Timeout ${snapshot.automationDefaultTimeoutMs} ms; catch-up ${snapshot.automationCatchUpWindowMinutes} min; cooldown ${snapshot.automationFailureCooldownMs} ms.`, fg: PALETTE.info },
606
+ { text: `Delete one-shot jobs after success: ${snapshot.automationDeleteAfterRun ? 'yes' : 'no'}.`, fg: snapshot.automationDeleteAfterRun ? PALETTE.info : PALETTE.muted },
607
+ );
645
608
  const ready = readyRoutineItems(snapshot);
646
609
  base.push(
647
610
  { text: `Automation: ${ready.length} schedule-ready routine(s); receipts ${snapshot.routineScheduleReceiptCount}.`, fg: ready.length > 0 ? PALETTE.good : PALETTE.warn },
@@ -650,12 +613,6 @@ export function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspac
650
613
  { text: 'Autonomy queue: review visible schedules, runs, receipts, and cancel routes first.', fg: PALETTE.good },
651
614
  { text: 'Reminders and routine promotion require confirmation.', fg: PALETTE.warn },
652
615
  );
653
- } else if (category.id === 'delegate') {
654
- base.push(
655
- { text: 'Build/fix/review work is handed to GoodVibes TUI.', fg: PALETTE.info },
656
- { text: `Delegated review policy: ${snapshot.delegatedReviewPolicy}`, fg: PALETTE.warn },
657
- { text: 'No coding-role Agent jobs are created here.', fg: PALETTE.good },
658
- );
659
616
  } else if (category.id === 'finish') {
660
617
  base.push(
661
618
  { text: 'Apply & close marks onboarding finished for this user.', fg: PALETTE.good },
@@ -22,7 +22,7 @@ export function safetyColor(action: AgentWorkspaceAction): string {
22
22
  export function actionResultColor(result: AgentWorkspaceActionResult): string {
23
23
  if (result.kind === 'blocked' || result.kind === 'error') return PALETTE.bad;
24
24
  if (result.kind === 'dispatched') return PALETTE.info;
25
- if (result.kind === 'refreshed') return PALETTE.good;
25
+ if (result.kind === 'refreshed' || result.kind === 'recap') return PALETTE.good;
26
26
  return PALETTE.muted;
27
27
  }
28
28