@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
@@ -1,7 +1,7 @@
1
1
  import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
2
2
  import type {
3
3
  ProviderAccountInspectionQuery,
4
- } from '../runtime/ui-service-queries.ts';
4
+ } from './ui-service-queries.ts';
5
5
 
6
6
  export type ProviderAuthRoute = 'api-key' | 'subscription' | 'service-oauth' | 'unconfigured';
7
7
  export type ProviderAuthFreshness = 'healthy' | 'expiring' | 'expired' | 'pending' | 'unconfigured';
@@ -261,7 +261,10 @@ export async function buildProviderAccountSnapshot(
261
261
  : 'No usable auth route is configured for this provider.',
262
262
  authFreshness: freshness,
263
263
  fallbackRoute: activeRoute !== preferredRoute ? activeRoute : undefined,
264
- fallbackRisk: hasSubscription && hasApiKey
264
+ // D7 fix: fallbackRisk is only meaningful when a fallbackRoute exists (i.e. activeRoute
265
+ // differs from preferredRoute). Setting it on a healthy provider where activeRoute===
266
+ // preferredRoute would show a spurious risk advisory with no fallback target.
267
+ fallbackRisk: (activeRoute !== preferredRoute) && hasSubscription && hasApiKey
265
268
  ? (isExpired(subscription?.expiresAt)
266
269
  ? 'preferred subscription path is expired; active route falls back to API key.'
267
270
  : 'Both subscription and API key are present; check route priority.')
@@ -46,7 +46,6 @@ import { IntegrationHelperService } from '@/runtime/index.ts';
46
46
  import { VoiceProviderRegistry, VoiceService, ensureBuiltinVoiceProviders } from '@pellux/goodvibes-sdk/platform/voice';
47
47
  import { WebSearchProviderRegistry, WebSearchService } from '@pellux/goodvibes-sdk/platform/web-search';
48
48
  import { MemoryEmbeddingProviderRegistry } from '@pellux/goodvibes-sdk/platform/state';
49
- import { PanelManager } from '../panels/panel-manager.ts';
50
49
  import { HookActivityTracker } from '@pellux/goodvibes-sdk/platform/hooks';
51
50
  import { HookDispatcher, createHookWorkbench, type HookWorkbench } from '@pellux/goodvibes-sdk/platform/hooks';
52
51
  import { PluginManager } from '@pellux/goodvibes-sdk/platform/plugins';
@@ -388,7 +387,6 @@ export interface RuntimeServices extends SdkRuntimeServices {
388
387
  readonly runtimeBus: RuntimeEventBus;
389
388
  readonly runtimeStore: RuntimeStore;
390
389
  readonly runtimeDispatch: DomainDispatch;
391
- readonly panelManager: PanelManager;
392
390
  readonly keybindingsManager: KeybindingsManager;
393
391
  readonly routeBindings: RouteBindingManager;
394
392
  readonly surfaceRegistry: SurfaceRegistry;
@@ -488,7 +486,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
488
486
  const featureFlags = options.featureFlags ?? createFeatureFlagManager();
489
487
  const runtimeDispatch = createDomainDispatch(options.runtimeStore);
490
488
  const gatewayMethods = new GatewayMethodCatalog();
491
- const panelManager = new PanelManager();
492
489
  const keybindingsManager = new KeybindingsManager({
493
490
  configPath: shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'keybindings.json'),
494
491
  });
@@ -738,6 +735,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
738
735
  const tokenAuditor = new ApiTokenAuditor({ managed: false });
739
736
  const componentHealthMonitor = new ComponentHealthMonitor();
740
737
  const worktreeRegistry = createDisabledAgentWorktreeRegistry(workingDirectory);
738
+ // Configured and attached to the runtime bus during bootstrap when webhook URLs are present.
741
739
  const webhookNotifier = new WebhookNotifier();
742
740
  const replayEngine = new DeterministicReplayEngine(workingDirectory);
743
741
  const providerOptimizer = new ProviderOptimizer(providerRegistry, providerCapabilityRegistry, false);
@@ -761,9 +759,24 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
761
759
  const modeManager = new ModeManager();
762
760
  const fileUndoManager = new FileUndoManager();
763
761
  const executionLedger = new AgentExecutionLedger(options.runtimeBus);
762
+ // The SDK's foundation/integration contracts still expect a panel manager;
763
+ // the Agent shell has no panel UI (the Activity sidebar replaced it), so we
764
+ // satisfy those contracts with a no-op implementation.
765
+ const NOOP_PANEL_MANAGER = (() => {
766
+ const emptyPane = { panels: [], activeIndex: 0 } as const;
767
+ return {
768
+ getTopPane: () => emptyPane,
769
+ getBottomPane: () => emptyPane,
770
+ getRegisteredTypes: () => [],
771
+ open: () => undefined,
772
+ show: () => {},
773
+ };
774
+ })();
775
+ const panelManager = NOOP_PANEL_MANAGER;
764
776
  const integrationHelpers = new IntegrationHelperService({
765
777
  workingDirectory,
766
778
  homeDirectory,
779
+ panelManager,
767
780
  runtimeStore: options.runtimeStore,
768
781
  runtimeBus: options.runtimeBus,
769
782
  configManager,
@@ -774,7 +787,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
774
787
  distributedRuntime,
775
788
  remoteRunnerRegistry,
776
789
  remoteSupervisor,
777
- panelManager,
778
790
  localUserAuthManager,
779
791
  providerRegistry,
780
792
  serviceRegistry,
@@ -89,6 +89,13 @@ export function allowTerminalWrite<T>(fn: () => T): T {
89
89
  }
90
90
 
91
91
  export function installTerminalOutputGuard(options: TerminalOutputGuardOptions): TerminalOutputGuard {
92
+ // D6 fix: if a guard already exists and hasn't been disposed, dispose it first
93
+ // so the monkeypatch chain is cleanly unwound BEFORE we snapshot the originals.
94
+ // This ensures stdout.write is the real underlying write when we capture it below.
95
+ if (currentGuard !== null) {
96
+ currentGuard.dispose();
97
+ }
98
+
92
99
  const stdout = options.stdout;
93
100
  const stderr = options.stderr ?? process.stderr;
94
101
  const originalStdoutWriteMethod = stdout.write;
@@ -71,7 +71,7 @@ const READ_ONLY_MEMORY_ACTIONS = new Set(['', 'status', 'summary', 'posture', 'm
71
71
  const READ_ONLY_COMPUTER_ACTIONS = new Set(['', 'status', 'summary', 'overview', 'computer', 'computer_use', 'plan', 'route', 'control_plan', 'browser_plan', 'desktop_plan', 'control', 'browser_control', 'desktop', 'desktop_control', 'screenshot', 'screen', 'screen_recording', 'observe', 'browser', 'pwa', 'cockpit', 'browser_cockpit', 'web', 'setup', 'configure', 'browser_desktop_control', 'mcp', 'tools', 'servers', 'mcp_servers']);
72
72
  const READ_ONLY_DEVICE_ACTIONS = new Set(['', 'status', 'map', 'capabilities', 'device', 'devices', 'mobile', 'phone', 'pairing', 'capability', 'route', 'pairing_route', 'show', 'inspect', 'browser', 'pwa', 'cockpit', 'browser_cockpit', 'web', 'control', 'browser_control', 'desktop', 'desktop_control', 'computer_use', 'voice', 'media', 'voice_media', 'workflows', 'provider', 'media_provider', 'voice_provider']);
73
73
  const READ_ONLY_MODELS_ACTIONS = new Set(['', 'status', 'routing', 'routes', 'models', 'model', 'readiness', 'route_readiness', 'route', 'model_route', 'inspect', 'show', 'candidate', 'endpoint', 'local', 'cookbook', 'local_cookbook', 'recipes', 'recipe', 'ollama', 'llama_cpp', 'llamacpp', 'vllm', 'local_servers', 'providers', 'provider_accounts', 'accounts', 'subscriptions', 'auth', 'logins', 'provider', 'provider_account', 'account', 'subscription', 'auth_status']);
74
- const READ_ONLY_WORKSPACE_ACTIONS = new Set(['', 'status', 'summary', 'home', 'workspace', 'categories', 'workspace_categories', 'actions', 'list_actions', 'workspace_actions', 'tasks', 'action', 'show', 'inspect', 'workspace_action', 'surfaces', 'ui_surfaces', 'screens', 'views', 'surface', 'ui_surface', 'screen', 'view', 'panels', 'panes', 'panel', 'pane', 'shortcuts', 'shortcut_help', 'help', 'keybindings', 'bindings', 'keys', 'keybinding', 'binding', 'key', 'commands', 'slash_commands', 'command_catalog', 'command', 'slash_command', 'inspect_command', 'cli_commands', 'cli_catalog', 'cli_command', 'inspect_cli_command']);
74
+ const READ_ONLY_WORKSPACE_ACTIONS = new Set(['', 'status', 'summary', 'home', 'workspace', 'categories', 'workspace_categories', 'actions', 'list_actions', 'workspace_actions', 'tasks', 'action', 'show', 'inspect', 'workspace_action', 'surfaces', 'ui_surfaces', 'screens', 'views', 'surface', 'ui_surface', 'screen', 'view', 'shortcuts', 'shortcut_help', 'help', 'keybindings', 'bindings', 'keys', 'keybinding', 'binding', 'key', 'commands', 'slash_commands', 'command_catalog', 'command', 'slash_command', 'inspect_command', 'cli_commands', 'cli_catalog', 'cli_command', 'inspect_cli_command']);
75
75
  const READ_ONLY_AUTONOMY_ACTIONS = new Set(['', 'intake', 'request', 'route', 'plan', 'triage', 'autonomy_intake', 'queue', 'list', 'work', 'ongoing', 'autonomy_queue', 'item', 'card', 'show', 'inspect', 'autonomy_queue_item', 'status', 'summary', 'overview']);
76
76
  const READ_ONLY_DELEGATION_ACTIONS = new Set(['', 'status', 'summary', 'overview', 'policy', 'decision', 'decisions', 'delegation_posture', 'routes', 'list', 'catalog', 'posture', 'route', 'item', 'card', 'show', 'inspect', 'delegation_route']);
77
77
  const READ_ONLY_EXECUTION_ACTIONS = new Set(['', 'status', 'summary', 'overview', 'routes', 'posture', 'execution_posture', 'route', 'show_route', 'inspect_route', 'execution_route', 'history', 'activity', 'records', 'execution_history', 'record', 'item', 'show', 'inspect', 'execution_history_item', 'processes', 'background', 'backgrounds', 'background_processes', 'capabilities', 'process_capabilities', 'process', 'background_process', 'recovery', 'file_recovery', 'undo_redo']);
@@ -84,7 +84,7 @@ export class ModelPickerDataProvider {
84
84
  */
85
85
  public subscribe(callback: () => void): () => void {
86
86
  this._subscribers.add(callback);
87
- return () => this._subscribers.delete(callback);
87
+ return () => { this._subscribers.delete(callback); };
88
88
  }
89
89
 
90
90
  /**
@@ -18,7 +18,7 @@ import type {
18
18
  ProviderHealthContext,
19
19
  CapabilityFlags,
20
20
  ProviderLatencyStats,
21
- } from '@/runtime/index.ts';
21
+ } from './types.ts';
22
22
 
23
23
  /** Status sort priority (lower = shown first). */
24
24
  const STATUS_ORDER: Record<string, number> = {
@@ -46,7 +46,7 @@ function buildHealthContext(record: ProviderHealthRecord | undefined): ProviderH
46
46
  if (record.stats.totalCalls > 0) {
47
47
  latency = {
48
48
  avgMs: record.stats.avgLatencyMs,
49
- p95Ms: record.stats.maxLatencyMs,
49
+ maxMs: record.stats.maxLatencyMs,
50
50
  minMs: record.stats.minLatencyMs,
51
51
  };
52
52
  }
@@ -32,8 +32,8 @@ export interface CapabilityFlags {
32
32
  export interface ProviderLatencyStats {
33
33
  /** Moving average latency in ms (last N calls). */
34
34
  readonly avgMs: number;
35
- /** Approximate 95th-percentile latency in ms (max of recent observations). */
36
- readonly p95Ms: number;
35
+ /** Maximum observed latency in ms (max of recent observations). */
36
+ readonly maxMs: number;
37
37
  /** Minimum observed latency in ms. */
38
38
  readonly minMs: number;
39
39
  }
@@ -15,7 +15,7 @@ import type {
15
15
  ProviderHealthEntry,
16
16
  HealthTimeline,
17
17
  HealthTimelinePoint,
18
- } from '@/runtime/index.ts';
18
+ } from './types.ts';
19
19
 
20
20
  /**
21
21
  * Number of timeline points retained per provider.
@@ -87,7 +87,7 @@ export class ProviderHealthDataProvider {
87
87
  */
88
88
  public subscribe(callback: () => void): () => void {
89
89
  this._subscribers.add(callback);
90
- return () => this._subscribers.delete(callback);
90
+ return () => { this._subscribers.delete(callback); };
91
91
  }
92
92
 
93
93
  /**
@@ -196,7 +196,7 @@ export class ProviderHealthDataProvider {
196
196
  isConfigured: record.isConfigured,
197
197
  successRate,
198
198
  errorRate,
199
- p95LatencyMs: record.stats.maxLatencyMs,
199
+ maxLatencyMs: record.stats.maxLatencyMs,
200
200
  avgLatencyMs: record.stats.avgLatencyMs,
201
201
  totalCalls: total,
202
202
  // cacheHitRate is populated only when cache-capability is wired to the provider record.
@@ -59,8 +59,8 @@ export interface ProviderHealthEntry {
59
59
  readonly successRate: number;
60
60
  /** Error rate (0–1), derived from totalCalls and errorCalls. */
61
61
  readonly errorRate: number;
62
- /** Approximate p95 latency in ms (max of recent observations). */
63
- readonly p95LatencyMs: number;
62
+ /** Maximum observed latency in ms (max of recent observations). */
63
+ readonly maxLatencyMs: number;
64
64
  /** Moving average latency in ms. */
65
65
  readonly avgLatencyMs: number;
66
66
  /** Total API calls recorded. */
@@ -19,7 +19,6 @@ export interface UiEnvironmentServices {
19
19
 
20
20
  export interface UiShellServices {
21
21
  readonly keybindingsManager: RuntimeServices['keybindingsManager'];
22
- readonly panelManager: RuntimeServices['panelManager'];
23
22
  readonly processManager: RuntimeServices['processManager'];
24
23
  readonly profileManager: RuntimeServices['profileManager'];
25
24
  readonly bookmarkManager: RuntimeServices['bookmarkManager'];
@@ -135,7 +134,6 @@ export function createUiRuntimeServices(
135
134
  },
136
135
  shell: {
137
136
  keybindingsManager: runtimeServices.keybindingsManager,
138
- panelManager: runtimeServices.panelManager,
139
137
  processManager: runtimeServices.processManager,
140
138
  profileManager: runtimeServices.profileManager,
141
139
  bookmarkManager: runtimeServices.bookmarkManager,
@@ -28,7 +28,6 @@ export function createFullscreenCompositeFromLines(
28
28
  viewport: normalizeFullscreenViewport(lines, width, height),
29
29
  footer: [],
30
30
  forceFullRedraw: true,
31
- panelWidth: 0,
32
31
  };
33
32
  }
34
33
 
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Ambient autonomy surfacing module.
3
+ *
4
+ * HISTORY: this factory was extracted from main.ts to keep that file under the
5
+ * 800-line cap. After extraction it gained two capabilities:
6
+ * 1. Calendar merging — listCalendarEvents callback merges upcoming events
7
+ * into the Coming-up sidebar alongside scheduled jobs (buildCalendarEventsLister).
8
+ * 2. Skill-draft accrual — onAwayDigest callback runs skill-draft proposal
9
+ * once per away-digest pass and appends a feed line when drafts are created
10
+ * (buildSkillDraftProposer).
11
+ * These additions are NOT "no behavior change" refactors; they add real surface.
12
+ */
13
+
14
+ import { buildAwayDigest, formatDigestTime, formatRelativeTime } from '../core/away-digest.ts';
15
+ import { LastSeenStore } from '../core/last-seen-store.ts';
16
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
17
+ import { AgentCalendarRegistry } from '../agent/calendar-registry.ts';
18
+ import { AgentSkillRegistry } from '../agent/skill-registry.ts';
19
+ import { runSkillDraftProposer } from '../agent/skill-draft-runner.ts';
20
+ import type { CommandContext } from '../input/command-registry.ts';
21
+
22
+ interface AutomationJobLike {
23
+ readonly name: string;
24
+ readonly enabled: boolean;
25
+ readonly nextRunAt?: number;
26
+ readonly lastRunAt?: number;
27
+ readonly runCount?: number;
28
+ }
29
+
30
+ interface ApprovalLike {
31
+ readonly status: string;
32
+ }
33
+
34
+ interface AutonomyMessageRouter {
35
+ high(message: string): void;
36
+ getFeed(): { push(text: string, priority?: 'high' | 'low', kind?: string): void } | null;
37
+ }
38
+
39
+ export interface AutonomySurfacingOptions {
40
+ readonly shellPaths: Parameters<typeof LastSeenStore.fromShellPaths>[0];
41
+ readonly listAutomationJobs: () => readonly AutomationJobLike[];
42
+ readonly listApprovals: () => readonly ApprovalLike[];
43
+ readonly getTasksSnapshot: () => readonly unknown[];
44
+ readonly router: AutonomyMessageRouter;
45
+ readonly render: () => void;
46
+ /**
47
+ * Optional callback to list upcoming local calendar events.
48
+ * Each item carries a formatted label and a numeric sort key (ms since epoch)
49
+ * so the Coming up section merges calendar and job entries chronologically.
50
+ */
51
+ readonly listCalendarEvents?: () => readonly { label: string; start: number }[];
52
+ /**
53
+ * Optional callback invoked once per announceAwayDigest pass.
54
+ * Should run skill draft proposal and return the count of drafts created.
55
+ * When > 0, a line is appended to the digest feed.
56
+ */
57
+ readonly onAwayDigest?: () => number;
58
+ }
59
+
60
+ const COMING_UP_TTL_MS = 60_000;
61
+ const FETCH_TIMEOUT_MS = 2500;
62
+ const LAST_SEEN_REFRESH_MS = 5 * 60_000;
63
+
64
+ /**
65
+ * Ambient autonomy surfacing for the shell: the launch "While you were away"
66
+ * digest and the sidebar's Coming up entries. Everything here is best-effort
67
+ * and offline-tolerant — failures are silent and renders are never blocked.
68
+ */
69
+ export function createAutonomySurfacing(options: AutonomySurfacingOptions) {
70
+ const lastSeenStore = LastSeenStore.fromShellPaths(options.shellPaths);
71
+
72
+ // Refresh lastSeenAt every 5 minutes so rapid restarts still detect activity.
73
+ const lastSeenRefreshInterval = setInterval(() => {
74
+ lastSeenStore.save();
75
+ }, LAST_SEEN_REFRESH_MS);
76
+ lastSeenRefreshInterval.unref();
77
+
78
+ const comingUpCache: { items: string[]; fetchedAt: number | null; fetching: boolean } = {
79
+ items: [],
80
+ fetchedAt: null,
81
+ fetching: false,
82
+ };
83
+
84
+ function refreshComingUp(): void {
85
+ if (comingUpCache.fetching) return;
86
+ const now = Date.now();
87
+ if (comingUpCache.fetchedAt !== null && now - comingUpCache.fetchedAt < COMING_UP_TTL_MS) return;
88
+ comingUpCache.fetching = true;
89
+ Promise.resolve().then(() => {
90
+ try {
91
+ const jobs = options.listAutomationJobs();
92
+ const jobItems: Array<{ label: string; sortKey: number }> = jobs
93
+ .filter((job) => job.enabled && job.nextRunAt !== undefined && job.nextRunAt > now)
94
+ .sort((a, b) => (a.nextRunAt ?? 0) - (b.nextRunAt ?? 0))
95
+ .map((job) => {
96
+ const when = job.nextRunAt ? formatRelativeTime(job.nextRunAt, now) : '';
97
+ const name = job.name.length > 22 ? `${job.name.slice(0, 20)}…` : job.name;
98
+ return { label: when ? `${name} — ${when}` : name, sortKey: job.nextRunAt ?? 0 };
99
+ });
100
+
101
+ const calItems: Array<{ label: string; sortKey: number }> = [];
102
+ if (options.listCalendarEvents) {
103
+ try {
104
+ for (const ev of options.listCalendarEvents()) {
105
+ calItems.push({ label: ev.label, sortKey: ev.start });
106
+ }
107
+ } catch {
108
+ // Calendar unavailable — skip silently.
109
+ }
110
+ }
111
+
112
+ comingUpCache.items = [...jobItems, ...calItems]
113
+ .sort((a, b) => a.sortKey - b.sortKey)
114
+ .slice(0, 3)
115
+ .map((item) => item.label);
116
+ comingUpCache.fetchedAt = Date.now();
117
+ } catch {
118
+ // Offline or manager unavailable — leave cache as-is.
119
+ } finally {
120
+ comingUpCache.fetching = false;
121
+ }
122
+ }).catch(() => {
123
+ comingUpCache.fetching = false;
124
+ });
125
+ }
126
+
127
+ function announceAwayDigest(): void {
128
+ void (async () => {
129
+ try {
130
+ const lastSeenAt = lastSeenStore.read();
131
+ const withTimeout = <T>(get: () => T) => Promise.race([
132
+ Promise.resolve(get()),
133
+ new Promise<never>((_, reject) => setTimeout(() => reject(new Error('timeout')), FETCH_TIMEOUT_MS)),
134
+ ]);
135
+
136
+ const [jobsResult, tasksResult] = await Promise.allSettled([
137
+ withTimeout(options.listAutomationJobs),
138
+ withTimeout(options.getTasksSnapshot),
139
+ ]);
140
+ const jobs = jobsResult.status === 'fulfilled' ? jobsResult.value : [];
141
+ const allTasks = tasksResult.status === 'fulfilled' ? tasksResult.value : [];
142
+
143
+ const firedSchedules = lastSeenAt !== null
144
+ ? jobs
145
+ .filter((job) => job.lastRunAt !== undefined && job.lastRunAt > lastSeenAt)
146
+ .map((job) => ({ name: job.name, lastRunAt: job.lastRunAt, runCount: job.runCount ?? 0 }))
147
+ : [];
148
+
149
+ const changedTasks = lastSeenAt !== null
150
+ ? allTasks
151
+ .filter((task) => {
152
+ const completedAt = (task as { completedAt?: number }).completedAt;
153
+ return completedAt !== undefined ? completedAt > lastSeenAt : false;
154
+ })
155
+ .map((task) => ({
156
+ title: (task as { title?: string; name?: string }).title
157
+ ?? (task as { name?: string }).name
158
+ ?? 'Task',
159
+ status: (task as { status?: string }).status ?? 'done',
160
+ completedAt: (task as { completedAt?: number }).completedAt,
161
+ }))
162
+ : [];
163
+
164
+ const pendingApprovals = options.listApprovals()
165
+ .filter((approval) => approval.status === 'pending').length;
166
+
167
+ const digest = buildAwayDigest({
168
+ lastSeenAt,
169
+ schedules: firedSchedules,
170
+ tasks: changedTasks,
171
+ pendingApprovals,
172
+ });
173
+
174
+ if (digest !== null) {
175
+ const full = [digest.headline, ...digest.lines].join('\n ');
176
+ options.router.high(`[Status] ${full}`);
177
+ for (const line of digest.lines) {
178
+ options.router.getFeed()?.push(`[Status] ${line}`, 'low', 'schedule');
179
+ }
180
+ options.render();
181
+ }
182
+
183
+ // Invoke optional skill-draft hook once per pass.
184
+ if (options.onAwayDigest) {
185
+ try {
186
+ const drafted = options.onAwayDigest();
187
+ if (drafted > 0) {
188
+ options.router.getFeed()?.push(
189
+ `[Status] I drafted ${drafted} skill${drafted !== 1 ? 's' : ''} from recent work — review them under Memory`,
190
+ 'low',
191
+ 'schedule',
192
+ );
193
+ }
194
+ } catch {
195
+ // Skill draft hook failed — skip silently.
196
+ }
197
+ }
198
+ } catch (err) {
199
+ logger.debug('away-digest failed (non-fatal)', { error: err instanceof Error ? err.message : String(err) });
200
+ }
201
+ })();
202
+ }
203
+
204
+ function stop(): void {
205
+ clearInterval(lastSeenRefreshInterval);
206
+ lastSeenStore.save();
207
+ }
208
+
209
+ return {
210
+ refreshComingUp,
211
+ announceAwayDigest,
212
+ stop,
213
+ // Return a defensive copy so callers cannot mutate internal cache state.
214
+ comingUpItems: (): readonly string[] => [...comingUpCache.items],
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Build the onAwayDigest callback for the given shellPaths and command context.
220
+ * Extracted so main.ts can pass a single symbol instead of an inline lambda.
221
+ * (See module-header note for history.)
222
+ */
223
+ export function buildSkillDraftProposer(
224
+ shellPaths: Parameters<typeof AgentSkillRegistry.fromShellPaths>[0],
225
+ context: CommandContext,
226
+ ): () => number {
227
+ return () =>
228
+ runSkillDraftProposer(
229
+ context,
230
+ AgentSkillRegistry.fromShellPaths(shellPaths),
231
+ ).proposed;
232
+ }
233
+
234
+ /**
235
+ * Build the listCalendarEvents callback for the given shellPaths.
236
+ * Extracts the closure that was previously inlined in main.ts so that
237
+ * file stays under the 800-line cap.
238
+ *
239
+ * The optional `upcomingEvents` parameter is a testability seam: when provided,
240
+ * it replaces the `registry.upcoming(7)` call so unit tests can inject a
241
+ * deterministic event set without mocking the module. Production callers omit
242
+ * this parameter; the default is the real registry lookup.
243
+ */
244
+ export function buildCalendarEventsLister(
245
+ shellPaths: Parameters<typeof LastSeenStore.fromShellPaths>[0],
246
+ upcomingEvents?: () => readonly import('../agent/calendar-registry.ts').AgentCalendarEvent[],
247
+ ): () => readonly { label: string; start: number }[] {
248
+ return () => {
249
+ const now = Date.now();
250
+ const registry = AgentCalendarRegistry.fromShellPaths(shellPaths);
251
+ return (upcomingEvents ?? (() => registry.upcoming(7)))()
252
+ .map((e) => {
253
+ const allDay = e.allDay;
254
+ // Sort key: use local midnight for all-day events so they sort at the
255
+ // correct local calendar date rather than UTC midnight (which would land
256
+ // the previous day for timezones east of UTC).
257
+ // Timed events: parse the ISO string directly; Date.parse handles
258
+ // offset-bearing strings correctly.
259
+ const startMs = allDay
260
+ ? new Date(`${e.start}T00:00:00`).getTime() // no tz suffix ⇒ local time
261
+ : Date.parse(e.start);
262
+ const sortMs = isNaN(startMs) ? now : startMs;
263
+ // Display: format via local Date methods so the user sees their own
264
+ // timezone, never a raw stored offset or UTC-shifted string.
265
+ const label = e.title.length > 22 ? `${e.title.slice(0, 20)}…` : e.title;
266
+ const when = allDay
267
+ ? e.start // date-only string, no time component to misformat
268
+ : formatDigestTime(startMs, now);
269
+ return { label: `${label} — ${when}`, start: sortMs };
270
+ });
271
+ };
272
+ }
@@ -18,15 +18,10 @@ interface ShellRemoteSnapshot {
18
18
  readonly contracts: readonly ShellRemoteContract[];
19
19
  }
20
20
 
21
- interface ShellPanelRecord {
22
- readonly id: string;
23
- }
24
-
25
21
  export function buildShellSessionContinuityHints(
26
22
  sessionSnapshot: ShellSessionSnapshot,
27
23
  tasksSnapshot: ShellTaskSnapshot,
28
24
  remoteSnapshot: ShellRemoteSnapshot,
29
- openPanels: readonly ShellPanelRecord[],
30
25
  ) {
31
26
  return {
32
27
  pendingApprovals: sessionSnapshot.pendingApproval ? 1 : 0,
@@ -34,6 +29,5 @@ export function buildShellSessionContinuityHints(
34
29
  blockedTasks: tasksSnapshot.tasks.filter((task) => task.status === 'blocked').length,
35
30
  remoteContracts: remoteSnapshot.contracts.length,
36
31
  remoteRunners: remoteSnapshot.contracts.slice(0, 4).map((contract) => contract.runnerId),
37
- openPanels: openPanels.map((panel) => panel.id),
38
32
  };
39
33
  }