@pellux/goodvibes-agent 1.4.3 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +7 -7
  3. package/dist/package/main.js +7450 -12285
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +1 -1
  6. package/docs/getting-started.md +2 -2
  7. package/docs/release-and-publishing.md +1 -1
  8. package/docs/tools-and-commands.md +5 -5
  9. package/package.json +4 -2
  10. package/release/performance-snapshot.json +2 -2
  11. package/release/release-notes.md +11 -7
  12. package/release/release-readiness.json +6 -6
  13. package/src/agent/behavior-discovery-summary.ts +4 -18
  14. package/src/agent/calendar-registry.ts +322 -0
  15. package/src/agent/competitive-feature-inventory.ts +268 -130
  16. package/src/agent/document-registry.ts +5 -1
  17. package/src/agent/email/email-service.ts +350 -0
  18. package/src/agent/email/imap-client.ts +596 -0
  19. package/src/agent/email/smtp-client.ts +453 -0
  20. package/src/agent/ics-calendar.ts +662 -0
  21. package/src/agent/ics-timezone.ts +172 -0
  22. package/src/agent/markdown-frontmatter.ts +31 -0
  23. package/src/agent/memory-safety.ts +1 -1
  24. package/src/agent/note-registry.ts +3 -9
  25. package/src/agent/persona-discovery.ts +3 -15
  26. package/src/agent/persona-registry.ts +1 -10
  27. package/src/agent/research-source-registry.ts +5 -1
  28. package/src/agent/routine-discovery.ts +3 -15
  29. package/src/agent/runtime-profile.ts +3 -0
  30. package/src/agent/skill-discovery.ts +3 -15
  31. package/src/agent/skill-draft-proposer.ts +203 -0
  32. package/src/agent/skill-draft-runner.ts +201 -0
  33. package/src/agent/skill-registry.ts +36 -1
  34. package/src/agent/skill-standard.ts +99 -0
  35. package/src/agent/vibe-file.ts +7 -22
  36. package/src/cli/completion.ts +1 -1
  37. package/src/cli/config-overrides.ts +10 -1
  38. package/src/cli/redaction.ts +17 -5
  39. package/src/config/provider-model.ts +2 -1
  40. package/src/config/secret-config.ts +13 -6
  41. package/src/core/activity-feed.ts +97 -0
  42. package/src/core/away-digest.ts +161 -0
  43. package/src/core/conversation-rendering.ts +7 -3
  44. package/src/core/conversation.ts +22 -15
  45. package/src/core/hardware-profile.ts +362 -0
  46. package/src/core/last-seen-store.ts +78 -0
  47. package/src/core/plain-language.ts +52 -0
  48. package/src/core/setup-incomplete-hint.ts +90 -0
  49. package/src/core/system-message-router.ts +38 -87
  50. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  51. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  52. package/src/input/agent-workspace-categories.ts +63 -158
  53. package/src/input/agent-workspace-command-editor.ts +4 -0
  54. package/src/input/agent-workspace-host-category.ts +0 -5
  55. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  56. package/src/input/agent-workspace-navigation.ts +10 -3
  57. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  58. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  59. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  60. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  61. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  62. package/src/input/agent-workspace-profile-editors.ts +155 -0
  63. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  64. package/src/input/agent-workspace-types.ts +5 -1
  65. package/src/input/agent-workspace.ts +65 -39
  66. package/src/input/command-registry.ts +18 -5
  67. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  68. package/src/input/commands/agent-skills-runtime.ts +60 -3
  69. package/src/input/commands/calendar-runtime.ts +209 -0
  70. package/src/input/commands/channels-runtime.ts +1 -0
  71. package/src/input/commands/command-error.ts +9 -0
  72. package/src/input/commands/compat-runtime.ts +1 -0
  73. package/src/input/commands/config.ts +1 -0
  74. package/src/input/commands/conversation-runtime.ts +1 -1
  75. package/src/input/commands/delegation-runtime.ts +5 -6
  76. package/src/input/commands/email-runtime.ts +387 -0
  77. package/src/input/commands/experience-runtime.ts +17 -4
  78. package/src/input/commands/guidance-runtime.ts +1 -1
  79. package/src/input/commands/health-runtime.ts +6 -1
  80. package/src/input/commands/knowledge-format.ts +73 -0
  81. package/src/input/commands/knowledge.ts +9 -74
  82. package/src/input/commands/local-provider-runtime.ts +2 -1
  83. package/src/input/commands/local-runtime.ts +10 -4
  84. package/src/input/commands/mcp-runtime.ts +7 -0
  85. package/src/input/commands/notify-runtime.ts +17 -1
  86. package/src/input/commands/onboarding-runtime.ts +1 -0
  87. package/src/input/commands/operator-actions-runtime.ts +1 -0
  88. package/src/input/commands/operator-runtime.ts +3 -0
  89. package/src/input/commands/personas-runtime.ts +1 -0
  90. package/src/input/commands/platform-access-runtime.ts +40 -17
  91. package/src/input/commands/product-runtime.ts +6 -1
  92. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  93. package/src/input/commands/qrcode-runtime.ts +1 -0
  94. package/src/input/commands/routines-runtime.ts +1 -0
  95. package/src/input/commands/runtime-services.ts +0 -13
  96. package/src/input/commands/security-runtime.ts +1 -0
  97. package/src/input/commands/session-content.ts +1 -0
  98. package/src/input/commands/shell-core.ts +5 -2
  99. package/src/input/commands/subscription-runtime.ts +33 -9
  100. package/src/input/commands/support-bundle-runtime.ts +8 -1
  101. package/src/input/commands/tasks-runtime.ts +1 -0
  102. package/src/input/commands/tts-runtime.ts +1 -0
  103. package/src/input/commands/vibe-runtime.ts +1 -0
  104. package/src/input/commands.ts +4 -0
  105. package/src/input/feed-context-factory.ts +3 -12
  106. package/src/input/handler-command-route.ts +7 -60
  107. package/src/input/handler-feed-routes.ts +0 -194
  108. package/src/input/handler-feed.ts +2 -54
  109. package/src/input/handler-interactions.ts +0 -2
  110. package/src/input/handler-modal-stack.ts +0 -9
  111. package/src/input/handler-picker-routes.ts +24 -1
  112. package/src/input/handler-shortcuts.ts +11 -40
  113. package/src/input/handler-ui-state.ts +13 -0
  114. package/src/input/handler.ts +8 -35
  115. package/src/input/keybindings.ts +40 -17
  116. package/src/input/model-picker-local-fit.ts +130 -0
  117. package/src/input/submission-router.ts +0 -5
  118. package/src/main.ts +111 -89
  119. package/src/renderer/activity-sidebar.ts +186 -0
  120. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  121. package/src/renderer/agent-workspace-style.ts +1 -1
  122. package/src/renderer/agent-workspace.ts +14 -2
  123. package/src/renderer/compositor.ts +37 -125
  124. package/src/renderer/conversation-overlays.ts +3 -3
  125. package/src/renderer/help-overlay.ts +7 -5
  126. package/src/renderer/model-workspace.ts +101 -86
  127. package/src/{panels → renderer}/polish.ts +3 -3
  128. package/src/renderer/shell-surface.ts +4 -5
  129. package/src/renderer/status-token.ts +31 -11
  130. package/src/renderer/tab-strip.ts +1 -1
  131. package/src/renderer/tool-call.ts +7 -8
  132. package/src/renderer/tool-labels.ts +92 -0
  133. package/src/renderer/ui-factory.ts +48 -96
  134. package/src/runtime/bootstrap-command-context.ts +0 -5
  135. package/src/runtime/bootstrap-command-parts.ts +6 -15
  136. package/src/runtime/bootstrap-core.ts +34 -13
  137. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  138. package/src/runtime/bootstrap-shell.ts +3 -50
  139. package/src/runtime/bootstrap.ts +3 -4
  140. package/src/runtime/context.ts +1 -1
  141. package/src/runtime/diagnostics/panels/index.ts +0 -1
  142. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  143. package/src/runtime/execution-ledger.ts +16 -1
  144. package/src/runtime/index.ts +6 -1
  145. package/src/runtime/onboarding/index.ts +1 -0
  146. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  147. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  148. package/src/runtime/services.ts +16 -4
  149. package/src/runtime/terminal-output-guard.ts +7 -0
  150. package/src/runtime/tool-permission-safety.ts +1 -1
  151. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  152. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  153. package/src/runtime/ui/model-picker/types.ts +2 -2
  154. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  155. package/src/runtime/ui/provider-health/types.ts +2 -2
  156. package/src/runtime/ui-services.ts +0 -2
  157. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  158. package/src/shell/autonomy-surfacing.ts +272 -0
  159. package/src/shell/session-continuity-hints.ts +0 -6
  160. package/src/shell/startup-wiring.ts +221 -0
  161. package/src/shell/ui-openers.ts +18 -29
  162. package/src/tools/agent-context-policy.ts +1 -1
  163. package/src/tools/agent-harness-background-processes.ts +4 -4
  164. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  165. package/src/tools/agent-harness-command-runner.ts +6 -1
  166. package/src/tools/agent-harness-document-ops.ts +26 -53
  167. package/src/tools/agent-harness-execution-history.ts +1 -13
  168. package/src/tools/agent-harness-execution-posture.ts +0 -15
  169. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  170. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  171. package/src/tools/agent-harness-metadata.ts +16 -0
  172. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  173. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  174. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  175. package/src/tools/agent-harness-tool-schema.ts +13 -14
  176. package/src/tools/agent-harness-tool.ts +27 -23
  177. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  178. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  179. package/src/tools/agent-workspace-tool.ts +2 -33
  180. package/src/utils/terminal-width.ts +9 -3
  181. package/src/version.ts +1 -1
  182. package/src/input/agent-workspace-panel-route.ts +0 -44
  183. package/src/input/panel-integration-actions.ts +0 -26
  184. package/src/panels/approval-panel.ts +0 -149
  185. package/src/panels/automation-control-panel.ts +0 -212
  186. package/src/panels/base-panel.ts +0 -254
  187. package/src/panels/builtin/agent.ts +0 -58
  188. package/src/panels/builtin/knowledge.ts +0 -26
  189. package/src/panels/builtin/operations.ts +0 -121
  190. package/src/panels/builtin/session.ts +0 -138
  191. package/src/panels/builtin/shared.ts +0 -275
  192. package/src/panels/builtin/usage.ts +0 -21
  193. package/src/panels/builtin-panels.ts +0 -23
  194. package/src/panels/confirm-state.ts +0 -61
  195. package/src/panels/context-visualizer-panel.ts +0 -204
  196. package/src/panels/cost-tracker-panel.ts +0 -444
  197. package/src/panels/docs-panel.ts +0 -285
  198. package/src/panels/index.ts +0 -25
  199. package/src/panels/knowledge-panel.ts +0 -417
  200. package/src/panels/memory-panel.ts +0 -226
  201. package/src/panels/panel-list-panel.ts +0 -464
  202. package/src/panels/panel-manager.ts +0 -570
  203. package/src/panels/provider-accounts-panel.ts +0 -233
  204. package/src/panels/provider-health-domains.ts +0 -208
  205. package/src/panels/provider-health-panel.ts +0 -720
  206. package/src/panels/provider-health-tracker.ts +0 -115
  207. package/src/panels/provider-stats-panel.ts +0 -366
  208. package/src/panels/qr-panel.ts +0 -207
  209. package/src/panels/schedule-panel.ts +0 -321
  210. package/src/panels/scrollable-list-panel.ts +0 -491
  211. package/src/panels/search-focus.ts +0 -32
  212. package/src/panels/security-panel.ts +0 -295
  213. package/src/panels/session-browser-panel.ts +0 -395
  214. package/src/panels/session-maintenance.ts +0 -125
  215. package/src/panels/subscription-panel.ts +0 -263
  216. package/src/panels/system-messages-panel.ts +0 -230
  217. package/src/panels/tasks-panel.ts +0 -344
  218. package/src/panels/thinking-panel.ts +0 -304
  219. package/src/panels/token-budget-panel.ts +0 -475
  220. package/src/panels/tool-inspector-panel.ts +0 -436
  221. package/src/panels/types.ts +0 -54
  222. package/src/renderer/panel-composite.ts +0 -158
  223. package/src/renderer/panel-tab-bar.ts +0 -69
  224. package/src/renderer/panel-workspace-bar.ts +0 -42
  225. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  226. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  227. /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
@@ -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
 
@@ -158,6 +158,8 @@ function editorPurposeLabel(editorKind: AgentWorkspaceEditorKind): string {
158
158
  case 'skill-search': return 'Search skills';
159
159
  case 'skill-show': return 'Show skill';
160
160
  case 'skill-discovery-import': return 'Import skill files';
161
+ case 'skill-standard-import': return 'Import shared skill';
162
+ case 'skill-standard-export': return 'Export skill to share';
161
163
  case 'routine': return 'Edit routine';
162
164
  case 'routine-search': return 'Search routines';
163
165
  case 'routine-show': return 'Show routine';
@@ -669,8 +671,18 @@ function buildActionRows(workspace: AgentWorkspace, width: number, height: numbe
669
671
  if (workspace.lastActionResult) {
670
672
  rows.push({ text: '' });
671
673
  rows.push({ text: `${onboarding ? 'Result' : 'Action Result'}: ${workspace.lastActionResult.title}`, fg: actionResultColor(workspace.lastActionResult), bold: true });
672
- for (const line of wrapText(workspace.lastActionResult.detail, Math.max(1, width - 2))) {
673
- rows.push({ text: ` ${line}`, fg: PALETTE.text });
674
+ // For recap results, skip the detail body the checkmarked lines below carry
675
+ // the full content. Rendering detail AND lines would duplicate every line.
676
+ if (workspace.lastActionResult.kind !== 'recap') {
677
+ for (const line of wrapText(workspace.lastActionResult.detail, Math.max(1, width - 2))) {
678
+ rows.push({ text: ` ${line}`, fg: PALETTE.text });
679
+ }
680
+ }
681
+ if (workspace.lastActionResult.kind === 'recap' && workspace.lastActionResult.lines?.length) {
682
+ rows.push({ text: '' });
683
+ for (const line of workspace.lastActionResult.lines) {
684
+ rows.push({ text: ` ✔ ${line}`, fg: PALETTE.good });
685
+ }
674
686
  }
675
687
  if (!onboarding && workspace.lastActionResult.command) {
676
688
  rows.push({ text: ` Command: ${workspace.lastActionResult.command}`, fg: PALETTE.muted });
@@ -17,25 +17,9 @@ export interface SearchInfo {
17
17
  viewportStartY: number;
18
18
  }
19
19
 
20
- export interface PanelCompositeData {
21
- /** Workspace-level tab bar spanning all open panels. */
22
- workspaceBar: Line;
23
- /** Top pane: tab bar */
24
- topTabBar?: Line;
25
- /** Top pane: panel content lines */
26
- topContent: Line[];
27
- /** Whether the top pane is focused (affects separator color) */
28
- topFocused: boolean;
29
- /** Bottom pane tab bar. Undefined = no bottom pane. */
30
- bottomTabBar?: Line;
31
- /** Bottom pane content lines. Undefined = no bottom pane. */
32
- bottomContent?: Line[];
33
- /** Whether the bottom pane is focused */
34
- bottomFocused?: boolean;
35
- /** Separator between left and right panel area */
36
- separator: boolean;
37
- /** Ratio of panel height for the top pane (0–1). Only used when bottom pane is present. */
38
- verticalSplitRatio: number;
20
+ export interface SidebarCompositeData {
21
+ /** Pre-rendered sidebar lines, one per viewport row. */
22
+ lines: Line[];
39
23
  }
40
24
 
41
25
  export interface CompositeRequest {
@@ -47,8 +31,8 @@ export interface CompositeRequest {
47
31
  forceFullRedraw?: boolean;
48
32
  selection?: SelectionInfo;
49
33
  search?: SearchInfo;
50
- panel?: PanelCompositeData;
51
- panelWidth?: number; // width of the right panel area (0 = no panel)
34
+ sidebar?: SidebarCompositeData;
35
+ sidebarWidth?: number; // width of the right sidebar area (0 = no sidebar)
52
36
  }
53
37
 
54
38
  /**
@@ -75,7 +59,7 @@ export class Compositor {
75
59
  }
76
60
 
77
61
  public composite(params: CompositeRequest): void {
78
- const { width, height, header, viewport, footer, forceFullRedraw, selection, search, panel, panelWidth } = params;
62
+ const { width, height, header, viewport, footer, forceFullRedraw, selection, search, sidebar, sidebarWidth } = params;
79
63
  const previousFrontBuffer = forceFullRedraw ? null : this.frontBuffer;
80
64
  if (forceFullRedraw) this.diffEngine.reset();
81
65
 
@@ -87,9 +71,9 @@ export class Compositor {
87
71
  }
88
72
  const newBuffer = this.backBuffer;
89
73
 
90
- const hasPanel = panel !== undefined && panelWidth !== undefined && panelWidth > 0;
91
- const leftWidth = hasPanel ? Math.max(1, width - panelWidth - 1) : width;
92
- const sepX = hasPanel ? leftWidth : -1;
74
+ const hasSidebar = sidebar !== undefined && sidebarWidth !== undefined && sidebarWidth > 0;
75
+ const leftWidth = hasSidebar ? Math.max(1, width - sidebarWidth - 1) : width;
76
+ const sepX = hasSidebar ? leftWidth : -1;
93
77
 
94
78
  // 1. Draw Header — always full width
95
79
  header.forEach((line, i) => newBuffer.blitLine(i, line));
@@ -102,41 +86,17 @@ export class Compositor {
102
86
  const lineCount = selection?.lineCount ?? 0;
103
87
  const offset = Math.max(0, vHeight - lineCount);
104
88
 
105
- // --- Pre-compute panel row layout when split pane is active ---
106
- // When both top and bottom panes are visible, the panel area is split:
107
- // row 0: workspace tab bar
108
- // row 1: top tab bar
109
- // rows 2..topH+1: top content
110
- // row topH+2: horizontal separator (───)
111
- // row topH+3: bottom tab bar
112
- // rows topH+4..end: bottom content
113
- const hasBottomPane = hasPanel && panel!.bottomTabBar !== undefined;
114
- let topPaneHeight = 0; // number of content rows in top pane
115
- let bottomPaneHeight = 0;
116
- let hSepRow = -1; // viewport row of the horizontal separator
117
- if (hasPanel && hasBottomPane) {
118
- const panelAreaRows = Math.max(0, vHeight - 1); // subtract workspace tab bar
119
- // top: 1 (tabbar) + topContent rows; bottom: 1 (sep) + 1 (tabbar) + bottomContent
120
- const contentRows = Math.max(0, panelAreaRows - 3); // subtract top-tabbar + h-sep + bottom-tabbar
121
- topPaneHeight = Math.max(1, Math.floor(contentRows * panel!.verticalSplitRatio));
122
- bottomPaneHeight = Math.max(1, contentRows - topPaneHeight);
123
- hSepRow = 2 + topPaneHeight; // workspace bar + top tab bar + top content rows
124
- }
125
-
126
- const sepFg = hasPanel && panel!.separator
127
- ? (panel!.topFocused || panel!.bottomFocused ? '244' : '238')
128
- : '238';
89
+ const sepFg = '238';
129
90
 
130
91
  viewport.forEach((line, i) => {
131
92
  const screenY = viewportStartY + i;
132
93
  if (screenY >= height) return;
133
94
 
134
- if (!hasPanel) {
135
- // No panel: existing fast path
95
+ if (!hasSidebar) {
96
+ // No sidebar: existing fast path
136
97
  newBuffer.blitLine(screenY, line);
137
98
  } else {
138
- // Panel active: write cells individually to support split layout
139
- // Left side: viewport cells 0..leftWidth-1
99
+ // Sidebar active: left side gets viewport cells 0..leftWidth-1
140
100
  for (let x = 0; x < leftWidth; x++) {
141
101
  const cell = line[x];
142
102
  if (cell !== undefined) {
@@ -151,79 +111,21 @@ export class Compositor {
151
111
  }
152
112
  }
153
113
 
154
- const p = panel!;
155
-
156
- // Separator column (vertical bar between left and panel area)
157
- if (p.separator) {
158
- newBuffer.setCell(sepX, screenY, createStyledCell('│', { fg: sepFg }));
159
- }
160
-
161
- const panelStartX = sepX + 1;
162
- const clearPanelRemainder = (fromX = 0) => {
163
- for (let x = Math.max(0, fromX); x < panelWidth; x++) {
164
- newBuffer.setCell(panelStartX + x, screenY, createEmptyCell());
165
- }
166
- };
167
- const drawPanelLine = (panelLine: Line | undefined) => {
168
- if (panelLine === undefined) {
169
- clearPanelRemainder();
170
- return;
171
- }
172
- const limit = Math.min(panelLine.length, panelWidth);
173
- for (let x = 0; x < limit; x++) {
174
- const cell = panelLine[x];
175
- if (cell !== undefined) {
176
- newBuffer.setCell(panelStartX + x, screenY, cell);
177
- }
178
- }
179
- clearPanelRemainder(limit);
180
- };
181
-
182
- if (!hasBottomPane) {
183
- // --- Single pane mode ---
184
- // viewport row 0 → workspace bar, viewport rows 1+ → panel content
185
- const panelLine = i === 0 ? p.workspaceBar : p.topContent[i - 1];
186
- drawPanelLine(panelLine);
187
- } else {
188
- // --- Two pane mode ---
189
- // Row layout (by viewport row i):
190
- // i = 0: workspace tab bar
191
- // i = 1: top tab bar
192
- // 2 <= i <= topPaneHeight+1: top content[i-2]
193
- // i = hSepRow: horizontal separator
194
- // i = hSepRow+1: bottom tab bar
195
- // i >= hSepRow+2: bottom content[i - (hSepRow+2)]
196
- let panelLine: Line | undefined;
197
-
198
- if (i === 0) {
199
- panelLine = p.workspaceBar;
200
- } else if (i === 1) {
201
- panelLine = p.topTabBar;
202
- } else if (i <= topPaneHeight + 1) {
203
- panelLine = p.topContent[i - 2];
204
- } else if (i === hSepRow) {
205
- // Horizontal separator between the two panes
206
- // Render ─ chars across the panel width
207
- const focusFg = p.bottomFocused ? '36' : '238'; // cyan if bottom pane focused
208
- for (let x = 0; x < panelWidth; x++) {
209
- newBuffer.setCell(panelStartX + x, screenY, createStyledCell('─', { fg: focusFg }));
210
- }
211
- // Also update the separator column char to T-junction (├):
212
- // ├ connects the vertical left-separator with the horizontal pane divider,
213
- // forming a clean T-shaped joint at the split point.
214
- if (p.separator) {
215
- newBuffer.setCell(sepX, screenY, createStyledCell('├', { fg: focusFg }));
216
- }
217
- } else if (i === hSepRow + 1) {
218
- panelLine = p.bottomTabBar;
219
- } else {
220
- panelLine = p.bottomContent?.[i - (hSepRow + 2)];
221
- }
114
+ // Separator column (vertical bar between conversation and sidebar)
115
+ newBuffer.setCell(sepX, screenY, createStyledCell('│', { fg: sepFg }));
222
116
 
223
- if (i !== hSepRow) {
224
- drawPanelLine(panelLine);
117
+ const sidebarStartX = sepX + 1;
118
+ const sidebarLine = sidebar!.lines[i];
119
+ const limit = sidebarLine === undefined ? 0 : Math.min(sidebarLine.length, sidebarWidth);
120
+ for (let x = 0; x < limit; x++) {
121
+ const cell = sidebarLine![x];
122
+ if (cell !== undefined) {
123
+ newBuffer.setCell(sidebarStartX + x, screenY, cell);
225
124
  }
226
125
  }
126
+ for (let x = limit; x < sidebarWidth; x++) {
127
+ newBuffer.setCell(sidebarStartX + x, screenY, createEmptyCell());
128
+ }
227
129
  }
228
130
 
229
131
  // Apply Selection Highlighting Overlay (left side only)
@@ -260,12 +162,22 @@ export class Compositor {
260
162
  newBuffer.blitLine(screenY, createEmptyLine(width));
261
163
  }
262
164
 
263
- // Draw separator on remaining viewport rows past content (when panel is active)
264
- if (hasPanel && panel!.separator) {
165
+ // Draw the separator and sidebar on viewport rows past the conversation content
166
+ if (hasSidebar) {
265
167
  for (let i = viewport.length; i < vHeight; i++) {
266
168
  const screenY = viewportStartY + i;
267
169
  if (screenY >= height) break;
268
170
  newBuffer.setCell(sepX, screenY, createStyledCell('│', { fg: sepFg }));
171
+ const sidebarStartX = sepX + 1;
172
+ const sidebarLine = sidebar!.lines[i];
173
+ const limit = sidebarLine === undefined ? 0 : Math.min(sidebarLine.length, sidebarWidth!);
174
+ for (let x = 0; x < limit; x++) {
175
+ const cell = sidebarLine![x];
176
+ if (cell !== undefined) newBuffer.setCell(sidebarStartX + x, screenY, cell);
177
+ }
178
+ for (let x = limit; x < sidebarWidth!; x++) {
179
+ newBuffer.setCell(sidebarStartX + x, screenY, createEmptyCell());
180
+ }
269
181
  }
270
182
  }
271
183
 
@@ -54,11 +54,11 @@ export function applyConversationOverlays(
54
54
  }
55
55
 
56
56
  if (input.searchManager.active) {
57
- next.push(...renderSearchOverlay(input.searchManager, conversationWidth));
57
+ next = [...next, ...renderSearchOverlay(input.searchManager, conversationWidth)];
58
58
  }
59
59
 
60
60
  if (input.historySearch.active) {
61
- next.push(...renderHistorySearchOverlay(input.historySearch, conversationWidth));
61
+ next = [...next, ...renderHistorySearchOverlay(input.historySearch, conversationWidth)];
62
62
  }
63
63
 
64
64
  if (input.processModal.active) {
@@ -102,7 +102,7 @@ export function applyConversationOverlays(
102
102
  }
103
103
 
104
104
  if (input.helpOverlayActive) {
105
- const lines = renderHelpOverlay(conversationWidth, keybindingsManager, commandRegistry.getAll(), input.helpScrollOffset, viewportHeight);
105
+ const lines = renderHelpOverlay(conversationWidth, keybindingsManager, commandRegistry.getVisible(), input.helpScrollOffset, viewportHeight);
106
106
  next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
107
107
  }
108
108
 
@@ -15,7 +15,7 @@ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
15
15
  const FEATURED_HELP_COMMANDS: Array<[name: string, argHint: string, desc: string]> = [
16
16
  ['agent', '', 'Open workspace; press / there to search every action'],
17
17
  ['agent', 'knowledge', 'Open isolated Agent Knowledge workspace actions'],
18
- ['agent', 'voice-media', 'Open voice, image, browser, and media workspace actions'],
18
+ ['agent', 'voice', 'Open voice, image, and media workspace actions'],
19
19
  ['setup', '', 'Open the Agent workspace'],
20
20
  ['knowledge', 'status', 'Inspect isolated Agent Knowledge readiness'],
21
21
  ['memory', '', 'Manage Agent-local memory records'],
@@ -55,6 +55,7 @@ const HELP_OVERLAY_STATIC_TEXT = [
55
55
  'Quick Start',
56
56
  'Available Slash Commands',
57
57
  'More Commands',
58
+ 'Hidden power commands still work — run /commands for the full catalog.',
58
59
  'Essentials',
59
60
  'Show this help overlay',
60
61
  'Keyboard shortcut reference',
@@ -158,7 +159,7 @@ export function renderHelpOverlay(
158
159
  ' ' + '\u2500'.repeat(40),
159
160
  ` ${'?'.padEnd(20)} Toggle help`,
160
161
  ` ${'/shortcuts'.padEnd(20)} Full keyboard shortcuts`,
161
- ` ${kb('panel-picker').padEnd(20)} Open the Agent operator workspace`,
162
+ ` ${kb('workspace-picker').padEnd(20)} Open the Agent operator workspace`,
162
163
  ` ${'Workspace /'.padEnd(20)} Search all Agent workspace actions`,
163
164
  ` ${'Workspace Enter'.padEnd(20)} Open selected action or form`,
164
165
  '',
@@ -234,6 +235,7 @@ export function renderHelpOverlay(
234
235
  commandRows.push(` ${nameCol} ${cmd.description}`);
235
236
  }
236
237
  }
238
+ commandRows.push('', ' Hidden power commands still work — run /commands for the full catalog.');
237
239
  } else if (!hasCommand('help')) {
238
240
  commandRows.push('', ' Essentials', ' ' + '\u2500'.repeat(40));
239
241
  commandRows.push(' /help Show this help overlay');
@@ -330,9 +332,9 @@ export function renderShortcutsOverlay(
330
332
  ' Workspace',
331
333
  ' ' + '\u2500'.repeat(40),
332
334
  row('Tab', 'Swap focus between input and active Agent workspace'),
333
- row(kb('panel-picker'), 'Open the Agent operator workspace'),
334
- row(kb('panel-tab-next'), 'Cycle Agent workspace category forward'),
335
- row(kb('panel-tab-prev'), 'Cycle Agent workspace category backward'),
335
+ row(kb('workspace-picker'), 'Open the Agent operator workspace'),
336
+ row(kb('workspace-tab-next'), 'Cycle Agent workspace category forward'),
337
+ row(kb('workspace-tab-prev'), 'Cycle Agent workspace category backward'),
336
338
  '',
337
339
  ` Config: /keybindings to list and customize`,
338
340
  ];