@pellux/goodvibes-agent 1.4.4 → 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 +14 -0
  2. package/README.md +7 -7
  3. package/dist/package/main.js +7424 -12241
  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 +40 -125
  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
@@ -11,6 +11,16 @@
11
11
  * }
12
12
  *
13
13
  * Each value is a KeyCombo or an array of KeyCombos for multi-binding support.
14
+ *
15
+ * Action ID backward-compatibility aliases (accepted in keybindings.json):
16
+ * "panel-picker" → "workspace-picker"
17
+ * "panel-close" → "workspace-close"
18
+ * "panel-close-all" → "workspace-close-all"
19
+ * "panel-tab-next" → "workspace-tab-next"
20
+ * "panel-tab-prev" → "workspace-tab-prev"
21
+ *
22
+ * These aliases are accepted when loading user-provided keybindings.json
23
+ * override files so existing user configs continue to work without changes.
14
24
  */
15
25
 
16
26
  import { readFileSync, existsSync } from 'node:fs';
@@ -34,11 +44,12 @@ export type KeyAction =
34
44
  | 'copy-selection'
35
45
  | 'clear-cancel'
36
46
  | 'screen-clear'
37
- | 'panel-picker'
38
- | 'panel-close'
39
- | 'panel-close-all'
40
- | 'panel-tab-next'
41
- | 'panel-tab-prev'
47
+ | 'workspace-picker'
48
+ | 'workspace-close'
49
+ | 'workspace-close-all'
50
+ | 'workspace-tab-next'
51
+ | 'workspace-tab-prev'
52
+ | 'sidebar-toggle'
42
53
  | 'history-search'
43
54
  | 'search'
44
55
  | 'block-copy'
@@ -59,11 +70,12 @@ export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
59
70
  'copy-selection': 'Copy selected text to clipboard',
60
71
  'clear-cancel': 'Clear input / cancel generation / exit (double)',
61
72
  'screen-clear': 'Repaint the screen',
62
- 'panel-picker': 'Open the Agent operator workspace',
63
- 'panel-close': 'Dismiss Agent workspace',
64
- 'panel-close-all': 'Dismiss Agent workspace focus',
65
- 'panel-tab-next': 'Cycle Agent workspace category forward',
66
- 'panel-tab-prev': 'Cycle Agent workspace category backward',
73
+ 'workspace-picker': 'Open the Agent operator workspace',
74
+ 'workspace-close': 'Close the Agent workspace',
75
+ 'workspace-close-all': 'Close the Agent workspace (alias: also bound to Ctrl+Shift+X)',
76
+ 'workspace-tab-next': 'Cycle Agent workspace category forward',
77
+ 'workspace-tab-prev': 'Cycle Agent workspace category backward',
78
+ 'sidebar-toggle': 'Show or hide the activity sidebar',
67
79
  'history-search': 'Reverse input history search',
68
80
  'search': 'Toggle conversation search',
69
81
  'block-copy': 'Copy nearest block to clipboard',
@@ -98,11 +110,12 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
98
110
  'copy-selection': [{ key: 'c', ctrl: true, shift: true }],
99
111
  'clear-cancel': [{ key: 'c', ctrl: true }],
100
112
  'screen-clear': [{ key: 'l', ctrl: true }],
101
- 'panel-picker': [{ key: 'p', ctrl: true }],
102
- 'panel-close': [{ key: 'x', ctrl: true }],
103
- 'panel-close-all': [{ key: 'x', ctrl: true, shift: true }],
104
- 'panel-tab-next': [{ key: ']', ctrl: true }],
105
- 'panel-tab-prev': [{ key: '[', ctrl: true }],
113
+ 'workspace-picker': [{ key: 'p', ctrl: true }],
114
+ 'workspace-close': [{ key: 'x', ctrl: true }],
115
+ 'workspace-close-all': [{ key: 'x', ctrl: true, shift: true }],
116
+ 'workspace-tab-next': [{ key: ']', ctrl: true }],
117
+ 'workspace-tab-prev': [{ key: '[', ctrl: true }],
118
+ 'sidebar-toggle': [{ key: 'o', ctrl: true }],
106
119
  'history-search': [{ key: 'r', ctrl: true }],
107
120
  'search': [{ key: 'f', ctrl: true }],
108
121
  'block-copy': [{ key: 'y', ctrl: true }],
@@ -116,7 +129,7 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
116
129
  'undo': [{ key: 'z', ctrl: true }],
117
130
  'redo': [{ key: 'z', ctrl: true, shift: true }],
118
131
  'paste': [{ key: 'v', ctrl: true }],
119
- 'replay-panel': [{ key: 'r', ctrl: true, shift: true }],
132
+ 'replay-panel': [], // intentionally unbound until replay-panel is implemented
120
133
  };
121
134
 
122
135
  /** Resolved overrides type: each key can be a single combo or array. */
@@ -185,7 +198,17 @@ export class KeybindingsManager {
185
198
  // Reset to defaults before applying overrides
186
199
  this.bindings = this.cloneDefaults();
187
200
 
188
- for (const [action, combo] of Object.entries(parsed)) {
201
+ // Backward-compatibility aliases: map old panel-* action ids to workspace-* equivalents.
202
+ const ALIASES: Readonly<Record<string, KeyAction>> = {
203
+ 'panel-picker': 'workspace-picker',
204
+ 'panel-close': 'workspace-close',
205
+ 'panel-close-all': 'workspace-close-all',
206
+ 'panel-tab-next': 'workspace-tab-next',
207
+ 'panel-tab-prev': 'workspace-tab-prev',
208
+ };
209
+
210
+ for (const [rawAction, combo] of Object.entries(parsed)) {
211
+ const action: string = Object.hasOwn(ALIASES, rawAction) ? ALIASES[rawAction as keyof typeof ALIASES] : rawAction;
189
212
  if (!validActions.has(action as KeyAction)) {
190
213
  logger.debug('keybindings: unknown action, skipping', { action });
191
214
  continue;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Local-fit recommendation builder.
3
+ *
4
+ * When no provider credentials are configured, the model picker would show an
5
+ * empty list. This module builds a small set of synthetic model entries that
6
+ * represent common local model sizes so the picker opens with useful content.
7
+ *
8
+ * Rules:
9
+ * - These entries are purely informational — they are NOT installed models.
10
+ * - They must never appear when any real provider is configured.
11
+ * - Selecting one routes through the local-provider install/confirm path,
12
+ * never a silent model switch.
13
+ * - The provider sentinel 'local' is distinct from 'ollama', 'lm-studio',
14
+ * etc. so cloud-provider fit checks are never triggered.
15
+ */
16
+
17
+ import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers';
18
+ import {
19
+ estimateModelBytes,
20
+ fitAssessment,
21
+ fitVerdictLabel,
22
+ readHardwareProfileSync,
23
+ } from '../core/hardware-profile.ts';
24
+ import type { HardwareProfile } from '../core/hardware-profile.ts';
25
+
26
+ /**
27
+ * Sentinel provider ID used on synthetic local recommendation entries.
28
+ * Must not collide with any real provider (ollama, lm-studio, etc.).
29
+ */
30
+ export const LOCAL_REC_PROVIDER = 'local';
31
+
32
+ /**
33
+ * Representative local model sizes to recommend.
34
+ * Each entry: { id, displayName, params, sizeLabel }
35
+ */
36
+ const LOCAL_REC_SIZES = [
37
+ { id: 'local:3b', displayName: '3B model (small, fast)', params: 3_000_000_000, sizeLabel: '3B' },
38
+ { id: 'local:7b', displayName: '7B model (balanced)', params: 7_000_000_000, sizeLabel: '7B' },
39
+ { id: 'local:13b', displayName: '13B model (more capable)', params: 13_000_000_000, sizeLabel: '13B' },
40
+ ] as const;
41
+
42
+ /**
43
+ * Sentinel id used on the synthetic "Sign in to a provider" row that is
44
+ * appended to the local-only list when no credentials are configured.
45
+ */
46
+ export const SIGN_IN_ROW_ID = 'local:sign-in';
47
+
48
+ /**
49
+ * Return true when a model entry is the synthetic sign-in affordance row.
50
+ * This row must never be committed as an active model.
51
+ */
52
+ export function isProviderSignInRow(model: ModelDefinition): boolean {
53
+ return model.id === SIGN_IN_ROW_ID && model.provider === LOCAL_REC_PROVIDER;
54
+ }
55
+
56
+ /**
57
+ * Return true when a model entry is a synthetic local fit recommendation
58
+ * (i.e. not a real installed model).
59
+ */
60
+ export function isLocalFitRecommendation(model: ModelDefinition): boolean {
61
+ return (
62
+ model.provider === LOCAL_REC_PROVIDER &&
63
+ (model.description ?? '').includes('not yet installed')
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Build the synthetic "Sign in to a provider" row for the local-only list.
69
+ * Selecting this row routes to the provider picker; it must never be committed
70
+ * as an active model.
71
+ */
72
+ export function buildSignInRow(): ModelDefinition {
73
+ return {
74
+ id: SIGN_IN_ROW_ID,
75
+ provider: LOCAL_REC_PROVIDER,
76
+ registryKey: SIGN_IN_ROW_ID,
77
+ displayName: 'Sign in to a provider →',
78
+ description: 'Open the provider sign-in flow to connect a cloud or local provider.',
79
+ capabilities: {
80
+ toolCalling: false,
81
+ codeEditing: false,
82
+ reasoning: false,
83
+ multimodal: false,
84
+ },
85
+ contextWindow: 0,
86
+ selectable: true,
87
+ tier: 'free',
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Build synthetic local recommendation entries annotated with hardware fit
93
+ * labels for the current machine.
94
+ *
95
+ * Pass a `profile` override for deterministic tests; omit to use the
96
+ * process-level hardware probe (`readHardwareProfileSync`).
97
+ */
98
+ export function buildLocalFitRecommendations(
99
+ profileOverride?: HardwareProfile,
100
+ ): ModelDefinition[] {
101
+ const profile = profileOverride ?? readHardwareProfileSync();
102
+
103
+ return LOCAL_REC_SIZES.map((spec) => {
104
+ const sizeBytes = estimateModelBytes(spec.params);
105
+ const verdict = fitAssessment(sizeBytes, profile);
106
+ const fitHint = fitVerdictLabel(verdict, spec.sizeLabel);
107
+ // Plain-language detail: never imply the model is ready to use.
108
+ const detail = fitHint
109
+ ? `recommended for your hardware — not yet installed (${fitHint})`
110
+ : 'recommended for your hardware — not yet installed';
111
+
112
+ const model: ModelDefinition = {
113
+ id: spec.id,
114
+ provider: LOCAL_REC_PROVIDER,
115
+ registryKey: spec.id,
116
+ displayName: spec.displayName,
117
+ description: detail,
118
+ capabilities: {
119
+ toolCalling: true,
120
+ codeEditing: false,
121
+ reasoning: false,
122
+ multimodal: false,
123
+ },
124
+ contextWindow: 8192,
125
+ selectable: true,
126
+ tier: 'free',
127
+ };
128
+ return model;
129
+ });
130
+ }
@@ -3,13 +3,11 @@ import type { SubmissionIntent } from './submission-intent.ts';
3
3
  export interface SubmissionRouterInput {
4
4
  readonly text: string;
5
5
  readonly commandMode?: boolean;
6
- readonly panelFocused?: boolean;
7
6
  readonly hasAttachments?: boolean;
8
7
  }
9
8
 
10
9
  const PLAN_COMMANDS = new Set(['plan']);
11
10
  const DELEGATION_COMMANDS = new Set(['delegate', 'build', 'review', 'wrfc', 'agents', 'remote']);
12
- const PANEL_COMMANDS = new Set(['panel']);
13
11
  const ORCHESTRATION_COMMANDS = new Set([
14
12
  'orchestration',
15
13
  'tasks',
@@ -45,9 +43,6 @@ export function routeSubmissionIntent(input: SubmissionRouterInput): SubmissionI
45
43
  if (DELEGATION_COMMANDS.has(commandName)) {
46
44
  return { kind: 'delegation', label: 'delegation', commandName, hasAttachments };
47
45
  }
48
- if (PANEL_COMMANDS.has(commandName)) {
49
- return { kind: 'slash-command', label: 'Agent workspace', commandName, hasAttachments };
50
- }
51
46
  if (ORCHESTRATION_COMMANDS.has(commandName)) {
52
47
  return { kind: 'orchestration', label: 'orchestration', commandName, hasAttachments };
53
48
  }
package/src/main.ts CHANGED
@@ -25,20 +25,17 @@ import { createShellLayout } from './renderer/layout-engine.ts';
25
25
  import { buildShellFooter, estimateShellFooterHeight } from './renderer/shell-surface.ts';
26
26
  import { buildConversationViewport } from './renderer/conversation-layout.ts';
27
27
  import { applyConversationOverlays } from './renderer/conversation-overlays.ts';
28
- import { buildPanelCompositeData } from './renderer/panel-composite.ts';
28
+ import { buildActivitySidebarLines, resolveActivitySidebarWidth } from './renderer/activity-sidebar.ts';
29
29
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
30
- import { registerBuiltinPanels } from './panels/builtin-panels.ts';
31
- import { renderPanelTabBar } from './renderer/panel-tab-bar.ts';
32
30
  import { bootstrapRuntime } from './runtime/bootstrap.ts';
33
31
  import type { BootstrapContext } from './runtime/bootstrap.ts';
34
32
  import type { HITLMode } from '@pellux/goodvibes-sdk/platform/state';
35
- import type { HookPhase, HookCategory, HookEventPath } from '@pellux/goodvibes-sdk/platform/hooks';
33
+ import { wireSessionPersistenceAndRecovery, wireSetupIncompleteHint } from './shell/startup-wiring.ts';
34
+ import { localModelCookbook } from './tools/agent-harness-model-routing.ts';
35
+ import { localModelSetupStatus } from './tools/agent-harness-setup-model-helpers.ts';
36
36
  import {
37
- checkRecoveryFile,
38
37
  deleteRecoveryFile,
39
38
  loadRecoveryConversation,
40
- persistConversation,
41
- writeRecoveryFile,
42
39
  } from '@/runtime/index.ts';
43
40
  import type { SessionSnapshot } from '@/runtime/index.ts';
44
41
  import { handleBlockingShellInput, type PendingPermissionState } from './shell/blocking-input.ts';
@@ -47,7 +44,7 @@ import { getTerminalSize } from './shell/terminal-size.ts';
47
44
  import { buildShellSessionContinuityHints } from './shell/session-continuity-hints.ts';
48
45
  import { wireShellUiOpeners } from './shell/ui-openers.ts';
49
46
  import { deriveComposerState } from './core/composer-state.ts';
50
- import { buildPersistedSessionContext, formatReturnContextForDisplay, getReturnContextMode, maybeAssistReturnContextSummary } from '@/runtime/index.ts';
47
+ import { buildPersistedSessionContext } from '@/runtime/index.ts';
51
48
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
52
49
  import { prepareShellCliRuntime } from './cli/entrypoint.ts';
53
50
  import { applyInitialTuiCliState, formatFatalStartupErrorForLog, formatFatalStartupErrorForUser, getInteractiveTerminalLaunchError } from './cli/tui-startup.ts';
@@ -56,6 +53,8 @@ import { attachSpokenTurnModelRouting, createSpokenTurnInputOptions } from './au
56
53
  import { allowTerminalWrite, installTuiTerminalOutputGuard } from './runtime/terminal-output-guard.ts';
57
54
  import { buildCommandArgsHint } from './input/command-args-hint.ts';
58
55
  import { GOODVIBES_AGENT_PAIRING_SURFACE } from './config/surface.ts';
56
+ import { createAutonomySurfacing, buildCalendarEventsLister, buildSkillDraftProposer } from './shell/autonomy-surfacing.ts';
57
+ import { startHardwareProbe } from './core/hardware-profile.ts';
59
58
 
60
59
  const ALT_SCREEN_ENTER = '\x1b[?1049h', ALT_SCREEN_EXIT = '\x1b[?1049l', MOUSE_ENABLE = '\x1b[?1000h\x1b[?1002h\x1b[?1006h', MOUSE_DISABLE = '\x1b[?1006l\x1b[?1002l\x1b[?1000l', CURSOR_HIDE = '\x1b[?25l', CURSOR_SHOW = '\x1b[?25h', CLEAR_SCREEN = '\x1b[2J\x1b[3J\x1b[H';
61
60
  const KEYBOARD_EXT_ENABLE = '\x1b[>4;2m' + '\x1b[?1u', KEYBOARD_EXT_DISABLE = '\x1b[>4;0m' + '\x1b[?1l', PASTE_ENABLE = '\x1b[?2004h', PASTE_DISABLE = '\x1b[?2004l';
@@ -127,12 +126,10 @@ async function main() {
127
126
  }
128
127
  }
129
128
 
130
- const panelManager = ctx.services.panelManager;
131
129
  const buildSessionContinuityHints = () => buildShellSessionContinuityHints(
132
130
  uiServices.readModels.session.getSnapshot(),
133
131
  uiServices.readModels.tasks.getSnapshot(),
134
132
  uiServices.readModels.remote.getSnapshot(),
135
- panelManager.getAllOpen(),
136
133
  );
137
134
  const buildCurrentSessionSnapshot = (): SessionSnapshot => {
138
135
  const messages = conversation.getMessageSnapshot();
@@ -154,13 +151,37 @@ async function main() {
154
151
  render();
155
152
  });
156
153
 
157
- let streamStartTime = 0;
158
- let streamDeltaCount = 0;
159
154
  let streamTokenSpeed = 0;
160
155
 
161
156
  let scrollTop = 0;
162
157
  let scrollLocked = true;
163
158
 
159
+ // Ambient autonomy surfacing: away digest at launch + sidebar Coming up.
160
+ const autonomy = createAutonomySurfacing({
161
+ shellPaths: ctx.services.shellPaths,
162
+ listAutomationJobs: () => ctx.services.automationManager.listJobs(),
163
+ listApprovals: () => ctx.services.approvalBroker.listApprovals(),
164
+ getTasksSnapshot: () => uiServices.readModels.tasks.getSnapshot().tasks,
165
+ router: {
166
+ high: (message) => systemMessageRouter.high(message),
167
+ getFeed: () => systemMessageRouter.getFeed(),
168
+ },
169
+ render: () => render(),
170
+ listCalendarEvents: buildCalendarEventsLister(ctx.services.shellPaths),
171
+ onAwayDigest: buildSkillDraftProposer(ctx.services.shellPaths, commandContext),
172
+ });
173
+
174
+ // Activity sidebar: shows ambient status on wide terminals. null = automatic
175
+ // (visible when the terminal is wide enough); the user can toggle it with
176
+ // Ctrl+O, which pins an explicit on/off override for the session.
177
+ let sidebarOverride: boolean | null = null;
178
+ const sidebarWidthFor = (width: number): number => {
179
+ const auto = resolveActivitySidebarWidth(width);
180
+ if (sidebarOverride === null) return auto;
181
+ if (!sidebarOverride) return 0;
182
+ return auto > 0 ? auto : Math.min(36, Math.max(28, Math.floor(width * 0.3)));
183
+ };
184
+
164
185
  const getPromptContentWidth = () => {
165
186
  const w = getTerminalSize(stdout).width;
166
187
  const boxMargin = 2;
@@ -228,6 +249,8 @@ async function main() {
228
249
  const exitApp = (): void => {
229
250
  stopSpokenOutputForExit?.();
230
251
  unsubs.forEach(fn => fn());
252
+ // Persist last-seen before shutdown so the next launch can compute the digest.
253
+ autonomy.stop();
231
254
  const snapshot = buildCurrentSessionSnapshot();
232
255
  ctx.shutdown(snapshot).catch((err) => {
233
256
  logger.debug('ctx.shutdown error during exitApp (non-fatal)', { error: summarizeError(err) });
@@ -349,6 +372,11 @@ async function main() {
349
372
  allowTerminalWrite(() => stdout.write(CLEAR_SCREEN));
350
373
  render();
351
374
  };
375
+ commandContext.toggleActivitySidebar = () => {
376
+ const width = getTerminalSize(stdout).width;
377
+ sidebarOverride = !(sidebarWidthFor(width) > 0);
378
+ render();
379
+ };
352
380
  permissionPromptRef.requestPermission = (request) =>
353
381
  new Promise((resolve) => {
354
382
  pendingPermission = {
@@ -389,7 +417,6 @@ async function main() {
389
417
  shell: {
390
418
  bookmarkManager: ctx.services.bookmarkManager,
391
419
  keybindingsManager: ctx.services.keybindingsManager,
392
- panelManager,
393
420
  processManager,
394
421
  profileManager: ctx.services.profileManager,
395
422
  },
@@ -430,8 +457,10 @@ async function main() {
430
457
  const render = () => {
431
458
  const { width, height } = getTerminalSize(stdout);
432
459
 
460
+ // Fire-and-forget refresh for the 'Coming up' sidebar section.
461
+ autonomy.refreshComingUp();
462
+
433
463
  if (input.agentWorkspace.active) {
434
- input.setPanelMouseLayout(null);
435
464
  activeConversationWidth = width;
436
465
  conversation.setSplashSuppressed(true);
437
466
  if (input.modelPicker.active) {
@@ -463,7 +492,6 @@ async function main() {
463
492
  const composerState = deriveComposerState({
464
493
  text: input.prompt,
465
494
  commandMode: input.commandMode,
466
- panelFocused: input.panelFocused,
467
495
  pendingApproval: pendingPermission !== null,
468
496
  hasAttachments: input.getImageAttachments().size > 0,
469
497
  turnState: sessionSnapshot.turnState,
@@ -513,31 +541,18 @@ async function main() {
513
541
 
514
542
  const shellHeaderLines = headerLines;
515
543
  const shellFooterLines = footerLines;
516
- const panelWidth = panelManager.isVisible() && panelManager.getAllOpen().length > 0
517
- ? panelManager.getRightWidth(width)
518
- : 0;
544
+ const sidebarWidth = sidebarWidthFor(width);
519
545
  const shellLayout = createShellLayout({
520
546
  width,
521
547
  height,
522
548
  headerHeight: shellHeaderLines.length,
523
549
  footerHeight: shellFooterLines.length,
524
- panelWidth,
550
+ panelWidth: sidebarWidth,
525
551
  });
526
- input.setPanelMouseLayout(shellLayout.panel
527
- ? {
528
- x: shellLayout.panel.x,
529
- y: shellLayout.panel.y,
530
- width: shellLayout.panel.width,
531
- height: shellLayout.panel.height,
532
- hasBottomPane: panelManager.isBottomPaneVisible() && panelManager.getBottomPane().panels.length > 0,
533
- verticalSplitRatio: panelManager.getVerticalSplitRatio(),
534
- }
535
- : null);
536
552
  const vHeight = shellLayout.body.height;
537
553
  const conversationWidth = shellLayout.conversation.width;
538
554
  activeConversationWidth = conversationWidth;
539
- const hasPanelWorkspace = panelManager.isVisible() && panelManager.getAllOpen().length > 0;
540
- conversation.setSplashSuppressed(hasPanelWorkspace);
555
+ conversation.setSplashSuppressed(false);
541
556
 
542
557
  // Flush pending renders after updating the width provider and splash posture
543
558
  // so the transcript and splash rebuild against the current shell layout.
@@ -599,13 +614,27 @@ async function main() {
599
614
  contextWindow: currentModel.contextWindow,
600
615
  });
601
616
 
602
- // Panel composite data
603
- const panelComposite = buildPanelCompositeData(
604
- panelManager,
605
- input,
606
- shellLayout.panel?.width ?? 0,
607
- shellLayout.panel?.height ?? vHeight,
608
- );
617
+ // Activity sidebar (ambient status on wide terminals)
618
+ const sidebar = sidebarWidth > 0
619
+ ? {
620
+ lines: buildActivitySidebarLines({
621
+ now: {
622
+ busy: orchestrator.isThinking,
623
+ label: sessionSnapshot.streamToolPreview?.trim() || undefined,
624
+ agents: activeAgents.slice(0, 3).map((agent) => ({
625
+ label: agent.label,
626
+ progress: agent.latestProgress?.trim() || undefined,
627
+ })),
628
+ processes: runningProcessCount,
629
+ },
630
+ needsYou: pendingPermission
631
+ ? ['Approval needed — answer the prompt under the conversation.']
632
+ : [],
633
+ comingUp: [...autonomy.comingUpItems()],
634
+ recent: systemMessageRouter.getFeed()?.latest(Math.max(4, vHeight - 8)) ?? [],
635
+ }, sidebarWidth, vHeight),
636
+ }
637
+ : undefined;
609
638
 
610
639
  compositor.composite({
611
640
  width, height,
@@ -622,8 +651,8 @@ async function main() {
622
651
  scrollTop,
623
652
  viewportStartY: shellHeaderLines.length,
624
653
  } : undefined,
625
- panel: panelComposite.panelData,
626
- panelWidth: panelComposite.panelWidth,
654
+ sidebar,
655
+ sidebarWidth,
627
656
  });
628
657
  };
629
658
  const terminalOutputGuard = installTuiTerminalOutputGuard({ stdout, stderr: process.stderr, notify: (message) => { systemMessageRouter.low(message); render(); } });
@@ -634,7 +663,6 @@ async function main() {
634
663
  wireShellUiOpeners({
635
664
  commandContext,
636
665
  input,
637
- panelManager,
638
666
  conversation,
639
667
  configManager,
640
668
  providerRegistry,
@@ -651,35 +679,6 @@ async function main() {
651
679
  render,
652
680
  });
653
681
 
654
- // --- Streaming speed + tool preview wiring ---
655
- unsubs.push(uiServices.events.turns.on('TURN_COMPLETED', () => {
656
- // Auto-save after every LLM turn so kills don't lose the session
657
- try {
658
- const snapshot = buildCurrentSessionSnapshot();
659
- persistConversation(
660
- runtime.sessionId,
661
- snapshot,
662
- runtime.model,
663
- runtime.provider,
664
- conversation.title || '',
665
- { workingDirectory: workingDir, homeDirectory, sessionManager: ctx.services.sessionManager },
666
- );
667
- hookDispatcher.fire({ path: 'Lifecycle:session:save' as HookEventPath, phase: 'Lifecycle' as HookPhase, category: 'session' as HookCategory, specific: 'save', sessionId: runtime.sessionId, timestamp: Date.now(), payload: { sessionId: runtime.sessionId } }).catch((err: unknown) => logger.debug('hook fire error', { error: summarizeError(err) }));
668
- } catch (e) { logger.debug('auto-save on turn:complete failed', { error: summarizeError(e) }); }
669
- }));
670
-
671
- unsubs.push(uiServices.events.turns.on('STREAM_START', () => {
672
- streamStartTime = Date.now();
673
- streamDeltaCount = 0;
674
- streamTokenSpeed = 0;
675
- }));
676
- unsubs.push(uiServices.events.turns.on('STREAM_DELTA', () => {
677
- streamDeltaCount++;
678
- const elapsed = (Date.now() - streamStartTime) / 1000;
679
- // Note: counts stream deltas, not actual tokens. ~1 delta per token for most providers.
680
- streamTokenSpeed = elapsed > 0 ? streamDeltaCount / elapsed : 0;
681
- }));
682
-
683
682
  stdin.setRawMode(true);
684
683
  stdin.resume();
685
684
  stdin.setEncoding('utf8');
@@ -721,25 +720,48 @@ async function main() {
721
720
  conversation.rebuildHistory();
722
721
  render();
723
722
 
724
- const recoveryInfo = checkRecoveryFile({ workingDirectory: workingDir, homeDirectory });
725
- if (recoveryInfo) {
726
- systemMessageRouter.high(`[Recovery] Found unsaved session from ${new Date(recoveryInfo.timestamp).toLocaleString()}. Title: "${recoveryInfo.title}". Press Ctrl+R to restore, Esc to discard, or start typing to ignore it.`);
727
- for (const line of formatReturnContextForDisplay(recoveryInfo.returnContext)) {
728
- systemMessageRouter.low(`[Recovery] ${line}`);
729
- }
730
- render();
731
- recoveryPending = true;
732
- }
723
+ // Async GPU probe runs off the render frame — nvidia-smi result will populate
724
+ // the module cache and appear on the next render cycle after it completes.
725
+ startHardwareProbe();
733
726
 
734
- recoveryInterval = setInterval(() => {
735
- const snapshot = buildCurrentSessionSnapshot();
736
- writeRecoveryFile(
737
- snapshot,
738
- runtime.sessionId,
739
- conversation.title ?? '',
740
- { workingDirectory: workingDir, homeDirectory },
741
- );
742
- }, 60_000);
727
+ // Away digest runs after the first render so it lands as ambient context,
728
+ // never a startup blocker.
729
+ autonomy.announceAwayDigest();
730
+
731
+ // If setup is in-progress (user has opened /agent but not finished), show a
732
+ // gentle plain-language reminder and point them back to /agent.
733
+ wireSetupIncompleteHint({
734
+ shellPaths: ctx.services.shellPaths,
735
+ providerReady: (() => {
736
+ try { return Boolean(providerRegistry.getCurrentModel()?.id); } catch { return false; }
737
+ })(),
738
+ // localReady mirrors the 'local-model-readiness' plan item from buildSetupPlan:
739
+ // cookbook status === 'detected-local-route'. Best-effort — never blocks render.
740
+ localReady: (() => {
741
+ try { return localModelSetupStatus(localModelCookbook(commandContext, false) as Record<string, unknown>) === 'ready'; } catch { return false; }
742
+ })(),
743
+ // hostReady is intentionally omitted: at startup the external-services stub
744
+ // always reports mode='external', which would produce a misleading 'active'
745
+ // line before any real probe has run. Omitting it keeps the hint honest.
746
+ systemMessageRouter,
747
+ });
748
+
749
+ // Wire streaming-speed metrics, auto-save, and recovery — all run after the
750
+ // first render so they land as ambient context, never startup blockers.
751
+ ({ recoveryInterval, recoveryPending } = wireSessionPersistenceAndRecovery({
752
+ buildCurrentSessionSnapshot,
753
+ runtime,
754
+ conversation,
755
+ workingDir,
756
+ homeDirectory,
757
+ systemMessageRouter,
758
+ render,
759
+ unsubs,
760
+ uiServicesTurns: uiServices.events.turns,
761
+ hookDispatcher,
762
+ sessionManager: ctx.services.sessionManager,
763
+ onStreamSpeedUpdate: (speed) => { streamTokenSpeed = speed; },
764
+ }));
743
765
  }
744
766
  main().catch((err: unknown) => {
745
767
  const detail = formatFatalStartupErrorForLog(err);
@@ -758,4 +780,4 @@ main().catch((err: unknown) => {
758
780
  // Ignore secondary stderr failures during process teardown.
759
781
  }
760
782
  process.exit(1);
761
- });
783
+ });