@pellux/goodvibes-agent 0.1.116 → 1.0.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 (285) hide show
  1. package/CHANGELOG.md +48 -661
  2. package/README.md +64 -57
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +199180 -192185
  5. package/docs/README.md +15 -8
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +59 -0
  8. package/docs/getting-started.md +61 -50
  9. package/docs/knowledge-artifacts-and-multimodal.md +91 -0
  10. package/docs/project-planning.md +79 -0
  11. package/docs/providers-and-routing.md +46 -0
  12. package/docs/release-and-publishing.md +48 -11
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +3 -6
  16. package/src/agent/behavior-discovery-summary.ts +1 -3
  17. package/src/agent/channel-delivery.ts +201 -0
  18. package/src/agent/media-generation.ts +159 -0
  19. package/src/agent/memory-prompt.ts +0 -1
  20. package/src/agent/note-registry.ts +329 -0
  21. package/src/agent/operator-actions.ts +343 -0
  22. package/src/agent/persona-discovery.ts +1 -3
  23. package/src/agent/persona-registry.ts +19 -16
  24. package/src/agent/record-labels.ts +107 -0
  25. package/src/agent/reminder-schedule-format.ts +33 -24
  26. package/src/agent/reminder-schedule.ts +27 -26
  27. package/src/agent/routine-registry.ts +54 -14
  28. package/src/agent/routine-schedule-args.ts +2 -1
  29. package/src/agent/routine-schedule-format.ts +77 -53
  30. package/src/agent/routine-schedule-promotion.ts +35 -33
  31. package/src/agent/routine-schedule-receipts.ts +29 -27
  32. package/src/agent/runtime-profile-starters.ts +5 -5
  33. package/src/agent/runtime-profile.ts +91 -12
  34. package/src/agent/skill-registry.ts +210 -32
  35. package/src/cli/agent-knowledge-args.ts +5 -1
  36. package/src/cli/agent-knowledge-command.ts +40 -34
  37. package/src/cli/agent-knowledge-format.ts +80 -67
  38. package/src/cli/agent-knowledge-methods.ts +1 -1
  39. package/src/cli/agent-knowledge-runtime.ts +32 -26
  40. package/src/cli/bundle-command.ts +14 -9
  41. package/src/cli/config-overrides.ts +37 -36
  42. package/src/cli/entrypoint.ts +16 -7
  43. package/src/cli/external-runtime.ts +10 -4
  44. package/src/cli/help.ts +54 -29
  45. package/src/cli/local-library-command.ts +203 -83
  46. package/src/cli/management-commands.ts +105 -68
  47. package/src/cli/management.ts +55 -74
  48. package/src/cli/memory-command.ts +66 -32
  49. package/src/cli/parser.ts +37 -3
  50. package/src/cli/profiles-command.ts +166 -31
  51. package/src/cli/provider-auth-routes.ts +2 -1
  52. package/src/cli/routines-command.ts +176 -40
  53. package/src/cli/service-posture.ts +16 -16
  54. package/src/cli/status.ts +56 -135
  55. package/src/core/conversation-message-snapshot.ts +131 -0
  56. package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
  57. package/src/input/agent-workspace-access-command-editors.ts +170 -0
  58. package/src/input/agent-workspace-activation.ts +35 -8
  59. package/src/input/agent-workspace-basic-command-editor-submission.ts +351 -94
  60. package/src/input/agent-workspace-basic-command-editors.ts +502 -9
  61. package/src/input/agent-workspace-categories.ts +423 -51
  62. package/src/input/agent-workspace-channel-command-editor-submission.ts +83 -0
  63. package/src/input/agent-workspace-channel-command-editors.ts +65 -0
  64. package/src/input/agent-workspace-channels.ts +35 -2
  65. package/src/input/agent-workspace-command-editor.ts +41 -3
  66. package/src/input/agent-workspace-config-reader.ts +16 -0
  67. package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
  68. package/src/input/agent-workspace-editors.ts +166 -1
  69. package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
  70. package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
  71. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  72. package/src/input/agent-workspace-learned-behavior.ts +67 -0
  73. package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
  74. package/src/input/agent-workspace-library-command-editors.ts +35 -0
  75. package/src/input/agent-workspace-local-operations.ts +218 -0
  76. package/src/input/agent-workspace-local-selection.ts +75 -0
  77. package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
  78. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  79. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  80. package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
  81. package/src/input/agent-workspace-memory-command-editors.ts +180 -0
  82. package/src/input/agent-workspace-memory-editor.ts +2 -2
  83. package/src/input/agent-workspace-navigation.ts +81 -0
  84. package/src/input/agent-workspace-notify-editor-submission.ts +121 -0
  85. package/src/input/agent-workspace-operations-command-editor-submission.ts +208 -0
  86. package/src/input/agent-workspace-operations-command-editors.ts +196 -0
  87. package/src/input/agent-workspace-panel-route.ts +43 -0
  88. package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
  89. package/src/input/agent-workspace-provider-command-editors.ts +93 -0
  90. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  91. package/src/input/agent-workspace-requirements.ts +11 -0
  92. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  93. package/src/input/agent-workspace-search.ts +169 -0
  94. package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
  95. package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
  96. package/src/input/agent-workspace-session-command-editors.ts +249 -0
  97. package/src/input/agent-workspace-setup.ts +37 -16
  98. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
  99. package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
  100. package/src/input/agent-workspace-snapshot.ts +162 -21
  101. package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
  102. package/src/input/agent-workspace-task-command-editors.ts +47 -0
  103. package/src/input/agent-workspace-token.ts +18 -2
  104. package/src/input/agent-workspace-types.ts +212 -4
  105. package/src/input/agent-workspace-voice-media.ts +4 -7
  106. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  107. package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
  108. package/src/input/agent-workspace.ts +178 -240
  109. package/src/input/command-registry.ts +5 -2
  110. package/src/input/commands/agent-runtime-profile-runtime.ts +126 -37
  111. package/src/input/commands/agent-skills-runtime.ts +149 -72
  112. package/src/input/commands/agent-workspace-runtime.ts +23 -6
  113. package/src/input/commands/brief-runtime.ts +55 -25
  114. package/src/input/commands/channels-runtime.ts +147 -33
  115. package/src/input/commands/compat-runtime.ts +32 -0
  116. package/src/input/commands/delegation-runtime.ts +29 -23
  117. package/src/input/commands/experience-runtime.ts +6 -4
  118. package/src/input/commands/guidance-runtime.ts +4 -4
  119. package/src/input/commands/health-runtime.ts +149 -126
  120. package/src/input/commands/knowledge.ts +82 -60
  121. package/src/input/commands/local-provider-runtime.ts +43 -21
  122. package/src/input/commands/local-runtime.ts +138 -16
  123. package/src/input/commands/local-setup-review.ts +10 -20
  124. package/src/input/commands/mcp-runtime.ts +63 -46
  125. package/src/input/commands/notify-runtime.ts +38 -9
  126. package/src/input/commands/operator-actions-runtime.ts +138 -0
  127. package/src/input/commands/operator-runtime.ts +6 -76
  128. package/src/input/commands/personas-runtime.ts +46 -31
  129. package/src/input/commands/planning-runtime.ts +1 -1
  130. package/src/input/commands/platform-access-runtime.ts +35 -98
  131. package/src/input/commands/product-runtime.ts +2 -2
  132. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  133. package/src/input/commands/qrcode-runtime.ts +50 -9
  134. package/src/input/commands/recall-bundle.ts +11 -11
  135. package/src/input/commands/recall-capture.ts +13 -11
  136. package/src/input/commands/recall-query.ts +29 -21
  137. package/src/input/commands/recall-review.ts +2 -1
  138. package/src/input/commands/routines-runtime.ts +99 -45
  139. package/src/input/commands/runtime-services.ts +1 -14
  140. package/src/input/commands/schedule-runtime.ts +33 -20
  141. package/src/input/commands/security-runtime.ts +6 -11
  142. package/src/input/commands/session-content.ts +80 -78
  143. package/src/input/commands/session-workflow.ts +134 -104
  144. package/src/input/commands/session.ts +3 -174
  145. package/src/input/commands/shell-core.ts +38 -17
  146. package/src/input/commands/subscription-runtime.ts +54 -185
  147. package/src/input/commands/support-bundle-runtime.ts +53 -0
  148. package/src/input/commands/tasks-runtime.ts +21 -21
  149. package/src/input/commands/work-plan-runtime.ts +33 -8
  150. package/src/input/commands.ts +6 -2
  151. package/src/input/feed-context-factory.ts +2 -2
  152. package/src/input/file-picker.ts +3 -2
  153. package/src/input/handler-command-route.ts +4 -7
  154. package/src/input/handler-content-actions.ts +89 -26
  155. package/src/input/handler-feed-routes.ts +19 -12
  156. package/src/input/handler-feed.ts +6 -5
  157. package/src/input/handler-interactions.ts +9 -36
  158. package/src/input/handler-modal-stack.ts +3 -3
  159. package/src/input/handler-onboarding.ts +52 -91
  160. package/src/input/handler-shortcuts.ts +16 -7
  161. package/src/input/handler.ts +50 -23
  162. package/src/input/input-history.ts +5 -6
  163. package/src/input/keybindings.ts +27 -16
  164. package/src/input/mcp-workspace.ts +90 -26
  165. package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
  166. package/src/input/onboarding/onboarding-wizard-apply.ts +62 -0
  167. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  168. package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
  169. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +223 -55
  170. package/src/input/onboarding/onboarding-wizard-steps.ts +59 -35
  171. package/src/input/onboarding/onboarding-wizard-types.ts +4 -1
  172. package/src/input/onboarding/onboarding-wizard.ts +87 -2
  173. package/src/input/profile-picker-modal.ts +31 -12
  174. package/src/input/session-picker-modal.ts +21 -4
  175. package/src/input/settings-modal-agent-policy.ts +6 -6
  176. package/src/input/settings-modal-behavior.ts +0 -3
  177. package/src/input/settings-modal-subscriptions.ts +3 -3
  178. package/src/input/settings-modal-types.ts +2 -13
  179. package/src/input/settings-modal.ts +20 -66
  180. package/src/input/submission-intent.ts +0 -1
  181. package/src/input/submission-router.ts +3 -3
  182. package/src/main.ts +18 -10
  183. package/src/panels/approval-panel.ts +8 -8
  184. package/src/panels/automation-control-panel.ts +4 -4
  185. package/src/panels/base-panel.ts +1 -1
  186. package/src/panels/builtin/agent.ts +1 -1
  187. package/src/panels/builtin/operations.ts +3 -12
  188. package/src/panels/builtin/session.ts +32 -24
  189. package/src/panels/builtin/shared.ts +7 -7
  190. package/src/panels/cost-tracker-panel.ts +1 -1
  191. package/src/panels/docs-panel.ts +5 -3
  192. package/src/panels/index.ts +0 -1
  193. package/src/panels/knowledge-panel.ts +6 -5
  194. package/src/panels/memory-panel.ts +7 -6
  195. package/src/panels/panel-list-panel.ts +36 -80
  196. package/src/panels/project-planning-panel.ts +19 -12
  197. package/src/panels/provider-account-snapshot.ts +51 -25
  198. package/src/panels/provider-accounts-panel.ts +33 -18
  199. package/src/panels/provider-health-domains.ts +48 -7
  200. package/src/panels/provider-health-panel.ts +5 -4
  201. package/src/panels/qr-panel.ts +44 -20
  202. package/src/panels/schedule-panel.ts +9 -17
  203. package/src/panels/security-panel.ts +8 -8
  204. package/src/panels/session-browser-panel.ts +10 -10
  205. package/src/panels/subscription-panel.ts +6 -6
  206. package/src/panels/system-messages-panel.ts +3 -3
  207. package/src/panels/tasks-panel.ts +21 -14
  208. package/src/panels/tool-inspector-panel.ts +19 -12
  209. package/src/panels/work-plan-panel.ts +5 -5
  210. package/src/planning/project-planning-coordinator.ts +3 -3
  211. package/src/provider-auth-route-display.ts +9 -0
  212. package/src/renderer/agent-workspace-style.ts +34 -0
  213. package/src/renderer/agent-workspace.ts +254 -57
  214. package/src/renderer/autocomplete-overlay.ts +25 -6
  215. package/src/renderer/block-actions.ts +1 -3
  216. package/src/renderer/bookmark-modal.ts +19 -4
  217. package/src/renderer/buffer.ts +4 -2
  218. package/src/renderer/context-inspector.ts +50 -13
  219. package/src/renderer/diff.ts +1 -1
  220. package/src/renderer/file-picker-overlay.ts +19 -6
  221. package/src/renderer/help-overlay.ts +112 -34
  222. package/src/renderer/history-search-overlay.ts +19 -4
  223. package/src/renderer/live-tail-modal.ts +27 -5
  224. package/src/renderer/mcp-workspace.ts +176 -43
  225. package/src/renderer/model-picker-overlay.ts +58 -3
  226. package/src/renderer/model-workspace.ts +104 -22
  227. package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
  228. package/src/renderer/process-modal.ts +27 -6
  229. package/src/renderer/profile-picker-modal.ts +21 -5
  230. package/src/renderer/search-overlay.ts +25 -5
  231. package/src/renderer/selection-modal-overlay.ts +46 -14
  232. package/src/renderer/session-picker-modal.ts +18 -1
  233. package/src/renderer/settings-modal-helpers.ts +2 -40
  234. package/src/renderer/settings-modal.ts +88 -55
  235. package/src/renderer/system-message.ts +1 -1
  236. package/src/renderer/tool-call.ts +20 -11
  237. package/src/runtime/agent-runtime-events.ts +129 -0
  238. package/src/runtime/bootstrap-command-context.ts +7 -1
  239. package/src/runtime/bootstrap-command-parts.ts +11 -8
  240. package/src/runtime/bootstrap-core.ts +23 -65
  241. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  242. package/src/runtime/bootstrap-shell.ts +46 -8
  243. package/src/runtime/bootstrap.ts +36 -46
  244. package/src/runtime/connected-host-auth.ts +47 -0
  245. package/src/runtime/diagnostics/panels/index.ts +2 -2
  246. package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
  247. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  248. package/src/runtime/index.ts +2 -5
  249. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  250. package/src/runtime/onboarding/apply.ts +288 -74
  251. package/src/runtime/onboarding/derivation.ts +10 -13
  252. package/src/runtime/onboarding/snapshot.ts +2 -17
  253. package/src/runtime/onboarding/types.ts +34 -20
  254. package/src/runtime/onboarding/verify.ts +105 -4
  255. package/src/runtime/operator-token-cleanup.ts +3 -3
  256. package/src/runtime/services.ts +95 -11
  257. package/src/runtime/store/selectors/index.ts +3 -3
  258. package/src/runtime/store/state.ts +1 -4
  259. package/src/runtime/surface-feature-flags.ts +41 -6
  260. package/src/runtime/ui-read-models.ts +3 -4
  261. package/src/runtime/ui-services.ts +6 -6
  262. package/src/shell/blocking-input.ts +2 -1
  263. package/src/shell/service-settings-sync.ts +7 -7
  264. package/src/shell/ui-openers.ts +17 -38
  265. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  266. package/src/tools/agent-channel-send-tool.ts +133 -0
  267. package/src/tools/agent-context-policy.ts +1 -1
  268. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  269. package/src/tools/agent-knowledge-tool.ts +170 -0
  270. package/src/tools/agent-local-registry-tool.ts +279 -73
  271. package/src/tools/agent-media-generate-tool.ts +133 -0
  272. package/src/tools/agent-notify-tool.ts +143 -0
  273. package/src/tools/agent-operator-action-tool.ts +137 -0
  274. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  275. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  276. package/src/tools/agent-tool-policy-guard.ts +13 -15
  277. package/src/tools/agent-work-plan-tool.ts +256 -0
  278. package/src/version.ts +1 -1
  279. package/docs/connected-services.md +0 -51
  280. package/src/cli/package-verification.ts +0 -274
  281. package/src/input/commands/policy-dispatch.ts +0 -339
  282. package/src/input/commands/policy.ts +0 -17
  283. package/src/panels/panel-picker.ts +0 -105
  284. package/src/panels/policy-panel.ts +0 -308
  285. package/src/renderer/panel-picker-overlay.ts +0 -202
@@ -1,11 +1,11 @@
1
1
  import type {
2
2
  AgentWorkspace,
3
3
  AgentWorkspaceAction,
4
- AgentWorkspaceActionResult,
5
4
  AgentWorkspaceCategory,
6
5
  AgentWorkspaceLocalEditor,
7
6
  AgentWorkspaceRuntimeSnapshot,
8
7
  } from '../input/agent-workspace.ts';
8
+ import { formatAgentRecordReviewState, formatAgentRecordSource } from '../agent/record-labels.ts';
9
9
  import type { Line } from '../types/grid.ts';
10
10
  import { wrapText } from '../utils/terminal-width.ts';
11
11
  import { GLYPHS } from './ui-primitives.ts';
@@ -17,20 +17,7 @@ import {
17
17
  WORKSPACE_PALETTE as PALETTE,
18
18
  type WorkspaceRow,
19
19
  } from './fullscreen-workspace.ts';
20
-
21
- function safetyColor(action: AgentWorkspaceAction): string {
22
- if (action.safety === 'safe') return PALETTE.good;
23
- if (action.safety === 'read-only') return PALETTE.info;
24
- if (action.safety === 'delegates') return PALETTE.warn;
25
- return PALETTE.bad;
26
- }
27
-
28
- function actionResultColor(result: AgentWorkspaceActionResult): string {
29
- if (result.kind === 'blocked' || result.kind === 'error') return PALETTE.bad;
30
- if (result.kind === 'dispatched') return PALETTE.info;
31
- if (result.kind === 'refreshed') return PALETTE.good;
32
- return PALETTE.muted;
33
- }
20
+ import { actionResultColor, safetyColor, setupStatusColor, type AgentWorkspaceContextLine as ContextLine } from './agent-workspace-style.ts';
34
21
 
35
22
  function buildLeftRows(workspace: AgentWorkspace, height: number): WorkspaceRow[] {
36
23
  const rows: WorkspaceRow[] = [];
@@ -75,15 +62,6 @@ function actionCommand(action: AgentWorkspaceAction): string {
75
62
  return action.command ?? '(guidance)';
76
63
  }
77
64
 
78
- type ContextLine = { readonly text: string; readonly fg?: string; readonly bold?: boolean; readonly dim?: boolean };
79
-
80
- function setupStatusColor(status: AgentWorkspaceRuntimeSnapshot['setupChecklist'][number]['status']): string {
81
- if (status === 'ready') return PALETTE.good;
82
- if (status === 'recommended') return PALETTE.warn;
83
- if (status === 'blocked') return PALETTE.bad;
84
- return PALETTE.muted;
85
- }
86
-
87
65
  function setupChecklistLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
88
66
  const readyCount = snapshot.setupChecklist.filter((item) => item.status === 'ready').length;
89
67
  const recommendedCount = snapshot.setupChecklist.filter((item) => item.status === 'recommended').length;
@@ -104,22 +82,60 @@ function setupChecklistLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLi
104
82
  return lines;
105
83
  }
106
84
 
107
- function discoverySummaryLine(label: string, summary: AgentWorkspaceRuntimeSnapshot['discoveredBehavior']['personas'], command: string): ContextLine[] {
85
+ function homeNextActionLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
86
+ const blocked = snapshot.setupChecklist.filter((item) => item.status === 'blocked');
87
+ const recommended = snapshot.setupChecklist.filter((item) => item.status === 'recommended');
88
+ const optional = snapshot.setupChecklist.filter((item) => item.status === 'optional');
89
+ const candidates = [...blocked, ...recommended, ...optional].slice(0, 5);
90
+ if (candidates.length === 0) {
91
+ return [
92
+ { text: 'Next Actions', fg: PALETTE.title, bold: true },
93
+ { text: 'Core setup is ready. Continue normal assistant work, review Knowledge sources, or tune local skills/routines as needed.', fg: PALETTE.good },
94
+ ];
95
+ }
96
+ const lines: ContextLine[] = [{ text: 'Next Actions', fg: PALETTE.title, bold: true }];
97
+ for (const item of candidates) {
98
+ const command = item.command ? ` -> ${item.command}` : '';
99
+ lines.push({
100
+ text: `${item.status.toUpperCase()} ${item.label}${command}`,
101
+ fg: setupStatusColor(item.status),
102
+ bold: item.status === 'blocked',
103
+ });
104
+ lines.push({ text: ` ${item.detail}`, fg: PALETTE.muted });
105
+ }
106
+ return lines;
107
+ }
108
+
109
+ function companionAccessLine(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine {
110
+ const access = snapshot.companionAccess;
111
+ const tokenState = access.tokenReadable
112
+ ? `ready sha256:${access.tokenFingerprint ?? 'unknown'}`
113
+ : access.tokenPresent
114
+ ? 'present but unreadable'
115
+ : 'missing';
116
+ const error = access.tokenError ? `; token read error ${access.tokenError}` : '';
117
+ return {
118
+ text: `Companion: ${access.surface}; token ${tokenState}; QR ${access.qrCommand}; manual token text hidden${error}.`,
119
+ fg: access.pairingReady ? PALETTE.good : PALETTE.warn,
120
+ };
121
+ }
122
+
123
+ function discoverySummaryLine(label: string, summary: AgentWorkspaceRuntimeSnapshot['discoveredBehavior']['personas'], actionLabel: string): ContextLine[] {
108
124
  if (summary.count === 0) return [];
109
125
  const names = summary.names.length > 0
110
126
  ? ` ${summary.names.join(', ')}${summary.count > summary.names.length ? `, +${summary.count - summary.names.length} more` : ''}.`
111
127
  : '';
112
128
  return [
113
129
  { text: `${label}: ${summary.count} discovered; project ${summary.projectLocalCount}; global ${summary.globalCount}.`, fg: PALETTE.info, bold: true },
114
- { text: ` ${command} to preview, then use the import form after review.${names}`, fg: PALETTE.muted },
130
+ { text: ` Choose ${actionLabel} to preview, then use the import form after review.${names}`, fg: PALETTE.muted },
115
131
  ];
116
132
  }
117
133
 
118
134
  function behaviorDiscoveryLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
119
135
  const lines: ContextLine[] = [
120
- ...discoverySummaryLine('Discovered personas', snapshot.discoveredBehavior.personas, '/personas discover'),
121
- ...discoverySummaryLine('Discovered skills', snapshot.discoveredBehavior.skills, '/agent-skills discover'),
122
- ...discoverySummaryLine('Discovered routines', snapshot.discoveredBehavior.routines, '/routines discover'),
136
+ ...discoverySummaryLine('Discovered personas', snapshot.discoveredBehavior.personas, 'Personas -> Discover persona files'),
137
+ ...discoverySummaryLine('Discovered skills', snapshot.discoveredBehavior.skills, 'Skills -> Discover skill files'),
138
+ ...discoverySummaryLine('Discovered routines', snapshot.discoveredBehavior.routines, 'Routines -> Discover routine files'),
123
139
  ];
124
140
  if (lines.length === 0) return [];
125
141
  return [
@@ -150,11 +166,15 @@ function localLibraryLines(
150
166
  item.enabled === true ? 'enabled' : item.enabled === false ? 'disabled' : '',
151
167
  item.scope && item.cls ? `${item.scope}/${item.cls}` : '',
152
168
  item.confidence !== undefined ? `${item.confidence}%` : '',
153
- item.reviewState,
169
+ item.requirementCount !== undefined && item.requirementCount > 0
170
+ ? (item.missingRequirementCount && item.missingRequirementCount > 0 ? `needs ${item.missingRequirementCount}/${item.requirementCount}` : `ready ${item.requirementCount}/${item.requirementCount}`)
171
+ : '',
172
+ formatAgentRecordReviewState(item.reviewState),
173
+ item.source ? `origin ${item.source}` : '',
154
174
  item.startCount !== undefined ? `starts ${item.startCount}` : '',
155
175
  ].filter(Boolean).join(' / ');
156
- const tags = item.tags.length > 0 ? ` tags=${item.tags.join(',')}` : '';
157
- const triggers = item.triggers.length > 0 ? ` triggers=${item.triggers.join(',')}` : '';
176
+ const tags = item.tags.length > 0 ? ` tags ${item.tags.join(',')}` : '';
177
+ const triggers = item.triggers.length > 0 ? ` triggers ${item.triggers.join(',')}` : '';
158
178
  const marker = selected ? `${GLYPHS.navigation.selected} ` : '';
159
179
  lines.push({
160
180
  text: `${marker}${item.id}: ${item.name} (${status})`,
@@ -162,6 +182,9 @@ function localLibraryLines(
162
182
  bold: selected || item.active === true,
163
183
  });
164
184
  lines.push({ text: ` ${item.description}${tags}${triggers}`, fg: PALETTE.muted });
185
+ if (item.missingRequirements && item.missingRequirements.length > 0) {
186
+ lines.push({ text: ` missing setup: ${item.missingRequirements.join(', ')}`, fg: PALETTE.warn });
187
+ }
165
188
  }
166
189
  if (items.length > 8) {
167
190
  lines.push({ text: `${items.length - 8} more item(s). Open the library command for the full list.`, fg: PALETTE.dim });
@@ -169,6 +192,86 @@ function localLibraryLines(
169
192
  return lines;
170
193
  }
171
194
 
195
+ type LocalRoutineItem = AgentWorkspaceRuntimeSnapshot['localRoutines'][number];
196
+
197
+ function readyRoutineItems(snapshot: AgentWorkspaceRuntimeSnapshot): readonly LocalRoutineItem[] {
198
+ return snapshot.localRoutines.filter((routine) =>
199
+ routine.enabled === true
200
+ && routine.reviewState === 'reviewed'
201
+ && (routine.missingRequirementCount ?? 0) === 0
202
+ );
203
+ }
204
+
205
+ function routinesNeedingSetup(snapshot: AgentWorkspaceRuntimeSnapshot): readonly LocalRoutineItem[] {
206
+ return snapshot.localRoutines.filter((routine) => (routine.missingRequirementCount ?? 0) > 0);
207
+ }
208
+
209
+ function routinesNeedingReview(snapshot: AgentWorkspaceRuntimeSnapshot): readonly LocalRoutineItem[] {
210
+ return snapshot.localRoutines.filter((routine) => routine.reviewState !== 'reviewed');
211
+ }
212
+
213
+ function routineNextActionLine(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine {
214
+ const discovered = snapshot.discoveredBehavior.routines.count;
215
+ const ready = readyRoutineItems(snapshot);
216
+ const needsSetup = routinesNeedingSetup(snapshot);
217
+ const needsReview = routinesNeedingReview(snapshot);
218
+
219
+ if (snapshot.localRoutineCount === 0 && discovered > 0) {
220
+ return { text: 'Next routine action: Discover routine files, preview one, then import it from this workspace.', fg: PALETTE.warn, bold: true };
221
+ }
222
+ if (snapshot.localRoutineCount === 0) {
223
+ return { text: 'Next routine action: Create routine for a repeatable main-conversation workflow.', fg: PALETTE.warn, bold: true };
224
+ }
225
+ if (needsSetup.length > 0) {
226
+ return { text: `Next routine action: Needs setup for ${needsSetup[0]?.id ?? 'a routine'} before it can be trusted for schedule promotion.`, fg: PALETTE.warn, bold: true };
227
+ }
228
+ if (needsReview.length > 0) {
229
+ return { text: `Next routine action: Review selected or inspect ${needsReview[0]?.id ?? 'a routine'} before schedule promotion.`, fg: PALETTE.warn, bold: true };
230
+ }
231
+ if (ready.length > 0 && snapshot.routineScheduleReceiptCount === 0) {
232
+ return { text: `Next routine action: Promote ${ready[0]?.id ?? 'a reviewed routine'} to a connected schedule when you have a real cadence.`, fg: PALETTE.good, bold: true };
233
+ }
234
+ return { text: 'Next routine action: Start selected in the main conversation, inspect receipts, or reconcile connected schedules.', fg: PALETTE.info, bold: true };
235
+ }
236
+
237
+ function routineScheduleReceiptLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
238
+ const latest = snapshot.latestRoutineScheduleReceipt;
239
+ const lines: ContextLine[] = [
240
+ {
241
+ text: `Promotion receipts: ${snapshot.routineScheduleReceiptCount}; created: ${snapshot.successfulRoutineScheduleReceiptCount}; failed: ${snapshot.failedRoutineScheduleReceiptCount}`,
242
+ fg: snapshot.failedRoutineScheduleReceiptCount > 0 ? PALETTE.warn : PALETTE.info,
243
+ },
244
+ ];
245
+ if (latest) {
246
+ lines.push({
247
+ text: `Latest receipt: ${latest.id} ${latest.status} routine=${latest.routineId} schedule="${latest.scheduleName}" ${latest.scheduleKind} ${latest.scheduleValue}`,
248
+ fg: latest.status === 'failed' ? PALETTE.warn : PALETTE.good,
249
+ });
250
+ } else {
251
+ lines.push({ text: 'No schedule promotion receipts yet. Confirm a routine promotion from Automation when ready.', fg: PALETTE.muted });
252
+ }
253
+ return lines;
254
+ }
255
+
256
+ function automationNextActionLine(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine {
257
+ const ready = readyRoutineItems(snapshot);
258
+ const needsSetup = routinesNeedingSetup(snapshot);
259
+ const needsReview = routinesNeedingReview(snapshot);
260
+ if (snapshot.routineScheduleReceiptCount > 0) {
261
+ return { text: 'Next automation action: Reconcile schedules to compare local receipts with the connected host.', fg: PALETTE.info, bold: true };
262
+ }
263
+ if (ready.length > 0) {
264
+ return { text: `Next automation action: Promote ${ready[0]?.id ?? 'a reviewed routine'} or create a one-off reminder.`, fg: PALETTE.good, bold: true };
265
+ }
266
+ if (needsSetup.length > 0) {
267
+ return { text: `Next automation action: Resolve routine setup gaps in ${needsSetup[0]?.id ?? 'Routines'} before promotion, or create a reminder.`, fg: PALETTE.warn, bold: true };
268
+ }
269
+ if (needsReview.length > 0) {
270
+ return { text: `Next automation action: Review ${needsReview[0]?.id ?? 'a routine'} in Routines before schedule promotion, or create a reminder.`, fg: PALETTE.warn, bold: true };
271
+ }
272
+ return { text: 'Next automation action: Create a reminder, or create/import a routine before recurring workflow promotion.', fg: PALETTE.warn, bold: true };
273
+ }
274
+
172
275
  function profileLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
173
276
  const lines: ContextLine[] = [
174
277
  { text: 'Agent Profiles', fg: PALETTE.title, bold: true },
@@ -179,8 +282,17 @@ function profileLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
179
282
  }
180
283
  for (const profile of snapshot.runtimeProfiles.slice(0, 6)) {
181
284
  const starter = profile.starterTemplateId ? ` starter=${profile.starterTemplateId}` : ' starter=none';
182
- const created = profile.createdAt ? ` created=${profile.createdAt.slice(0, 10)}` : '';
183
- lines.push({ text: `${profile.id}${starter}${created}`, fg: PALETTE.info, bold: profile.id === snapshot.activeRuntimeProfile });
285
+ const created = profile.createdAt ? ` created ${profile.createdAt.slice(0, 10)}` : '';
286
+ const states = [
287
+ profile.id === snapshot.activeRuntimeProfile ? 'active' : '',
288
+ profile.id === snapshot.selectedRuntimeProfile ? 'default' : '',
289
+ ].filter(Boolean).join(', ');
290
+ const stateText = states ? ` [${states}]` : '';
291
+ lines.push({
292
+ text: `${profile.id}${stateText}${starter}${created}`,
293
+ fg: profile.id === snapshot.selectedRuntimeProfile ? PALETTE.good : PALETTE.info,
294
+ bold: profile.id === snapshot.activeRuntimeProfile || profile.id === snapshot.selectedRuntimeProfile,
295
+ });
184
296
  lines.push({ text: ` home: ${profile.homeDirectory}`, fg: PALETTE.muted });
185
297
  }
186
298
  if (snapshot.runtimeProfiles.length > 6) {
@@ -194,8 +306,9 @@ function starterTemplateLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextL
194
306
  { text: 'Starter Templates', fg: PALETTE.title, bold: true },
195
307
  ];
196
308
  for (const template of snapshot.runtimeStarterTemplates.slice(0, 6)) {
309
+ const origin = template.source === 'local' ? 'Local' : formatAgentRecordSource(template.source);
197
310
  lines.push({
198
- text: `${template.id}: ${template.name} [${template.source}]`,
311
+ text: `${template.id}: ${template.name} [${origin}]`,
199
312
  fg: template.source === 'local' ? PALETTE.good : PALETTE.info,
200
313
  bold: template.id === 'research',
201
314
  });
@@ -217,7 +330,12 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
217
330
  base.push(
218
331
  { text: `Chat route: ${snapshot.provider} / ${snapshot.modelDisplayName}`, fg: PALETTE.info },
219
332
  { text: `Session: ${snapshot.sessionId}`, fg: PALETTE.muted },
220
- { text: `Policy: ${snapshot.executionPolicy}; WRFC ${snapshot.wrfcPolicy}`, fg: PALETTE.good },
333
+ { text: `Policy: ${snapshot.executionPolicy}; delegated review ${snapshot.delegatedReviewPolicy}`, fg: PALETTE.good },
334
+ { text: `Knowledge: ${snapshot.knowledgeRoute}; ${snapshot.knowledgeIsolation}; no fallback`, fg: PALETTE.good },
335
+ { text: `Local: ${snapshot.localMemoryCount} memory, ${snapshot.localNoteCount} notes, ${snapshot.localPersonaCount} personas, ${snapshot.localSkillCount} skills, ${snapshot.localRoutineCount} routines.`, fg: PALETTE.info },
336
+ { text: `Channels: ${snapshot.channels.filter((channel) => channel.ready).length}/${snapshot.channels.length} ready; MCP ${snapshot.mcpConnectedServerCount}/${snapshot.mcpServerCount} connected; voice/media ${snapshot.voiceProviderCount}/${snapshot.mediaProviderCount}.`, fg: PALETTE.info },
337
+ { text: '' },
338
+ ...homeNextActionLines(snapshot),
221
339
  );
222
340
  } else if (category.id === 'setup') {
223
341
  base.push(
@@ -229,6 +347,17 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
229
347
  { text: `Workspace: ${snapshot.workingDirectory}`, fg: PALETTE.muted },
230
348
  { text: `Home: ${snapshot.homeDirectory}`, fg: PALETTE.muted },
231
349
  );
350
+ } else if (category.id === 'artifacts') {
351
+ const mediaReady = snapshot.voiceMediaReadiness.readyMediaProviderCount;
352
+ base.push(
353
+ { text: `Chat route: ${snapshot.provider} / ${snapshot.modelDisplayName}`, fg: PALETTE.info },
354
+ { text: `Agent Knowledge route: ${snapshot.knowledgeRoute}`, fg: PALETTE.info },
355
+ { text: `Media providers: ${mediaReady}/${snapshot.mediaProviderCount} ready; generation-capable ${snapshot.mediaGenerationProviderCount}.`, fg: mediaReady > 0 ? PALETTE.good : PALETTE.warn },
356
+ { text: 'Use this area for concrete files and generated output: attach images, export transcripts, ingest reviewed sources, inspect source records, and generate media.', fg: PALETTE.good },
357
+ { text: 'Agent Knowledge ingest writes only to the isolated Agent segment. Default knowledge and non-Agent routes are not fallback storage.', fg: PALETTE.warn },
358
+ { text: 'Generated media is stored as artifacts and referenced by id; the TUI should not print inline base64.', fg: PALETTE.muted },
359
+ { text: `Workspace path: ${snapshot.workingDirectory}`, fg: PALETTE.muted },
360
+ );
232
361
  } else if (category.id === 'channels') {
233
362
  const enabledCount = snapshot.channels.filter((channel) => channel.enabled).length;
234
363
  const readyCount = snapshot.channels.filter((channel) => channel.ready).length;
@@ -236,16 +365,20 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
236
365
  const readyChannels = snapshot.channels.filter((channel) => channel.ready).map((channel) => channel.label);
237
366
  const needsTarget = snapshot.channels.filter((channel) => channel.setupState === 'needs-target');
238
367
  const needsConfig = snapshot.channels.filter((channel) => channel.setupState === 'needs-config');
368
+ const nextAttentionChannel = needsConfig[0] ?? needsTarget[0] ?? snapshot.channels.find((channel) => !channel.enabled);
239
369
  const disabledChannels = snapshot.channels.filter((channel) => !channel.enabled).map((channel) => channel.label);
240
370
  const disabledPreview = disabledChannels.slice(0, 6).join(', ');
241
371
  const disabledSuffix = disabledChannels.length > 6 ? `, +${disabledChannels.length - 6} more` : '';
242
372
  const orderedChannels = [
243
373
  ...snapshot.channels.filter((channel) => channel.enabled),
244
374
  ...snapshot.channels.filter((channel) => !channel.enabled),
245
- ].slice(0, 6);
375
+ ].slice(0, 3);
246
376
  base.push(
247
377
  { text: `GoodVibes API: ${snapshot.runtimeBaseUrl}`, fg: PALETTE.info },
378
+ companionAccessLine(snapshot),
248
379
  { text: `Readiness: ${readyCount}/${snapshot.channels.length} ready; ${enabledCount} enabled; ${configuredDefaults} default target(s) configured.`, fg: PALETTE.info },
380
+ { text: 'Setup path: pair companion -> inspect readiness -> review accounts/policies/status -> fix one channel -> add explicit notification target if needed.', fg: PALETTE.good },
381
+ { text: `Next channel action: ${nextAttentionChannel ? `${nextAttentionChannel.label} - ${nextAttentionChannel.nextStep}` : 'All enabled channels are ready; keep delivery explicit and review policies before sending.'}`, fg: nextAttentionChannel ? PALETTE.warn : PALETTE.good },
249
382
  { text: `Ready channels: ${readyChannels.join(', ') || 'none'}.`, fg: readyChannels.length > 0 ? PALETTE.good : PALETTE.warn },
250
383
  { text: `Needs default target: ${needsTarget.map((channel) => `${channel.label} -> ${channel.defaultTargetKeys.join('|')}`).join(', ') || 'none'}.`, fg: needsTarget.length > 0 ? PALETTE.warn : PALETTE.muted },
251
384
  { text: `Needs config: ${needsConfig.map((channel) => `${channel.label} -> ${channel.missingRequiredKeys.join('|')}`).join(', ') || 'none'}.`, fg: needsConfig.length > 0 ? PALETTE.warn : PALETTE.muted },
@@ -259,21 +392,30 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
259
392
  fg: channel.ready ? PALETTE.good : channel.enabled ? PALETTE.warn : PALETTE.dim,
260
393
  });
261
394
  }
262
- base.push({ text: 'Only config key names and readiness state are rendered here.', fg: PALETTE.muted });
263
- base.push({ text: 'Pairing: use /pair or /qrcode; inspect notification targets with /notify list and health with /health review.', fg: PALETTE.info });
264
395
  } else if (category.id === 'knowledge') {
265
396
  base.push(
266
397
  { text: `Route family: ${snapshot.knowledgeRoute}/{status,ask,search}`, fg: PALETTE.info },
267
- { text: `Isolation: ${snapshot.knowledgeIsolation}; no default Knowledge/Wiki or non-Agent fallback`, fg: PALETTE.good },
398
+ { text: `Isolation: ${snapshot.knowledgeIsolation}; no default knowledge or non-Agent fallback`, fg: PALETTE.good },
268
399
  { text: 'Ingest: URL, URL-list, and bookmark imports require explicit --yes and write only to Agent Knowledge.', fg: PALETTE.info },
269
400
  { text: 'Review: queue, issues, candidates, reports, reindex, and consolidation stay inside the Agent segment.', fg: PALETTE.muted },
270
401
  { text: 'Agent-owned content appears here only after explicit Agent knowledge ingestion.', fg: PALETTE.muted },
271
402
  );
403
+ } else if (category.id === 'research') {
404
+ base.push(
405
+ { text: `Chat route: ${snapshot.provider} / ${snapshot.modelDisplayName}`, fg: PALETTE.info },
406
+ { text: `Agent Knowledge route: ${snapshot.knowledgeRoute}`, fg: PALETTE.info },
407
+ { text: `Browser tools: ${snapshot.voiceMediaReadiness.browserToolState}; public base URL ${snapshot.browserToolPublicBaseUrl}.`, fg: snapshot.browserToolExposureEnabled ? PALETTE.warn : PALETTE.muted },
408
+ { text: snapshot.voiceMediaReadiness.browserToolNextStep, fg: PALETTE.muted },
409
+ { text: 'Research requests are submitted to the main conversation. Agent may use connected read-only web tools when the user asks.', fg: PALETTE.good },
410
+ { text: 'URL references and web research do not ingest into Agent Knowledge. Ingestion is a separate confirmed Agent Knowledge action.', fg: PALETTE.warn },
411
+ { text: 'Use Agent Knowledge ask/search first for known source-backed context; use web research for current or external sources.', fg: PALETTE.info },
412
+ { text: 'External sends, browser-side effects, package installs, and connected-host mutations are outside this read-only research lane.', fg: PALETTE.warn },
413
+ );
272
414
  } else if (category.id === 'tools') {
273
415
  base.push(
274
416
  { text: `MCP servers: ${snapshot.mcpConnectedServerCount}/${snapshot.mcpServerCount} connected; quarantined ${snapshot.mcpQuarantinedServerCount}; allow-all ${snapshot.mcpAllowAllServerCount}.`, fg: snapshot.mcpQuarantinedServerCount > 0 || snapshot.mcpAllowAllServerCount > 0 ? PALETTE.warn : PALETTE.info },
275
- { text: 'Open MCP workspace for live server status, tool inventory, config paths, and command previews.', fg: PALETTE.info },
276
- { text: 'Add/update requires typed confirmation and dispatches /mcp add ... --yes through the command router.', fg: PALETTE.good },
417
+ { text: 'Open MCP workspace for live server status, tool inventory, config paths, and confirmed add/remove/reload actions.', fg: PALETTE.info },
418
+ { text: 'Add/update requires typed confirmation and dispatches through the TUI command router.', fg: PALETTE.good },
277
419
  { text: 'Trust changes remain explicit; allow-all is kept behind the settings workspace.', fg: PALETTE.warn },
278
420
  { text: 'Useful first actions: /mcp review, /mcp tools, /mcp config, and Add MCP server.', fg: PALETTE.muted },
279
421
  { text: 'Normal assistant chat can use tools serially when policy allows; onboarding does not start hidden tool work.', fg: PALETTE.muted },
@@ -317,8 +459,12 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
317
459
  { text: 'Image input uses prompt attachments; media generation/provider setup stays behind explicit commands and configured providers.', fg: PALETTE.muted },
318
460
  );
319
461
  } else if (category.id === 'profiles') {
462
+ const defaultProfile = snapshot.selectedRuntimeProfile
463
+ ? `${snapshot.selectedRuntimeProfile}${snapshot.selectedRuntimeProfileExists ? '' : ' (missing)'}`
464
+ : '(base Agent home)';
320
465
  base.push(
321
466
  { text: `Active Agent profile: ${snapshot.activeRuntimeProfile}`, fg: PALETTE.info },
467
+ { text: `Default for next launch: ${defaultProfile}`, fg: snapshot.selectedRuntimeProfileExists || !snapshot.selectedRuntimeProfile ? PALETTE.info : PALETTE.warn },
322
468
  { text: `Agent profiles under this home: ${snapshot.runtimeProfileCount}`, fg: PALETTE.info },
323
469
  { text: `Starter templates: ${snapshot.runtimeStarterTemplateCount}; local custom: ${snapshot.localStarterTemplateCount}`, fg: PALETTE.info },
324
470
  { text: `Starter ids: ${snapshot.runtimeStarterTemplates.map((template) => template.id).join(', ') || 'none'}`, fg: PALETTE.info },
@@ -331,25 +477,35 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
331
477
  { text: '' },
332
478
  { text: 'Named Agent profiles isolate local config, sessions, memory, personas, skills, routines, setup, and bundles.', fg: PALETTE.good },
333
479
  { text: 'Starter authoring: browse, export, edit, import, and create Agent profiles from inside this workspace.', fg: PALETTE.info },
334
- { text: 'Connected GoodVibes services stay shared unless the owning host is configured separately.', fg: PALETTE.warn },
480
+ { text: 'The connected GoodVibes host stays shared unless that host is configured separately.', fg: PALETTE.warn },
335
481
  { text: 'Portable bundles require explicit export/import commands with real paths and --yes.', fg: PALETTE.muted },
336
482
  );
337
483
  } else if (category.id === 'memory') {
338
484
  base.push(
339
485
  { text: `Session memories: ${snapshot.sessionMemoryCount}`, fg: PALETTE.info },
340
486
  { text: `Agent memory: ${snapshot.localMemoryCount}; prompt-active: ${snapshot.localMemoryPromptActiveCount}; review queue: ${snapshot.localMemoryReviewQueueCount}`, fg: PALETTE.info },
487
+ { text: `Scratchpad notes: ${snapshot.localNoteCount}; review queue: ${snapshot.localNoteReviewQueueCount}`, fg: PALETTE.info },
341
488
  { text: `Local routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
342
489
  { text: `Local skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}; bundles: ${snapshot.localSkillBundleCount}; active skills: ${snapshot.activeSkillCount}`, fg: PALETTE.info },
343
490
  { text: `Local personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
344
- { text: 'Durable memory, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
491
+ { text: 'Durable memory, scratchpad notes, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
345
492
  { text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
346
493
  { text: '' },
347
494
  ...localLibraryLines('Agent Memory', snapshot.localMemories, 'No Agent memory yet. Create one here with Create memory.', workspace.selectedLocalLibraryItem('memory')?.id ?? null),
348
495
  );
496
+ } else if (category.id === 'notes') {
497
+ base.push(
498
+ { text: `Scratchpad notes: ${snapshot.localNoteCount}; review queue: ${snapshot.localNoteReviewQueueCount}`, fg: PALETTE.info },
499
+ { text: 'Notes are Agent-local working context for source triage, temporary decisions, and operator handoff.', fg: PALETTE.good },
500
+ { text: 'Notes do not become memory and are not ingested into Agent Knowledge unless the user takes a separate explicit action.', fg: PALETTE.warn },
501
+ { text: 'Use reviewed notes to prefill durable memory, skills, routines, or personas, or to decide that a reviewed source deserves Agent Knowledge ingest.', fg: PALETTE.muted },
502
+ { text: '' },
503
+ ...localLibraryLines('Scratchpad Notes', snapshot.localNotes, 'No local notes yet. Create one here with Create note.', workspace.selectedLocalLibraryItem('note')?.id ?? null),
504
+ );
349
505
  } else if (category.id === 'personas') {
350
506
  base.push(
351
507
  { text: `Personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
352
- { text: 'Personas are local behavior profiles for the serial main-conversation assistant, not spawned agents.', fg: PALETTE.good },
508
+ { text: 'Personas are local behavior profiles for the serial main-conversation assistant, not separate Agent jobs.', fg: PALETTE.good },
353
509
  { text: 'Use them for tone, role, domain constraints, tool posture, and repeatable operating preferences.', fg: PALETTE.muted },
354
510
  { text: '' },
355
511
  ...localLibraryLines('Persona Library', snapshot.localPersonas, 'No local personas yet. Create one here with Create persona.', workspace.selectedLocalLibraryItem('persona')?.id ?? null),
@@ -365,10 +521,16 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
365
521
  ...localLibraryLines('Skill Bundles', snapshot.localSkillBundles, 'No local skill bundles yet. Use Skill bundles and Create bundle after creating skills.', null),
366
522
  );
367
523
  } else if (category.id === 'routines') {
524
+ const ready = readyRoutineItems(snapshot);
525
+ const needsSetup = routinesNeedingSetup(snapshot);
526
+ const needsReview = routinesNeedingReview(snapshot);
368
527
  base.push(
369
528
  { text: `Routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
529
+ { text: `Schedule-ready routines: ${ready.length}; setup gaps: ${needsSetup.length}; review needed: ${needsReview.length}`, fg: needsSetup.length > 0 || needsReview.length > 0 ? PALETTE.warn : PALETTE.good },
370
530
  { text: 'Routines are repeatable main-conversation workflows. Starting one does not create hidden jobs.', fg: PALETTE.good },
371
531
  { text: 'Scheduling a reviewed routine is explicit and requires a confirmed schedule command.', fg: PALETTE.warn },
532
+ routineNextActionLine(snapshot),
533
+ ...routineScheduleReceiptLines(snapshot),
372
534
  { text: '' },
373
535
  ...localLibraryLines('Routine Library', snapshot.localRoutines, 'No local routines yet. Create one here with Create routine.', workspace.selectedLocalLibraryItem('routine')?.id ?? null),
374
536
  );
@@ -376,18 +538,26 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
376
538
  base.push(
377
539
  { text: 'Work plan and approvals are read or explicitly confirmed through public operator routes.', fg: PALETTE.info },
378
540
  { text: 'This workspace does not approve, deny, cancel, or mutate requests by selection alone.', fg: PALETTE.good },
541
+ { text: 'Approve, deny, and cancel forms require an approval id and typed confirmation.', fg: PALETTE.warn },
379
542
  );
380
543
  } else if (category.id === 'automation') {
544
+ const ready = readyRoutineItems(snapshot);
381
545
  base.push(
382
- { text: 'Automation and schedules default to read-only observability.', fg: PALETTE.info },
546
+ { text: 'Automation and schedules default to read-only observability; side effects require confirmed forms.', fg: PALETTE.info },
383
547
  { text: 'Confirmed reminders and routine promotion use connected schedules only.', fg: PALETTE.info },
384
- { text: 'Local scheduler mutation/run controls remain blocked.', fg: PALETTE.warn },
548
+ { text: 'Local scheduler mutation controls remain blocked; job/run/schedule controls go through the connected host.', fg: PALETTE.warn },
549
+ { text: 'Reminder path: Create reminder -> choose at/every/cron -> optional delivery target -> confirm yes.', fg: PALETTE.good },
550
+ { text: 'Operator path: choose job/run/schedule action -> enter id -> type yes -> dispatch once.', fg: PALETTE.good },
551
+ { text: 'Routine path: Routines -> resolve setup -> review selected -> Promote routine -> Reconcile schedules.', fg: PALETTE.good },
552
+ { text: `Schedule-ready routines: ${ready.length}; local promotion receipts: ${snapshot.routineScheduleReceiptCount}`, fg: ready.length > 0 ? PALETTE.good : PALETTE.warn },
553
+ automationNextActionLine(snapshot),
554
+ ...routineScheduleReceiptLines(snapshot),
385
555
  );
386
556
  } else if (category.id === 'delegate') {
387
557
  base.push(
388
558
  { text: 'Build/fix/review work is handed to GoodVibes TUI/shared-session contracts.', fg: PALETTE.info },
389
- { text: `WRFC policy: ${snapshot.wrfcPolicy}`, fg: PALETTE.warn },
390
- { text: 'Agent does not spawn local Engineer/Reviewer/Tester roots.', fg: PALETTE.good },
559
+ { text: `Delegated review policy: ${snapshot.delegatedReviewPolicy}`, fg: PALETTE.warn },
560
+ { text: 'Agent does not create coding-role Agent jobs.', fg: PALETTE.good },
391
561
  );
392
562
  }
393
563
  if (snapshot.warnings.length > 0) {
@@ -421,6 +591,17 @@ function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCat
421
591
  { text: '' },
422
592
  { text: category.detail, fg: PALETTE.text },
423
593
  { text: '' },
594
+ ...(workspace.actionSearchActive ? [
595
+ { text: 'Action Search', fg: PALETTE.title, bold: true },
596
+ {
597
+ text: workspace.actionSearchQuery.length > 0
598
+ ? `Query: ${workspace.actionSearchQuery} (${workspace.actionSearchResults.length} result${workspace.actionSearchResults.length === 1 ? '' : 's'})`
599
+ : 'Type to search every Agent workspace action.',
600
+ fg: workspace.actionSearchQuery.length > 0 && workspace.actionSearchResults.length === 0 ? PALETTE.warn : PALETTE.info,
601
+ },
602
+ { text: 'Enter opens the selected result. Esc clears search and returns to normal workspace navigation.', fg: PALETTE.muted },
603
+ { text: '' },
604
+ ] satisfies ContextLine[] : []),
424
605
  ...(workspace.localEditor ? editorContextLines(workspace.localEditor) : []),
425
606
  ...(workspace.localEditor ? [{ text: '' }] : []),
426
607
  ...snapshotLines(workspace, category, workspace.runtimeSnapshot),
@@ -486,7 +667,9 @@ function buildEditorFieldRows(editor: AgentWorkspaceLocalEditor, index: number,
486
667
  const selected = index === editor.selectedFieldIndex;
487
668
  const marker = selected ? GLYPHS.navigation.selected : ' ';
488
669
  const required = field.required ? ' *' : '';
489
- const value = field.value.length > 0 ? field.value : '(empty)';
670
+ const value = field.value.length > 0
671
+ ? field.redact ? '*'.repeat(Math.min(12, Math.max(6, Array.from(field.value).length))) : field.value
672
+ : '(empty)';
490
673
  const color = selected ? PALETTE.text : field.value.length > 0 ? PALETTE.info : PALETTE.muted;
491
674
  const rows: WorkspaceRow[] = [{
492
675
  text: `${marker} ${field.label}${required}`,
@@ -511,23 +694,32 @@ function buildActionRows(workspace: AgentWorkspace, width: number, height: numbe
511
694
  const labelWidth = Math.min(28, Math.max(16, Math.floor(width * 0.30)));
512
695
  const safetyWidth = 10;
513
696
  const commandWidth = Math.max(10, width - labelWidth - safetyWidth - 9);
697
+ if (workspace.actionSearchActive) {
698
+ rows.push({
699
+ text: ` Search: ${workspace.actionSearchQuery || '(type to filter actions)'}`,
700
+ fg: workspace.actionSearchQuery.length > 0 && workspace.actions.length === 0 ? PALETTE.warn : PALETTE.info,
701
+ bold: true,
702
+ });
703
+ }
514
704
  rows.push({
515
- text: ` ${padDisplay('Action', labelWidth)} ${padDisplay('Safety', safetyWidth)} ${padDisplay('Command', commandWidth)}`,
705
+ text: ` ${padDisplay(workspace.actionSearchActive ? 'Result' : 'Action', labelWidth)} ${padDisplay('Safety', safetyWidth)} ${padDisplay('Command', commandWidth)}`,
516
706
  fg: PALETTE.muted,
517
707
  bold: true,
518
708
  });
519
709
 
520
710
  const actions = workspace.actions;
521
- const visible = Math.max(1, height - 2);
711
+ const visible = Math.max(1, height - (workspace.actionSearchActive ? 3 : 2));
522
712
  const window = stableWindow(actions.length, workspace.selectedActionIndex, visible);
523
713
  if (window.start > 0) rows.push({ text: `${GLYPHS.navigation.moreAbove} ${window.start} more action(s) above`, kind: 'more', fg: PALETTE.dim, dim: true });
524
714
 
525
715
  for (let index = window.start; index < window.end; index += 1) {
526
716
  const action = actions[index]!;
527
717
  const selected = index === workspace.selectedActionIndex;
718
+ const searchResult = workspace.actionSearchActive ? workspace.actionSearchResults[index] : null;
719
+ const label = searchResult ? `${searchResult.category.label} / ${action.label}` : action.label;
528
720
  const marker = selected ? GLYPHS.navigation.selected : ' ';
529
721
  rows.push({
530
- text: `${marker} ${padDisplay(action.label, labelWidth)} ${padDisplay(action.safety, safetyWidth)} ${padDisplay(actionCommand(action), commandWidth)}`,
722
+ text: `${marker} ${padDisplay(label, labelWidth)} ${padDisplay(action.safety, safetyWidth)} ${padDisplay(actionCommand(action), commandWidth)}`,
531
723
  selected: selected && workspace.focusPane === 'actions',
532
724
  fg: safetyColor(action),
533
725
  bold: selected,
@@ -556,12 +748,15 @@ function footerText(workspace: AgentWorkspace): string {
556
748
  if (workspace.localEditor) {
557
749
  return `Agent workspace · editing ${workspace.localEditor.kind} · Enter next/save · Ctrl-J newline · Esc cancel`;
558
750
  }
751
+ if (workspace.actionSearchActive) {
752
+ return 'Agent workspace · action search · type filter · Up/Down results · Enter open · Esc clear';
753
+ }
559
754
  const focus = workspace.focusPane === 'categories' ? 'categories' : 'actions';
560
- return `Agent workspace · focus ${focus} · Up/Down navigate · Left/Right pane · Enter open/action · R refresh · Esc close`;
755
+ return `Agent workspace · ${focus} · / search · Up/Down · Left/Right · Ctrl+[/] area · Enter open/action · R refresh · Esc close`;
561
756
  }
562
757
 
563
758
  export function renderAgentWorkspace(workspace: AgentWorkspace, width: number, height: number): Line[] {
564
- const category = workspace.selectedCategory;
759
+ const category = workspace.selectedActionCategory;
565
760
  const action = workspace.selectedAction;
566
761
  const setupCategory = category.id === 'setup';
567
762
  const layoutOptions = {
@@ -577,9 +772,11 @@ export function renderAgentWorkspace(workspace: AgentWorkspace, width: number, h
577
772
  width,
578
773
  height,
579
774
  title: 'GoodVibes Agent / Operator Workspace',
580
- stateLabel: workspace.localEditor ? 'Editor' : workspace.focusPane === 'categories' ? 'Categories' : 'Actions',
775
+ stateLabel: workspace.localEditor ? 'Editor' : workspace.actionSearchActive ? 'Search' : workspace.focusPane === 'categories' ? 'Categories' : 'Actions',
581
776
  leftHeader: 'Operator Areas',
582
- mainHeader: `${category.label} · ${category.actions.length} action(s)`,
777
+ mainHeader: workspace.actionSearchActive
778
+ ? `Search actions · ${workspace.actions.length} result(s)`
779
+ : `${category.label} · ${category.actions.length} action(s)`,
583
780
  leftRows: buildLeftRows(workspace, metrics.bodyRows),
584
781
  contextRows: buildContextRows(workspace, category, action, metrics.contextWidth),
585
782
  controlRows: buildActionRows(workspace, metrics.contextWidth, metrics.controlRows),
@@ -15,6 +15,9 @@ const TITLE_FG = DEFAULT_OVERLAY_PALETTE.titleFg;
15
15
  const BODY_FG = DEFAULT_OVERLAY_PALETTE.bodyFg;
16
16
  const MUTED_FG = DEFAULT_OVERLAY_PALETTE.mutedFg;
17
17
  const SELECTED_BG = DEFAULT_OVERLAY_PALETTE.selectedBg;
18
+ const AUTOCOMPLETE_TITLE = ' Commands';
19
+ const AUTOCOMPLETE_EMPTY_QUERY = '/';
20
+ const AUTOCOMPLETE_HINTS = '[Tab] Complete [Up/Down] Navigate [Enter] Execute [Esc] Cancel';
18
21
 
19
22
  interface CellStyle {
20
23
  fg: string;
@@ -27,6 +30,23 @@ function putText(line: Line, startX: number, maxWidth: number, text: string, sty
27
30
  putOverlayText(line, startX, maxWidth, text, style);
28
31
  }
29
32
 
33
+ function autocompleteQueryText(query: string): string {
34
+ return query ? `/${query}` : AUTOCOMPLETE_EMPTY_QUERY;
35
+ }
36
+
37
+ function autocompleteScrollText(selected: string | number, total: string | number): string {
38
+ return `${selected}/${total}`;
39
+ }
40
+
41
+ export function renderAutocompletePackageText(): string {
42
+ return [
43
+ AUTOCOMPLETE_TITLE.trim(),
44
+ autocompleteQueryText('<query>'),
45
+ autocompleteScrollText('<selected>', '<total>'),
46
+ AUTOCOMPLETE_HINTS,
47
+ ].join('\n');
48
+ }
49
+
30
50
  /**
31
51
  * Render the slash command autocomplete dropdown as Line[] for overlay in the viewport.
32
52
  */
@@ -51,8 +71,8 @@ export function renderAutocompleteOverlay(
51
71
  lines.push(createOverlayBorderLine(width, layout, '┌', '─', '┐', BORDER_FG));
52
72
 
53
73
  const titleLine = createOverlayContentLine(width, layout);
54
- const titleText = ' Commands';
55
- const queryText = state.query ? `/${state.query}` : '/';
74
+ const titleText = AUTOCOMPLETE_TITLE;
75
+ const queryText = autocompleteQueryText(state.query);
56
76
  const queryWidth = Math.min(Math.floor(layout.innerWidth / 2), Math.max(8, layout.innerWidth - getDisplayWidth(titleText) - 2));
57
77
  const leftText = fitDisplay(titleText, Math.max(0, layout.innerWidth - queryWidth));
58
78
  const rightText = truncateDisplay(queryText, queryWidth);
@@ -126,8 +146,8 @@ export function renderAutocompleteOverlay(
126
146
  }
127
147
 
128
148
  if (total > maxVisible) {
129
- const scrollLine = createOverlayContentLine(width, layout);
130
- const scrollText = `${state.selectedIndex + 1}/${total}`;
149
+ const scrollLine = createOverlayContentLine(width, layout);
150
+ const scrollText = autocompleteScrollText(state.selectedIndex + 1, total);
131
151
  putText(
132
152
  scrollLine,
133
153
  layout.margin + 2 + Math.max(0, layout.innerWidth - getDisplayWidth(scrollText)),
@@ -139,12 +159,11 @@ export function renderAutocompleteOverlay(
139
159
  }
140
160
 
141
161
  const footerLine = createOverlayContentLine(width, layout);
142
- const hints = '[Tab] Complete [Up/Down] Navigate [Enter] Execute [Esc] Cancel';
143
162
  putText(
144
163
  footerLine,
145
164
  layout.margin + 2,
146
165
  layout.innerWidth,
147
- fitDisplay(truncateDisplay(hints, layout.innerWidth), layout.innerWidth),
166
+ fitDisplay(truncateDisplay(AUTOCOMPLETE_HINTS, layout.innerWidth), layout.innerWidth),
148
167
  { fg: MUTED_FG, dim: true },
149
168
  );
150
169
  lines.push(footerLine);
@@ -1,6 +1,6 @@
1
1
  import type { BlockMeta } from '../core/conversation';
2
2
 
3
- export type BlockActionId = 'copy' | 'bookmark' | 'toggle' | 'apply' | 'rerun';
3
+ export type BlockActionId = 'copy' | 'bookmark' | 'toggle' | 'rerun';
4
4
 
5
5
  export interface BlockAction {
6
6
  id: BlockActionId;
@@ -12,7 +12,6 @@ const ALL_ACTIONS: BlockAction[] = [
12
12
  { id: 'copy', label: 'Copy', key: 'c' },
13
13
  { id: 'bookmark', label: 'Bookmark', key: 'b' },
14
14
  { id: 'toggle', label: 'Collapse/Expand',key: 'Tab' },
15
- { id: 'apply', label: 'Diff apply blocked', key: 'a' },
16
15
  { id: 'rerun', label: 'Re-run tool', key: 'r' },
17
16
  ];
18
17
 
@@ -36,7 +35,6 @@ export class BlockActionsMenu {
36
35
  open(block: BlockMeta): void {
37
36
  this.block = block;
38
37
  this.actions = ALL_ACTIONS.filter(a => {
39
- if (a.id === 'apply') return block.type === 'diff';
40
38
  if (a.id === 'rerun') return block.type === 'tool';
41
39
  return true;
42
40
  });