@pellux/goodvibes-tui 0.18.12 → 0.18.13

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 (157) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/config/index.ts +1 -138
  6. package/src/config/subscription-providers.ts +1 -127
  7. package/src/core/conversation-rendering.ts +3 -3
  8. package/src/core/conversation.ts +176 -423
  9. package/src/core/history.ts +45 -0
  10. package/src/core/orchestrator.ts +3 -735
  11. package/src/core/system-message-router.ts +19 -58
  12. package/src/input/handler-content-actions.ts +2 -2
  13. package/src/input/handler-feed.ts +1 -1
  14. package/src/input/handler-modal-token-routes.ts +1 -1
  15. package/src/input/handler-ui-state.ts +1 -1
  16. package/src/input/handler.ts +1 -1
  17. package/src/input/search.ts +1 -1
  18. package/src/input/selection.ts +2 -2
  19. package/src/main.ts +1 -1
  20. package/src/panels/agent-inspector-panel.ts +3 -3
  21. package/src/panels/agent-logs-panel.ts +3 -3
  22. package/src/panels/approval-panel.ts +2 -2
  23. package/src/panels/automation-control-panel.ts +3 -3
  24. package/src/panels/base-panel.ts +14 -14
  25. package/src/panels/builtin/operations.ts +1 -1
  26. package/src/panels/builtin/session.ts +1 -1
  27. package/src/panels/builtin/shared.ts +3 -3
  28. package/src/panels/cockpit-panel.ts +2 -2
  29. package/src/panels/communication-panel.ts +3 -3
  30. package/src/panels/context-visualizer-panel.ts +2 -2
  31. package/src/panels/control-plane-panel.ts +3 -3
  32. package/src/panels/cost-tracker-panel.ts +3 -3
  33. package/src/panels/debug-panel.ts +2 -2
  34. package/src/panels/diff-panel.ts +2 -2
  35. package/src/panels/docs-panel.ts +1 -1
  36. package/src/panels/eval-panel.ts +2 -2
  37. package/src/panels/file-explorer-panel.ts +3 -3
  38. package/src/panels/file-preview-panel.ts +3 -3
  39. package/src/panels/forensics-panel.ts +2 -2
  40. package/src/panels/git-panel.ts +1 -1
  41. package/src/panels/hooks-panel.ts +3 -3
  42. package/src/panels/incident-review-panel.ts +1 -1
  43. package/src/panels/intelligence-panel.ts +2 -2
  44. package/src/panels/knowledge-panel.ts +1 -1
  45. package/src/panels/local-auth-panel.ts +2 -2
  46. package/src/panels/marketplace-panel.ts +1 -1
  47. package/src/panels/mcp-panel.ts +3 -3
  48. package/src/panels/memory-panel.ts +1 -1
  49. package/src/panels/ops-control-panel.ts +3 -3
  50. package/src/panels/ops-strategy-panel.ts +2 -2
  51. package/src/panels/orchestration-panel.ts +2 -2
  52. package/src/panels/panel-list-panel.ts +6 -6
  53. package/src/panels/plan-dashboard-panel.ts +1 -1
  54. package/src/panels/plugins-panel.ts +2 -2
  55. package/src/panels/policy-panel.ts +2 -2
  56. package/src/panels/polish.ts +3 -3
  57. package/src/panels/provider-accounts-panel.ts +2 -2
  58. package/src/panels/provider-health-panel.ts +2 -2
  59. package/src/panels/provider-stats-panel.ts +3 -3
  60. package/src/panels/remote-panel.ts +3 -3
  61. package/src/panels/routes-panel.ts +3 -3
  62. package/src/panels/sandbox-panel.ts +2 -2
  63. package/src/panels/schedule-panel.ts +1 -1
  64. package/src/panels/security-panel.ts +2 -2
  65. package/src/panels/services-panel.ts +2 -2
  66. package/src/panels/session-browser-panel.ts +2 -2
  67. package/src/panels/settings-sync-panel.ts +2 -2
  68. package/src/panels/skills-panel.ts +6 -6
  69. package/src/panels/subscription-panel.ts +2 -2
  70. package/src/panels/symbol-outline-panel.ts +3 -3
  71. package/src/panels/system-messages-panel.ts +4 -4
  72. package/src/panels/tasks-panel.ts +2 -2
  73. package/src/panels/thinking-panel.ts +3 -3
  74. package/src/panels/token-budget-panel.ts +1 -1
  75. package/src/panels/tool-inspector-panel.ts +3 -3
  76. package/src/panels/types.ts +5 -5
  77. package/src/panels/watchers-panel.ts +3 -3
  78. package/src/panels/welcome-panel.ts +1 -1
  79. package/src/panels/worktree-panel.ts +2 -2
  80. package/src/panels/wrfc-panel.ts +3 -3
  81. package/src/permissions/prompt.ts +3 -22
  82. package/src/plugins/loader.ts +15 -304
  83. package/src/renderer/agent-detail-modal.ts +1 -1
  84. package/src/renderer/autocomplete-overlay.ts +2 -2
  85. package/src/renderer/bookmark-modal.ts +1 -1
  86. package/src/renderer/bottom-bar.ts +2 -2
  87. package/src/renderer/buffer.ts +1 -1
  88. package/src/renderer/code-block.ts +2 -2
  89. package/src/renderer/compositor.ts +2 -2
  90. package/src/renderer/context-inspector.ts +1 -1
  91. package/src/renderer/conversation-layout.ts +2 -2
  92. package/src/renderer/conversation-overlays.ts +1 -1
  93. package/src/renderer/conversation-surface.ts +2 -2
  94. package/src/renderer/diff-view.ts +2 -2
  95. package/src/renderer/diff.ts +1 -1
  96. package/src/renderer/file-picker-overlay.ts +2 -2
  97. package/src/renderer/file-tree.ts +2 -2
  98. package/src/renderer/help-overlay.ts +1 -1
  99. package/src/renderer/history-search-overlay.ts +2 -2
  100. package/src/renderer/live-tail-modal.ts +1 -1
  101. package/src/renderer/markdown.ts +2 -2
  102. package/src/renderer/modal-factory.ts +3 -3
  103. package/src/renderer/model-picker-overlay.ts +2 -2
  104. package/src/renderer/overlay-box.ts +2 -2
  105. package/src/renderer/panel-composite.ts +1 -1
  106. package/src/renderer/panel-picker-overlay.ts +2 -2
  107. package/src/renderer/panel-tab-bar.ts +1 -1
  108. package/src/renderer/panel-workspace-bar.ts +1 -1
  109. package/src/renderer/process-indicator.ts +2 -2
  110. package/src/renderer/process-modal.ts +1 -1
  111. package/src/renderer/profile-picker-modal.ts +2 -2
  112. package/src/renderer/progress.ts +2 -2
  113. package/src/renderer/search-overlay.ts +2 -2
  114. package/src/renderer/selection-modal-overlay.ts +2 -2
  115. package/src/renderer/session-picker-modal.ts +2 -2
  116. package/src/renderer/settings-modal.ts +2 -2
  117. package/src/renderer/shell-surface.ts +1 -1
  118. package/src/renderer/system-message.ts +1 -1
  119. package/src/renderer/tab-strip.ts +2 -2
  120. package/src/renderer/text-layout.ts +1 -1
  121. package/src/renderer/thinking.ts +1 -1
  122. package/src/renderer/tool-call.ts +2 -2
  123. package/src/renderer/ui-factory.ts +2 -2
  124. package/src/runtime/bootstrap-command-context.ts +4 -5
  125. package/src/runtime/bootstrap-command-parts.ts +1 -3
  126. package/src/runtime/bootstrap-core.ts +3 -2
  127. package/src/runtime/bootstrap-hook-bridge.ts +15 -174
  128. package/src/runtime/bootstrap-shell.ts +4 -4
  129. package/src/runtime/bootstrap.ts +1 -1
  130. package/src/runtime/context.ts +4 -20
  131. package/src/runtime/diagnostics/panels/index.ts +1 -1
  132. package/src/runtime/diagnostics/panels/ops.ts +1 -1
  133. package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
  134. package/src/runtime/perf/panel-contracts.ts +32 -0
  135. package/src/runtime/perf/panel-health-monitor.ts +18 -0
  136. package/src/runtime/services.ts +4 -4
  137. package/src/runtime/store/domains/conversation.ts +1 -181
  138. package/src/runtime/store/domains/permissions.ts +1 -143
  139. package/src/runtime/store/helpers/reducers/conversation.ts +1 -228
  140. package/src/runtime/store/helpers/reducers/lifecycle.ts +1 -440
  141. package/src/runtime/store/selectors/index.ts +11 -6
  142. package/src/runtime/store/state.ts +12 -4
  143. package/src/runtime/ui-events.ts +46 -0
  144. package/src/runtime/ui-services.ts +1 -1
  145. package/src/shell/ui-openers.ts +1 -1
  146. package/src/tools/index.ts +1 -186
  147. package/src/types/grid.ts +48 -0
  148. package/src/utils/clipboard.ts +21 -0
  149. package/src/utils/splash-lines.ts +1 -1
  150. package/src/utils/terminal-width.ts +185 -0
  151. package/src/version.ts +1 -1
  152. package/src/daemon/facade-composition.ts +0 -398
  153. package/src/daemon/facade.ts +0 -638
  154. package/src/daemon/surface-policy.ts +0 -60
  155. package/src/daemon/types.ts +0 -191
  156. package/src/runtime/ui-read-models-core.ts +0 -95
  157. package/src/runtime/ui-read-models-operations.ts +0 -203
@@ -1,638 +0,0 @@
1
- import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
2
- import { jsonErrorResponse } from '@pellux/goodvibes-sdk/platform/daemon/http/error-response';
3
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
4
- import { AgentManager } from '@pellux/goodvibes-sdk/platform/tools/agent/index';
5
- import type { AgentRecord } from '@pellux/goodvibes-sdk/platform/tools/agent/index';
6
- import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
7
- import type { ServiceRegistry } from '../config/service-registry.ts';
8
- import type { UserAuthManager } from '@pellux/goodvibes-sdk/platform/security/user-auth';
9
- import type {
10
- AutomationDeliveryManager,
11
- AutomationManager,
12
- } from '@pellux/goodvibes-sdk/platform/automation/index';
13
- import type { ApprovalBroker, ControlPlaneGateway, SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane/index';
14
- import type { GatewayMethodCatalog } from '@pellux/goodvibes-sdk/platform/control-plane/index';
15
- import type {
16
- BuiltinChannelRuntime,
17
- ChannelReplyPipeline,
18
- ChannelProviderRuntimeManager,
19
- ChannelPluginRegistry,
20
- ChannelPolicyManager,
21
- RouteBindingManager,
22
- SurfaceRegistry,
23
- ChannelSurface,
24
- } from '@pellux/goodvibes-sdk/platform/channels/index';
25
- import type { RuntimeEventBus } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
26
- import type { PlatformServiceManager } from '@pellux/goodvibes-sdk/platform/daemon/service-manager';
27
- import type { WatcherRegistry } from '@pellux/goodvibes-sdk/platform/watchers/index';
28
- import { type DistributedPeerAuth } from '@pellux/goodvibes-sdk/platform/runtime/remote/index';
29
- import type { KnowledgeGraphqlService, KnowledgeService } from '@pellux/goodvibes-sdk/platform/knowledge/index';
30
- import type { IntegrationHelperService } from '@pellux/goodvibes-sdk/platform/runtime/integration/helpers';
31
- import type { DaemonControlPlaneHelper, ControlPlaneWebSocketData } from '@pellux/goodvibes-sdk/platform/daemon/control-plane';
32
- import type { DaemonSurfaceDeliveryHelper } from '@pellux/goodvibes-sdk/platform/daemon/surface-delivery';
33
- import type { DaemonSurfaceActionHelper } from '@pellux/goodvibes-sdk/platform/daemon/surface-actions';
34
- import type { DaemonTransportEventsHelper } from '@pellux/goodvibes-sdk/platform/daemon/transport-events';
35
- import type { DaemonHttpRouter } from '@pellux/goodvibes-sdk/platform/daemon/http/router';
36
- import { isSurfaceDeliveryEnabled } from './surface-policy.ts';
37
- import {
38
- configureDaemonSessionContinuation,
39
- createDaemonFacadeCollaborators,
40
- resolveDaemonFacadeRuntime,
41
- } from './facade-composition.ts';
42
- import {
43
- GlobalNetworkTransportInstaller,
44
- resolveInboundTlsContext,
45
- type ResolvedInboundTlsContext,
46
- } from '@pellux/goodvibes-sdk/platform/runtime/network/index';
47
- import { createRuntimeServices, type RuntimeServices } from '../runtime/services.ts';
48
- import {
49
- readAutomationReasoningEffort,
50
- readAutomationWakeMode,
51
- readExternalContentSource,
52
- readStringList,
53
- } from '@pellux/goodvibes-sdk/platform/daemon/helpers';
54
- import type { DaemonConfig, DaemonDangerConfig, PendingSurfaceReply } from './types.ts';
55
-
56
- interface UpgradeCapableServer {
57
- upgrade(req: Request, options?: { data?: unknown }): boolean;
58
- }
59
-
60
- type JsonBody = Record<string, unknown>;
61
-
62
- // ---------------------------------------------------------------------------
63
- // DaemonServer
64
- // ---------------------------------------------------------------------------
65
-
66
- /**
67
- * DaemonServer — HTTP task server, disabled by default.
68
- *
69
- * Enable via: danger.daemon = true in config.
70
- * All routes require Bearer token auth (set via enable()).
71
- * POST /task — submit a task; returns agentId.
72
- * GET /task/:id — returns agent status.
73
- * GET /status — server health check.
74
- */
75
- export class DaemonServer {
76
- private enabled = false;
77
- private server: ReturnType<typeof Bun.serve> | null = null;
78
- private port: number;
79
- private host: string;
80
- private agentManager: AgentManager;
81
- private readonly runtimeServices: RuntimeServices;
82
- private readonly integrationHelpers: IntegrationHelperService;
83
- private configManager: ConfigManager;
84
- private authToken: string | null = null;
85
- private userAuth: UserAuthManager;
86
- private githubWebhookSecret: string | null;
87
- private automationManager: AutomationManager;
88
- private runtimeBus: RuntimeEventBus;
89
- private readonly runtimeStore: RuntimeServices['runtimeStore'];
90
- private readonly runtimeDispatch: RuntimeServices['runtimeDispatch'];
91
- private readonly controlPlaneGateway: ControlPlaneGateway;
92
- private readonly gatewayMethods: GatewayMethodCatalog;
93
- private readonly sessionBroker: SharedSessionBroker;
94
- private readonly approvalBroker: ApprovalBroker;
95
- private readonly routeBindings: RouteBindingManager;
96
- private readonly deliveryManager: AutomationDeliveryManager;
97
- private readonly surfaceRegistry: SurfaceRegistry;
98
- private readonly channelPolicy: ChannelPolicyManager;
99
- private readonly channelPlugins: ChannelPluginRegistry;
100
- private readonly channelReplyPipeline: ChannelReplyPipeline;
101
- private readonly providerRuntime: ChannelProviderRuntimeManager;
102
- private readonly builtinChannels: BuiltinChannelRuntime;
103
- private readonly watcherRegistry: WatcherRegistry;
104
- private readonly platformServiceManager: PlatformServiceManager;
105
- private readonly distributedRuntime: RuntimeServices['distributedRuntime'];
106
- private readonly voiceService: RuntimeServices['voiceService'];
107
- private readonly webSearchService: RuntimeServices['webSearchService'];
108
- private readonly knowledgeService: KnowledgeService;
109
- private readonly knowledgeGraphqlService: KnowledgeGraphqlService;
110
- private readonly mediaProviders: RuntimeServices['mediaProviders'];
111
- private readonly multimodalService: RuntimeServices['multimodalService'];
112
- private readonly artifactStore: RuntimeServices['artifactStore'];
113
- private readonly serviceRegistry: ServiceRegistry;
114
- private readonly serveFactory: typeof Bun.serve;
115
- private readonly pendingSurfaceReplies = new Map<string, PendingSurfaceReply>();
116
- private readonly controlPlaneHelper: DaemonControlPlaneHelper;
117
- private readonly surfaceDeliveryHelper: DaemonSurfaceDeliveryHelper;
118
- private readonly surfaceActionHelper: DaemonSurfaceActionHelper;
119
- private readonly transportEventsHelper: DaemonTransportEventsHelper;
120
- private readonly httpRouter: DaemonHttpRouter;
121
- private replyPoller: ReturnType<typeof setInterval> | null = null;
122
- private tlsState: ResolvedInboundTlsContext | null = null;
123
- private approvalBrokerUnsubscribe: (() => void) | null = null;
124
-
125
- constructor(private config: DaemonConfig = {}, _configManager?: ConfigManager) {
126
- const resolved = resolveDaemonFacadeRuntime(config, _configManager);
127
- this.configManager = resolved.configManager;
128
- this.runtimeServices = resolved.runtimeServices;
129
- this.integrationHelpers = resolved.integrationHelpers;
130
- this.port = resolved.port;
131
- this.host = resolved.host;
132
- this.agentManager = resolved.agentManager;
133
- this.userAuth = resolved.userAuth;
134
- this.automationManager = resolved.automationManager;
135
- this.runtimeBus = resolved.runtimeBus;
136
- this.runtimeStore = resolved.runtimeStore;
137
- this.runtimeDispatch = resolved.runtimeDispatch;
138
- this.controlPlaneGateway = resolved.controlPlaneGateway;
139
- this.gatewayMethods = resolved.gatewayMethods;
140
- this.sessionBroker = resolved.sessionBroker;
141
- this.approvalBroker = resolved.approvalBroker;
142
- this.routeBindings = resolved.routeBindings;
143
- this.deliveryManager = resolved.deliveryManager;
144
- this.surfaceRegistry = resolved.surfaceRegistry;
145
- this.channelPolicy = resolved.channelPolicy;
146
- this.channelPlugins = resolved.channelPlugins;
147
- this.watcherRegistry = resolved.watcherRegistry;
148
- this.platformServiceManager = resolved.platformServiceManager;
149
- this.distributedRuntime = resolved.distributedRuntime;
150
- this.voiceService = resolved.voiceService;
151
- this.webSearchService = resolved.webSearchService;
152
- this.knowledgeService = resolved.knowledgeService;
153
- this.knowledgeGraphqlService = resolved.knowledgeGraphqlService;
154
- this.mediaProviders = resolved.mediaProviders;
155
- this.multimodalService = resolved.multimodalService;
156
- this.artifactStore = resolved.artifactStore;
157
- this.serviceRegistry = resolved.serviceRegistry;
158
- this.serveFactory = resolved.serveFactory;
159
- this.githubWebhookSecret = resolved.githubWebhookSecret;
160
-
161
- const collaborators = createDaemonFacadeCollaborators({
162
- runtime: resolved,
163
- pendingSurfaceReplies: this.pendingSurfaceReplies,
164
- authToken: () => this.authToken,
165
- trustProxyEnabled: () => this.trustProxyEnabled(),
166
- dispatchApiRoutes: (req) => this.dispatchApiRoutes(req),
167
- parseJsonBody: (req) => this.parseJsonBody(req),
168
- requireAuthenticatedSession: (req) => this.requireAuthenticatedSession(req),
169
- trySpawnAgent: (input, logLabel, sessionId) => this.trySpawnAgent(input, logLabel, sessionId),
170
- checkAuth: (req) => this.checkAuth(req),
171
- extractAuthToken: (req) => this.extractAuthToken(req),
172
- requireAdmin: (req) => this.requireAdmin(req),
173
- requireRemotePeer: (req, scope) => this.requireRemotePeer(req, scope),
174
- describeAuthenticatedPrincipal: (token) => this.describeAuthenticatedPrincipal(token),
175
- invokeGatewayMethodCall: (input) => this.invokeGatewayMethodCall(input),
176
- syncSpawnedAgentTask: (record, sessionId) => this.syncSpawnedAgentTask(record, sessionId),
177
- syncFinishedAgentTask: (record) => this.syncFinishedAgentTask(record),
178
- surfaceDeliveryEnabled: (surface) => this.surfaceDeliveryEnabled(surface),
179
- signWebhookPayload: (body, secret) => this.signWebhookPayload(body, secret),
180
- handleApprovalAction: (approvalId, action, req) => this.handleApprovalAction(approvalId, action, req),
181
- tlsState: () => this.tlsState,
182
- });
183
- this.channelReplyPipeline = collaborators.channelReplyPipeline;
184
- this.controlPlaneHelper = collaborators.controlPlaneHelper;
185
- this.surfaceDeliveryHelper = collaborators.surfaceDeliveryHelper;
186
- this.surfaceActionHelper = collaborators.surfaceActionHelper;
187
- this.transportEventsHelper = collaborators.transportEventsHelper;
188
- this.httpRouter = collaborators.httpRouter;
189
- this.providerRuntime = collaborators.providerRuntime;
190
- this.builtinChannels = collaborators.builtinChannels;
191
-
192
- configureDaemonSessionContinuation({
193
- sessionBroker: this.sessionBroker,
194
- trySpawnAgent: (input, logLabel, sessionId) => this.trySpawnAgent(input, logLabel, sessionId),
195
- queueSurfaceReplyFromBinding: (binding, input) => this.surfaceDeliveryHelper.queueSurfaceReplyFromBinding(binding, input),
196
- });
197
-
198
- this.distributedRuntime.attachRuntime({
199
- sessionBridge: this.sessionBroker,
200
- approvalBridge: this.approvalBroker,
201
- automationBridge: this.automationManager,
202
- eventPublisher: (event, payload) => {
203
- this.controlPlaneGateway.publishEvent(event, payload);
204
- },
205
- });
206
- }
207
-
208
- listRecentControlPlaneEvents(limit = 100): readonly import('@pellux/goodvibes-sdk/platform/control-plane/gateway').ControlPlaneRecentEvent[] {
209
- return this.controlPlaneGateway.listRecentEvents(limit);
210
- }
211
-
212
- /**
213
- * Enable the daemon. Requires danger.daemon = true in config.
214
- * The provided token is used to authenticate all incoming requests.
215
- * Returns true if enabled, false if the config forbids it.
216
- */
217
- enable(dangerConfig: DaemonDangerConfig, token?: string): boolean {
218
- if (!dangerConfig.daemon) {
219
- logger.info('DaemonServer.enable: danger.daemon is false — not enabling');
220
- return false;
221
- }
222
- this.enabled = true;
223
- this.authToken = token ?? null;
224
- this.controlPlaneGateway.setServerState({ enabled: true, host: this.host, port: this.port });
225
- return true;
226
- }
227
-
228
- /**
229
- * Start the daemon. Refuses to start if not explicitly enabled.
230
- */
231
- async start(): Promise<void> {
232
- if (!this.enabled) {
233
- logger.info('Daemon mode is disabled. Enable via danger.daemon config.');
234
- return;
235
- }
236
- if (this.authToken === null) {
237
- logger.info('DaemonServer: starting with session-based authentication via UserAuth');
238
- }
239
- if (this.server !== null) {
240
- logger.info('DaemonServer: already running');
241
- return;
242
- }
243
-
244
- new GlobalNetworkTransportInstaller().install(this.configManager);
245
- if (!this.approvalBrokerUnsubscribe) {
246
- this.approvalBrokerUnsubscribe = this.approvalBroker.subscribe((approval) => {
247
- void this.surfaceDeliveryHelper.notifyApprovalUpdate(approval);
248
- });
249
- }
250
- this.routeBindings.attachRuntime({
251
- runtimeBus: this.runtimeBus,
252
- runtimeStore: this.runtimeStore,
253
- });
254
- this.surfaceRegistry.attachRuntime(this.runtimeStore);
255
- this.deliveryManager.attachRuntime({
256
- runtimeBus: this.runtimeBus,
257
- runtimeStore: this.runtimeStore,
258
- });
259
- this.automationManager.attachRuntime({
260
- runtimeBus: this.runtimeBus,
261
- runtimeStore: this.runtimeStore,
262
- deliveryManager: this.deliveryManager,
263
- });
264
- this.controlPlaneGateway.attachRuntime({
265
- runtimeBus: this.runtimeBus,
266
- runtimeStore: this.runtimeStore,
267
- });
268
-
269
- const self = this;
270
- this.transportEventsHelper.emitTransportInitializing();
271
- try {
272
- this.tlsState = resolveInboundTlsContext(this.configManager, 'controlPlane');
273
- this.server = this.serveFactory({
274
- port: this.port,
275
- hostname: this.host,
276
- ...(this.tlsState.tls ? { tls: this.tlsState.tls } : {}),
277
- async fetch(req: Request, server: UpgradeCapableServer): Promise<Response | undefined> {
278
- const upgrade = self.tryUpgradeControlPlaneWebSocket(req, server);
279
- if (upgrade === 'upgraded') return;
280
- if (upgrade) return upgrade;
281
- return self.handleRequest(req);
282
- },
283
- websocket: {
284
- open(ws) {
285
- self.handleControlPlaneWebSocketOpen(ws as unknown as { data: ControlPlaneWebSocketData; send(message: string): void });
286
- },
287
- message(ws, message) {
288
- void self.handleControlPlaneWebSocketMessage(
289
- ws as unknown as { data: ControlPlaneWebSocketData; send(message: string): void },
290
- message,
291
- );
292
- },
293
- close(ws) {
294
- self.handleControlPlaneWebSocketClose(ws as unknown as { data: ControlPlaneWebSocketData });
295
- },
296
- },
297
- });
298
-
299
- await Promise.all([
300
- this.sessionBroker.start(),
301
- this.approvalBroker.start(),
302
- this.channelPolicy.start(),
303
- this.automationManager.start(),
304
- this.distributedRuntime.start(),
305
- ]);
306
- await this.providerRuntime.startConfigured();
307
- if (this.replyPoller === null) {
308
- this.replyPoller = setInterval(() => {
309
- void this.pollPendingSurfaceReplies();
310
- }, 2_000);
311
- }
312
- this.surfaceRegistry.syncConfiguredSurfaces();
313
- if (this.configManager.get('watchers.enabled')) {
314
- this.watcherRegistry.registerPollingWatcher({
315
- id: 'daemon-heartbeat',
316
- label: 'Daemon heartbeat',
317
- source: {
318
- id: 'source:daemon-heartbeat',
319
- kind: 'watcher',
320
- label: 'Daemon heartbeat',
321
- enabled: true,
322
- createdAt: Date.now(),
323
- updatedAt: Date.now(),
324
- metadata: {},
325
- },
326
- intervalMs: Number(this.configManager.get('watchers.heartbeatIntervalMs') ?? 30_000),
327
- run: () => new Date().toISOString(),
328
- });
329
- this.watcherRegistry.startWatcher('daemon-heartbeat');
330
- }
331
- this.controlPlaneGateway.setServerState({ enabled: true, host: this.host, port: this.port });
332
- this.transportEventsHelper.emitTransportConnected();
333
- logger.info('DaemonServer started', {
334
- port: this.port,
335
- host: this.host,
336
- tlsMode: this.tlsState.mode,
337
- scheme: this.tlsState.scheme,
338
- trustProxy: this.tlsState.trustProxy,
339
- });
340
- } catch (err) {
341
- const message = summarizeError(err);
342
- if (this.replyPoller !== null) {
343
- clearInterval(this.replyPoller);
344
- this.replyPoller = null;
345
- }
346
- this.pendingSurfaceReplies.clear();
347
- this.automationManager.stop();
348
- this.providerRuntime.stopAll();
349
- this.watcherRegistry.stopWatcher('daemon-heartbeat', 'daemon-start-failed');
350
- this.approvalBrokerUnsubscribe?.();
351
- this.approvalBrokerUnsubscribe = null;
352
- if (this.server !== null) {
353
- this.server.stop(true);
354
- this.server = null;
355
- }
356
- this.tlsState = null;
357
- this.controlPlaneGateway.setServerState({ enabled: this.enabled, host: this.host, port: this.port });
358
- this.transportEventsHelper.emitTransportTerminalFailure(message);
359
- throw err;
360
- }
361
- }
362
-
363
- /**
364
- * Stop the daemon server.
365
- */
366
- async stop(): Promise<void> {
367
- if (this.server === null) return;
368
- this.automationManager.stop();
369
- this.providerRuntime.stopAll();
370
- this.watcherRegistry.stopWatcher('daemon-heartbeat', 'daemon-stopped');
371
- if (this.replyPoller !== null) {
372
- clearInterval(this.replyPoller);
373
- this.replyPoller = null;
374
- }
375
- this.pendingSurfaceReplies.clear();
376
- this.approvalBrokerUnsubscribe?.();
377
- this.approvalBrokerUnsubscribe = null;
378
- this.httpRouter.dispose();
379
- this.server.stop(true);
380
- this.server = null;
381
- this.tlsState = null;
382
- this.controlPlaneGateway.setServerState({ enabled: this.enabled, host: this.host, port: this.port });
383
- this.transportEventsHelper.emitTransportDisconnected('Daemon server stopped', false);
384
- logger.info('DaemonServer stopped');
385
- }
386
-
387
- /**
388
- * Returns true if the server is currently running.
389
- */
390
- get isRunning(): boolean {
391
- return this.server !== null;
392
- }
393
-
394
- // -------------------------------------------------------------------------
395
- // Auth
396
- // -------------------------------------------------------------------------
397
-
398
- private extractAuthToken(req: Request): string {
399
- return this.controlPlaneHelper.extractAuthToken(req);
400
- }
401
-
402
- private checkAuth(req: Request): boolean {
403
- return this.controlPlaneHelper.checkAuth(req);
404
- }
405
-
406
- private requireAuthenticatedSession(req: Request): { username: string; roles: readonly string[] } | null {
407
- return this.controlPlaneHelper.requireAuthenticatedSession(req);
408
- }
409
-
410
- private requireAdmin(req: Request): Response | null {
411
- return this.controlPlaneHelper.requireAdmin(req);
412
- }
413
-
414
- private async requireRemotePeer(req: Request, scope?: string): Promise<DistributedPeerAuth | Response> {
415
- return await this.controlPlaneHelper.requireRemotePeer(req, scope);
416
- }
417
-
418
- private describeAuthenticatedPrincipal(token: string): {
419
- principalId: string;
420
- principalKind: 'user' | 'bot' | 'service' | 'token';
421
- admin: boolean;
422
- scopes: readonly string[];
423
- } | null {
424
- return this.controlPlaneHelper.describeAuthenticatedPrincipal(token);
425
- }
426
-
427
- private getGrantedGatewayScopes(includeWrite: boolean): readonly string[] {
428
- return this.controlPlaneHelper.getGrantedGatewayScopes(includeWrite);
429
- }
430
-
431
- private validateGatewayInvocation(
432
- descriptor: import('@pellux/goodvibes-sdk/platform/control-plane/index').GatewayMethodDescriptor,
433
- context?: {
434
- readonly principalKind?: 'user' | 'bot' | 'service' | 'token' | 'remote-peer';
435
- readonly scopes?: readonly string[];
436
- readonly admin?: boolean;
437
- },
438
- ): { status: number; ok: false; body: Record<string, unknown> } | null {
439
- return this.controlPlaneHelper.validateGatewayInvocation(descriptor, context);
440
- }
441
-
442
- private tryUpgradeControlPlaneWebSocket(
443
- req: Request,
444
- server: UpgradeCapableServer,
445
- ): Response | 'upgraded' | null {
446
- return this.controlPlaneHelper.tryUpgradeControlPlaneWebSocket(req, server);
447
- }
448
-
449
- private handleControlPlaneWebSocketOpen(ws: {
450
- data: import('@pellux/goodvibes-sdk/platform/daemon/control-plane').ControlPlaneWebSocketData;
451
- send(message: string): void;
452
- }): void {
453
- this.controlPlaneHelper.handleControlPlaneWebSocketOpen(ws);
454
- }
455
-
456
- private async handleControlPlaneWebSocketMessage(
457
- ws: {
458
- data: import('@pellux/goodvibes-sdk/platform/daemon/control-plane').ControlPlaneWebSocketData;
459
- send(message: string): void;
460
- },
461
- message: string | Buffer | ArrayBuffer | Uint8Array,
462
- ): Promise<void> {
463
- await this.controlPlaneHelper.handleControlPlaneWebSocketMessage(ws, message);
464
- }
465
-
466
- private handleControlPlaneWebSocketClose(ws: {
467
- data: import('@pellux/goodvibes-sdk/platform/daemon/control-plane').ControlPlaneWebSocketData;
468
- }): void {
469
- this.controlPlaneHelper.handleControlPlaneWebSocketClose(ws);
470
- }
471
-
472
- private async invokeWebSocketControlPlaneCall(input: {
473
- readonly authToken: string;
474
- readonly method: string;
475
- readonly path: string;
476
- readonly query?: Record<string, unknown>;
477
- readonly body?: unknown;
478
- readonly context?: {
479
- readonly principalKind?: 'user' | 'bot' | 'service' | 'token' | 'remote-peer';
480
- readonly admin?: boolean;
481
- readonly scopes?: readonly string[];
482
- };
483
- }): Promise<{ status: number; ok: boolean; body: unknown }> {
484
- return await this.controlPlaneHelper.invokeWebSocketControlPlaneCall(input);
485
- }
486
-
487
- private async invokeGatewayMethodCall(input: {
488
- readonly authToken: string;
489
- readonly methodId: string;
490
- readonly query?: Record<string, unknown>;
491
- readonly body?: unknown;
492
- readonly context?: {
493
- readonly principalId?: string;
494
- readonly principalKind?: 'user' | 'bot' | 'service' | 'token' | 'remote-peer';
495
- readonly admin?: boolean;
496
- readonly scopes?: readonly string[];
497
- readonly clientKind?: string;
498
- };
499
- }): Promise<{ status: number; ok: boolean; body: unknown }> {
500
- return await this.controlPlaneHelper.invokeGatewayMethodCall(input);
501
- }
502
-
503
- // -------------------------------------------------------------------------
504
- // Request handling
505
- // -------------------------------------------------------------------------
506
-
507
- private async handleRequest(req: Request): Promise<Response> {
508
- return await this.httpRouter.handleRequest(req);
509
- }
510
-
511
- private async dispatchApiRoutes(req: Request): Promise<Response | null> {
512
- return await this.httpRouter.dispatchApiRoutes(req);
513
- }
514
-
515
- private async parseJsonBody(req: Request): Promise<JsonBody | Response> {
516
- return await this.httpRouter.parseJsonBody(req);
517
- }
518
-
519
- private async parseOptionalJsonBody(req: Request): Promise<JsonBody | null | Response> {
520
- return await this.httpRouter.parseOptionalJsonBody(req);
521
- }
522
-
523
- private parseJsonText(rawBody: string): JsonBody | Response {
524
- return this.httpRouter.parseJsonText(rawBody);
525
- }
526
-
527
- private recordApiResponse(
528
- req: Request,
529
- path: string,
530
- response: Response,
531
- clientKind:
532
- | 'web'
533
- | 'slack'
534
- | 'discord'
535
- | 'ntfy'
536
- | 'webhook'
537
- | 'telegram'
538
- | 'google-chat'
539
- | 'signal'
540
- | 'whatsapp'
541
- | 'imessage'
542
- | 'msteams'
543
- | 'bluebubbles'
544
- | 'mattermost'
545
- | 'matrix'
546
- | 'daemon' = 'web',
547
- ): Response {
548
- return this.httpRouter.recordApiResponse(req, path, response, clientKind);
549
- }
550
- private async handleApprovalAction(
551
- approvalId: string,
552
- action: 'claim' | 'approve' | 'deny' | 'cancel',
553
- req: Request,
554
- ): Promise<Response> {
555
- const body = await this.parseOptionalJsonBody(req);
556
- const payload = body instanceof Response || body === null ? {} as JsonBody : body;
557
- const actor = this.requireAuthenticatedSession(req)?.username ?? (this.authToken ? 'shared-token' : 'operator');
558
- const note = typeof payload.note === 'string' ? payload.note : undefined;
559
- if (action === 'claim') {
560
- const approval = await this.approvalBroker.claimApproval(approvalId, actor, 'web', note);
561
- return approval
562
- ? this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
563
- : this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ error: 'Unknown approval' }, { status: 404 }));
564
- }
565
- if (action === 'cancel') {
566
- const approval = await this.approvalBroker.cancelApproval(approvalId, actor, 'web', note);
567
- return approval
568
- ? this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
569
- : this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ error: 'Unknown approval' }, { status: 404 }));
570
- }
571
- const approval = await this.approvalBroker.resolveApproval(approvalId, {
572
- approved: action === 'approve',
573
- remember: typeof payload.remember === 'boolean' ? payload.remember : false,
574
- actor,
575
- actorSurface: 'web',
576
- note,
577
- });
578
- return approval
579
- ? this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
580
- : this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ error: 'Unknown approval' }, { status: 404 }));
581
- }
582
- private trySpawnAgent(input: Parameters<AgentManager['spawn']>[0], logLabel = 'DaemonServer', sessionId?: string): AgentRecord | Response {
583
- try {
584
- const spawnInput = Array.isArray((input as { tools?: readonly string[] }).tools)
585
- ? {
586
- ...input,
587
- tools: [...((input as { tools?: readonly string[] }).tools ?? [])],
588
- }
589
- : input;
590
- const record = this.agentManager.spawn(spawnInput);
591
- this.syncSpawnedAgentTask(record, sessionId);
592
- return record;
593
- } catch (err) {
594
- const message = summarizeError(err);
595
- logger.error(`${logLabel}: agent spawn failed`, { error: message });
596
- return jsonErrorResponse(err, { status: 500, fallbackMessage: 'Failed to spawn agent' });
597
- }
598
- }
599
- private syncSpawnedAgentTask(record: AgentRecord, sessionId?: string): void {
600
- this.runtimeDispatch?.syncRuntimeTask({
601
- id: record.id,
602
- kind: 'agent',
603
- title: record.task.length > 80 ? `${record.task.slice(0, 77)}...` : record.task,
604
- description: record.task,
605
- status: record.status === 'pending' ? 'queued' : 'running',
606
- owner: record.id,
607
- cancellable: true,
608
- childTaskIds: [],
609
- queuedAt: record.startedAt,
610
- startedAt: record.status === 'pending' ? undefined : record.startedAt,
611
- correlationId: sessionId,
612
- }, 'daemon.server.agent-spawn');
613
- }
614
- private syncFinishedAgentTask(record: AgentRecord): void {
615
- const status = record.status === 'completed'
616
- ? 'completed'
617
- : record.status === 'failed'
618
- ? 'failed'
619
- : 'cancelled';
620
- this.runtimeDispatch?.transitionRuntimeTask(record.id, status, {
621
- endedAt: record.completedAt ?? Date.now(),
622
- result: record.fullOutput ?? record.streamingContent,
623
- error: record.error,
624
- }, 'daemon.server.agent-finish');
625
- }
626
- private surfaceDeliveryEnabled(surface: 'slack' | 'discord' | 'ntfy' | 'webhook' | 'telegram' | 'google-chat' | 'signal' | 'whatsapp' | 'imessage' | 'msteams' | 'bluebubbles' | 'mattermost' | 'matrix'): boolean {
627
- return isSurfaceDeliveryEnabled(this.configManager, surface);
628
- }
629
- private async pollPendingSurfaceReplies(): Promise<void> {
630
- await this.surfaceDeliveryHelper.pollPendingSurfaceReplies((record) => this.syncFinishedAgentTask(record));
631
- }
632
- private trustProxyEnabled(): boolean {
633
- return this.tlsState?.trustProxy ?? Boolean(this.configManager.get('controlPlane.trustProxy'));
634
- }
635
- private signWebhookPayload(body: string, secret: string): string {
636
- return this.surfaceDeliveryHelper.signWebhookPayload(body, secret);
637
- }
638
- }