@pellux/goodvibes-daemon-sdk 0.18.3 → 0.30.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 (82) hide show
  1. package/README.md +13 -7
  2. package/dist/api-router.d.ts +8 -1
  3. package/dist/api-router.d.ts.map +1 -1
  4. package/dist/api-router.js +12 -3
  5. package/dist/artifact-upload.d.ts +26 -0
  6. package/dist/artifact-upload.d.ts.map +1 -0
  7. package/dist/artifact-upload.js +535 -0
  8. package/dist/automation.d.ts +2 -2
  9. package/dist/automation.d.ts.map +1 -1
  10. package/dist/channel-route-types.d.ts.map +1 -1
  11. package/dist/channel-routes.d.ts +2 -2
  12. package/dist/channel-routes.d.ts.map +1 -1
  13. package/dist/channel-routes.js +28 -3
  14. package/dist/context.d.ts +151 -51
  15. package/dist/context.d.ts.map +1 -1
  16. package/dist/control-routes.d.ts +2 -2
  17. package/dist/control-routes.d.ts.map +1 -1
  18. package/dist/error-response.d.ts.map +1 -1
  19. package/dist/error-response.js +172 -12
  20. package/dist/http-policy.d.ts +1 -1
  21. package/dist/http-policy.d.ts.map +1 -1
  22. package/dist/http-policy.js +0 -1
  23. package/dist/index.d.ts +8 -4
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -1
  26. package/dist/integration-route-types.d.ts +3 -1
  27. package/dist/integration-route-types.d.ts.map +1 -1
  28. package/dist/integration-routes.d.ts +2 -2
  29. package/dist/integration-routes.d.ts.map +1 -1
  30. package/dist/integration-routes.js +3 -0
  31. package/dist/knowledge-refinement-routes.d.ts +4 -0
  32. package/dist/knowledge-refinement-routes.d.ts.map +1 -0
  33. package/dist/knowledge-refinement-routes.js +58 -0
  34. package/dist/knowledge-route-types.d.ts +11 -1
  35. package/dist/knowledge-route-types.d.ts.map +1 -1
  36. package/dist/knowledge-routes.d.ts +2 -2
  37. package/dist/knowledge-routes.d.ts.map +1 -1
  38. package/dist/knowledge-routes.js +156 -13
  39. package/dist/media-route-types.d.ts +10 -0
  40. package/dist/media-route-types.d.ts.map +1 -1
  41. package/dist/media-routes.d.ts +2 -2
  42. package/dist/media-routes.d.ts.map +1 -1
  43. package/dist/media-routes.js +134 -15
  44. package/dist/operator.d.ts +2 -2
  45. package/dist/operator.d.ts.map +1 -1
  46. package/dist/operator.js +49 -15
  47. package/dist/otlp-protobuf.d.ts +3 -0
  48. package/dist/otlp-protobuf.d.ts.map +1 -0
  49. package/dist/otlp-protobuf.js +977 -0
  50. package/dist/remote-routes.d.ts +2 -2
  51. package/dist/remote-routes.d.ts.map +1 -1
  52. package/dist/remote-routes.js +146 -13
  53. package/dist/remote.d.ts +2 -2
  54. package/dist/remote.d.ts.map +1 -1
  55. package/dist/route-helpers.d.ts +8 -0
  56. package/dist/route-helpers.d.ts.map +1 -1
  57. package/dist/route-helpers.js +24 -0
  58. package/dist/runtime-automation-routes.d.ts +2 -2
  59. package/dist/runtime-automation-routes.d.ts.map +1 -1
  60. package/dist/runtime-automation-routes.js +4 -1
  61. package/dist/runtime-route-types.d.ts +46 -3
  62. package/dist/runtime-route-types.d.ts.map +1 -1
  63. package/dist/runtime-routes.d.ts +2 -2
  64. package/dist/runtime-routes.d.ts.map +1 -1
  65. package/dist/runtime-routes.js +1 -0
  66. package/dist/runtime-session-routes.d.ts +13 -3
  67. package/dist/runtime-session-routes.d.ts.map +1 -1
  68. package/dist/runtime-session-routes.js +102 -10
  69. package/dist/sessions.d.ts +2 -2
  70. package/dist/sessions.d.ts.map +1 -1
  71. package/dist/sessions.js +3 -0
  72. package/dist/system-route-types.d.ts +19 -0
  73. package/dist/system-route-types.d.ts.map +1 -1
  74. package/dist/system-routes.d.ts +2 -2
  75. package/dist/system-routes.d.ts.map +1 -1
  76. package/dist/system-routes.js +18 -0
  77. package/dist/tasks.d.ts +2 -2
  78. package/dist/tasks.d.ts.map +1 -1
  79. package/dist/telemetry-routes.d.ts +25 -2
  80. package/dist/telemetry-routes.d.ts.map +1 -1
  81. package/dist/telemetry-routes.js +131 -15
  82. package/package.json +128 -5
@@ -1,3 +1,10 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readBoundedPositiveInteger } from './route-helpers.js';
3
+ const DEFAULT_LIST_LIMIT = 100;
4
+ const MAX_LIST_LIMIT = 500;
5
+ function readBoundedLimit(url, key = 'limit') {
6
+ return readBoundedPositiveInteger(url.searchParams.get(key), DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT);
7
+ }
1
8
  export function createDaemonRuntimeSessionRouteHandlers(context) {
2
9
  return {
3
10
  createSharedSession: async (request) => handleCreateSharedSession(context, request),
@@ -14,6 +21,7 @@ export function createDaemonRuntimeSessionRouteHandlers(context) {
14
21
  getRuntimeTask: (taskId) => handleGetRuntimeTask(context, taskId),
15
22
  runtimeTaskAction: (taskId, action, request) => handleRuntimeTaskAction(context, taskId, action, request),
16
23
  getTaskStatus: (agentId) => handleGetTaskStatus(context, agentId),
24
+ getSharedSessionEvents: (sessionId, request) => handleGetSharedSessionEvents(context, sessionId, request),
17
25
  };
18
26
  }
19
27
  async function handleCreateSharedSession(context, req) {
@@ -171,7 +179,7 @@ async function handleGetSharedSessionMessages(context, sessionId, url) {
171
179
  if (!session) {
172
180
  return Response.json({ error: 'Unknown shared session' }, { status: 404 });
173
181
  }
174
- const limit = Number(url.searchParams.get('limit') ?? 100);
182
+ const limit = readBoundedLimit(url);
175
183
  return Response.json({
176
184
  session,
177
185
  messages: context.sessionBroker.getMessages(sessionId, limit),
@@ -183,25 +191,100 @@ async function handleGetSharedSessionInputs(context, sessionId, url) {
183
191
  if (!session) {
184
192
  return Response.json({ error: 'Unknown shared session' }, { status: 404 });
185
193
  }
186
- const limit = Number(url.searchParams.get('limit') ?? 100);
194
+ const limit = readBoundedLimit(url);
187
195
  return Response.json({
188
196
  session,
189
197
  inputs: context.sessionBroker.getInputs(sessionId, limit),
190
198
  });
191
199
  }
200
+ /**
201
+ * Handle POST /api/sessions/:sessionId/messages.
202
+ *
203
+ * Accepts `{body}` in the request payload and returns 400 when it is absent or empty.
204
+ */
192
205
  async function handlePostSharedSessionMessage(context, sessionId, req) {
193
206
  const body = await context.parseJsonBody(req);
194
207
  if (body instanceof Response)
195
208
  return body;
209
+ // Validate kind field. Ordinary session messages default to conversation
210
+ // routing; callers must explicitly opt into kind='task' for agent/WRFC work.
211
+ const kind = body.kind === undefined ? 'message' : body.kind;
212
+ if (kind !== 'task' && kind !== 'message' && kind !== 'followup') {
213
+ return Response.json({ error: `Invalid kind '${String(kind)}'. Accepted values: 'task' | 'message' | 'followup'`, code: 'INVALID_KIND' }, { status: 400 });
214
+ }
196
215
  const message = readSharedSessionMessageBody(body);
197
216
  if (!message) {
198
217
  return Response.json({ error: 'Missing shared session message body' }, { status: 400 });
199
218
  }
219
+ // kind='followup' — always queues/spawns a follow-up turn via followUpMessage()
220
+ if (kind === 'followup') {
221
+ const followUpSubmission = await context.sessionBroker.followUpMessage(buildSharedSessionMessageInput(sessionId, body, message));
222
+ return await respondToSessionSubmission(context, req, followUpSubmission, message, `/api/sessions/${sessionId}/messages`, 'DaemonServer.handlePostSharedSessionMessage.followup', {
223
+ context: `shared-session:${followUpSubmission.session.id}`,
224
+ });
225
+ }
226
+ // kind='message' — companion main-chat send. Persist + publish COMPANION_MESSAGE_RECEIVED
227
+ // on the runtime bus; TUI's bootstrap-core subscriber delegates to
228
+ // orchestrator.handleUserInput() which fires a real LLM turn (same entry as TUI input
229
+ // box). Turn events stream to both TUI and companion over SSE automatically.
230
+ // Deliberately short-circuits BEFORE sessionBroker.submitMessage() to avoid the
231
+ // buildContinuationTask WRFC engineer-chain spawn path.
232
+ if (kind === 'message') {
233
+ const session = context.sessionBroker.getSession(sessionId);
234
+ if (!session) {
235
+ return Response.json({ error: 'Unknown shared session', code: 'SESSION_NOT_FOUND' }, { status: 404 });
236
+ }
237
+ if (session.status === 'closed') {
238
+ return Response.json({ error: 'Session is closed', code: 'SESSION_CLOSED' }, { status: 409 });
239
+ }
240
+ const messageId = randomUUID();
241
+ const timestamp = Date.now();
242
+ // Persist the companion message to the session log so GET /api/sessions/:id/messages
243
+ // returns it and the TUI can render it.
244
+ await context.sessionBroker.appendCompanionMessage(sessionId, {
245
+ messageId,
246
+ body: message,
247
+ source: 'companion-followup',
248
+ timestamp,
249
+ });
250
+ // Also notify the TUI's in-process subscriber via the conversation follow-up event.
251
+ context.publishConversationFollowup(sessionId, {
252
+ messageId,
253
+ body: message,
254
+ source: 'companion-followup',
255
+ timestamp,
256
+ });
257
+ // Return immediately. appendCompanionMessage persists the message and
258
+ // publishConversationFollowup emits COMPANION_MESSAGE_RECEIVED on the runtime bus.
259
+ // The TUI's bootstrap-core.ts COMPANION_MESSAGE_RECEIVED subscriber delegates to
260
+ // orchestrator.handleUserInput(), which adds the user message to the conversation
261
+ // view and fires a real LLM turn whose events stream to both TUI and companion SSE.
262
+ // The { routedTo: 'conversation' } shape signals the companion app that the message
263
+ // was received and persisted (isConversationRouteResult check).
264
+ return context.recordApiResponse(req, `/api/sessions/${sessionId}/messages`, Response.json({
265
+ messageId,
266
+ routedTo: 'conversation',
267
+ sessionId,
268
+ }, { status: 202 }));
269
+ }
200
270
  const submission = await context.sessionBroker.submitMessage(buildSharedSessionMessageInput(sessionId, body, message));
201
271
  return await respondToSessionSubmission(context, req, submission, message, `/api/sessions/${sessionId}/messages`, 'DaemonServer.handlePostSharedSessionMessage', {
202
272
  context: `shared-session:${submission.session.id}`,
203
273
  });
204
274
  }
275
+ /**
276
+ * Handle GET /api/sessions/:id/events — creates a session-scoped SSE stream
277
+ * for the companion app to receive turn events (STREAM_DELTA, TURN_COMPLETED,
278
+ * etc.) and agent events from the shared session.
279
+ */
280
+ async function handleGetSharedSessionEvents(context, sessionId, req) {
281
+ await context.sessionBroker.start();
282
+ const session = context.sessionBroker.getSession(sessionId);
283
+ if (!session) {
284
+ return Response.json({ error: 'Unknown shared session', code: 'SESSION_NOT_FOUND' }, { status: 404 });
285
+ }
286
+ return context.openSessionEventStream(req, sessionId);
287
+ }
205
288
  async function handlePostSharedSessionSteer(context, sessionId, req) {
206
289
  const body = await context.parseJsonBody(req);
207
290
  if (body instanceof Response)
@@ -237,6 +320,12 @@ async function handleCancelSharedSessionInput(context, sessionId, inputId) {
237
320
  if (!input) {
238
321
  return Response.json({ error: 'Unknown shared session input' }, { status: 404 });
239
322
  }
323
+ // F17: cancelInput returns the entry unchanged when state is not 'queued'.
324
+ // Return 409 so callers know the cancel was a no-op (e.g. already spawned).
325
+ const inputRecord = input;
326
+ if (inputRecord.state !== 'queued' && inputRecord.state !== 'cancelled') {
327
+ return Response.json({ error: `Cannot cancel input in state '${inputRecord.state}'`, code: 'CANCEL_NOT_ALLOWED', input }, { status: 409 });
328
+ }
240
329
  return Response.json({ input });
241
330
  }
242
331
  function handleGetRuntimeTask(context, taskId) {
@@ -246,14 +335,17 @@ function handleGetRuntimeTask(context, taskId) {
246
335
  }
247
336
  return Response.json({ task });
248
337
  }
249
- function readSharedSessionMessageBody(body) {
250
- return typeof body.message === 'string'
251
- ? body.message.trim()
252
- : typeof body.body === 'string'
253
- ? body.body.trim()
254
- : typeof body.text === 'string'
255
- ? body.text.trim()
256
- : '';
338
+ /**
339
+ * Extract the message body from an incoming POST body.
340
+ *
341
+ * F13 normalization: accepts the canonical 'body' field.
342
+ * The caller must check for an empty result and return 400.
343
+ *
344
+ * @param body - Parsed JSON body from the request.
345
+ * @returns Trimmed message string, or '' if none of the accepted fields are present.
346
+ */
347
+ export function readSharedSessionMessageBody(body) {
348
+ return typeof body.body === 'string' ? body.body.trim() : '';
257
349
  }
258
350
  function buildSharedSessionMessageInput(sessionId, body, message) {
259
351
  return {
@@ -1,3 +1,3 @@
1
- import type { DaemonApiRouteHandlers } from './context.js';
2
- export declare function dispatchSessionRoutes(req: Request, handlers: Pick<DaemonApiRouteHandlers, 'getIntegrationSessions' | 'createSharedSession' | 'getSharedSession' | 'closeSharedSession' | 'reopenSharedSession' | 'getSharedSessionMessages' | 'postSharedSessionMessage' | 'getSharedSessionInputs' | 'postSharedSessionSteer' | 'postSharedSessionFollowUp' | 'cancelSharedSessionInput'>): Promise<Response | null>;
1
+ import type { DaemonSessionRouteHandlers } from './context.js';
2
+ export declare function dispatchSessionRoutes(req: Request, handlers: DaemonSessionRouteHandlers): Promise<Response | null>;
3
3
  //# sourceMappingURL=sessions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE3D,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,IAAI,CACZ,sBAAsB,EACpB,wBAAwB,GACxB,qBAAqB,GACrB,kBAAkB,GAClB,oBAAoB,GACpB,qBAAqB,GACrB,0BAA0B,GAC1B,0BAA0B,GAC1B,wBAAwB,GACxB,wBAAwB,GACxB,2BAA2B,GAC3B,0BAA0B,CAC7B,GACA,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqC1B"}
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE/D,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,0BAA0B,GACnC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAwC1B"}
package/dist/sessions.js CHANGED
@@ -33,5 +33,8 @@ export async function dispatchSessionRoutes(req, handlers) {
33
33
  if (sharedSessionCancelInputMatch && method === 'POST') {
34
34
  return handlers.cancelSharedSessionInput(sharedSessionCancelInputMatch[1], sharedSessionCancelInputMatch[2]);
35
35
  }
36
+ const sharedSessionEventsMatch = pathname.match(/^\/api\/sessions\/([^/]+)\/events$/);
37
+ if (sharedSessionEventsMatch && method === 'GET')
38
+ return handlers.getSharedSessionEvents(sharedSessionEventsMatch[1], req);
36
39
  return null;
37
40
  }
@@ -102,6 +102,23 @@ export interface ApprovalBrokerLike {
102
102
  readonly note?: string;
103
103
  }): Promise<unknown | null>;
104
104
  }
105
+ export interface WorkspaceSwapManagerLike {
106
+ getCurrentWorkingDir(): string;
107
+ requestSwap(newWorkingDir: string): Promise<{
108
+ ok: true;
109
+ previous: string;
110
+ current: string;
111
+ } | {
112
+ ok: false;
113
+ code: 'WORKSPACE_BUSY';
114
+ reason: string;
115
+ retryAfter: number;
116
+ } | {
117
+ ok: false;
118
+ code: 'INVALID_PATH';
119
+ reason: string;
120
+ }>;
121
+ }
105
122
  export interface DaemonSystemRouteContext {
106
123
  readonly approvalBroker: ApprovalBrokerLike;
107
124
  readonly configManager: ConfigManagerLike;
@@ -119,6 +136,8 @@ export interface DaemonSystemRouteContext {
119
136
  roles: readonly string[];
120
137
  } | null;
121
138
  readonly routeBindings: RouteBindingManagerLike;
139
+ /** Manages runtime.workingDir swaps. Null when workspace swapping is not available. */
140
+ readonly swapManager: WorkspaceSwapManagerLike | null;
122
141
  readonly watcherRegistry: WatcherRegistryLike;
123
142
  }
124
143
  //# sourceMappingURL=system-route-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"system-route-types.d.ts","sourceRoot":"","sources":["../src/system-route-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"system-route-types.d.ts","sourceRoot":"","sources":["../src/system-route-types.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { DaemonApiRouteHandlers } from './context.js';
1
+ import type { DaemonSystemRouteHandlers } from './context.js';
2
2
  import type { DaemonSystemRouteContext } from './system-route-types.js';
3
- export declare function createDaemonSystemRouteHandlers(context: DaemonSystemRouteContext, request: Request): Pick<DaemonApiRouteHandlers, 'getWatchers' | 'postWatcher' | 'patchWatcher' | 'watcherAction' | 'deleteWatcher' | 'getServiceStatus' | 'installService' | 'startService' | 'stopService' | 'restartService' | 'uninstallService' | 'getRouteBindings' | 'postRouteBinding' | 'patchRouteBinding' | 'deleteRouteBinding' | 'getApprovals' | 'approvalAction' | 'getConfig' | 'postConfig'>;
3
+ export declare function createDaemonSystemRouteHandlers(context: DaemonSystemRouteContext, request: Request): DaemonSystemRouteHandlers;
4
4
  //# sourceMappingURL=system-routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"system-routes.d.ts","sourceRoot":"","sources":["../src/system-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,KAAK,EAMV,wBAAwB,EAEzB,MAAM,yBAAyB,CAAC;AAEjC,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,wBAAwB,EACjC,OAAO,EAAE,OAAO,GACf,IAAI,CACL,sBAAsB,EACpB,aAAa,GACb,aAAa,GACb,cAAc,GACd,eAAe,GACf,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,cAAc,GACd,aAAa,GACb,gBAAgB,GAChB,kBAAkB,GAClB,kBAAkB,GAClB,kBAAkB,GAClB,mBAAmB,GACnB,oBAAoB,GACpB,cAAc,GACd,gBAAgB,GAChB,WAAW,GACX,YAAY,CACf,CAwJA"}
1
+ {"version":3,"file":"system-routes.d.ts","sourceRoot":"","sources":["../src/system-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAG9D,OAAO,KAAK,EAMV,wBAAwB,EAEzB,MAAM,yBAAyB,CAAC;AAEjC,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,wBAAwB,EACjC,OAAO,EAAE,OAAO,GACf,yBAAyB,CA6K3B"}
@@ -157,6 +157,24 @@ export function createDaemonSystemRouteHandlers(context, request) {
157
157
  if (!key || typeof key !== 'string') {
158
158
  return Response.json({ error: 'Missing or invalid key' }, { status: 400 });
159
159
  }
160
+ // Special-case: runtime.workingDir is handled by the workspace swap manager.
161
+ if (key === 'runtime.workingDir') {
162
+ if (!context.swapManager) {
163
+ return Response.json({ error: 'Workspace swapping is not available in this daemon configuration.' }, { status: 400 });
164
+ }
165
+ if (typeof value !== 'string' || !value.trim()) {
166
+ return Response.json({ error: 'runtime.workingDir value must be a non-empty string path.', code: 'INVALID_PATH' }, { status: 400 });
167
+ }
168
+ const result = await context.swapManager.requestSwap(value);
169
+ if (result.ok) {
170
+ return Response.json({ success: true, key, value: result.current, previous: result.previous });
171
+ }
172
+ if (result.code === 'WORKSPACE_BUSY') {
173
+ return Response.json({ error: result.reason, code: 'WORKSPACE_BUSY', retryAfter: result.retryAfter }, { status: 409 });
174
+ }
175
+ // INVALID_PATH
176
+ return Response.json({ error: result.reason, code: 'INVALID_PATH' }, { status: 400 });
177
+ }
160
178
  if (!context.isValidConfigKey(key)) {
161
179
  return Response.json({ error: 'Invalid config key' }, { status: 400 });
162
180
  }
package/dist/tasks.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import type { DaemonApiRouteHandlers } from './context.js';
2
- export declare function dispatchTaskRoutes(req: Request, handlers: Pick<DaemonApiRouteHandlers, 'getIntegrationTasks' | 'getRuntimeTask' | 'runtimeTaskAction' | 'getTaskStatus' | 'postTask'>): Promise<Response | null>;
1
+ import type { DaemonTaskRouteHandlers } from './context.js';
2
+ export declare function dispatchTaskRoutes(req: Request, handlers: DaemonTaskRouteHandlers): Promise<Response | null>;
3
3
  //# sourceMappingURL=tasks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE3D,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,IAAI,CACZ,sBAAsB,EACpB,qBAAqB,GACrB,gBAAgB,GAChB,mBAAmB,GACnB,eAAe,GACf,UAAU,CACb,GACA,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqB1B"}
1
+ {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAE5D,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqB1B"}
@@ -1,4 +1,4 @@
1
- import type { DaemonApiRouteHandlers } from './context.js';
1
+ import type { DaemonTelemetryRouteHandlers } from './context.js';
2
2
  import { type AuthenticatedPrincipal } from './http-policy.js';
3
3
  import type { RuntimeEventDomain } from '@pellux/goodvibes-contracts';
4
4
  type TelemetrySeverity = 'debug' | 'info' | 'warn' | 'error';
@@ -35,10 +35,33 @@ interface TelemetryApiLike {
35
35
  buildOtlpLogDocument(filter: TelemetryFilter, view: TelemetryViewMode): unknown;
36
36
  buildOtlpMetricDocument(): unknown;
37
37
  }
38
+ /**
39
+ * Ingest sink — receives parsed OTLP records forwarded by the POST receivers.
40
+ * When null (no ingest sink wired), the route still accepts and acknowledges
41
+ * payloads (to keep client exporters happy) but discards the data.
42
+ */
43
+ interface TelemetryIngestSink {
44
+ /** Ingest a batch of log records from an OTLP ExportLogsServiceRequest. */
45
+ ingestLogs(payload: Record<string, unknown>): void;
46
+ /** Ingest a batch of trace spans from an OTLP ExportTraceServiceRequest. */
47
+ ingestTraces(payload: Record<string, unknown>): void;
48
+ /** Ingest a batch of metric data points from an OTLP ExportMetricsServiceRequest. */
49
+ ingestMetrics(payload: Record<string, unknown>): void;
50
+ }
38
51
  interface TelemetryRouteContext {
39
52
  readonly telemetryApi: TelemetryApiLike | null;
40
53
  readonly resolveAuthenticatedPrincipal: (req: Request) => AuthenticatedPrincipal | null;
54
+ /**
55
+ * Sink for OTLP POST receivers. Must be provided by the caller.
56
+ * In production `DaemonHttpRouter` this is the `TelemetryApiService` instance
57
+ * which stores ingested records in its bounded event buffer (default 500
58
+ * records) and makes them observable via GET /api/v1/telemetry/events.
59
+ * Pass `null` only in test/stub contexts where ingestion is intentionally
60
+ * a no-op — the receivers still return 200 to keep OTLP exporters happy but
61
+ * discard the payload.
62
+ */
63
+ readonly ingestSink: TelemetryIngestSink | null;
41
64
  }
42
- export declare function createDaemonTelemetryRouteHandlers(context: TelemetryRouteContext): Pick<DaemonApiRouteHandlers, 'getTelemetrySnapshot' | 'getTelemetryEvents' | 'getTelemetryErrors' | 'getTelemetryTraces' | 'getTelemetryMetrics' | 'createTelemetryEventStream' | 'getTelemetryOtlpTraces' | 'getTelemetryOtlpLogs' | 'getTelemetryOtlpMetrics'>;
65
+ export declare function createDaemonTelemetryRouteHandlers(context: TelemetryRouteContext): DaemonTelemetryRouteHandlers;
43
66
  export {};
44
67
  //# sourceMappingURL=telemetry-routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry-routes.d.ts","sourceRoot":"","sources":["../src/telemetry-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAyB,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,KAAK,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAC7D,KAAK,iBAAiB,GAAG,MAAM,GAAG,KAAK,CAAC;AAExC,UAAU,eAAe;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACjD,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED,UAAU,gBAAgB;IACxB,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,GAAG;QACrF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;QACjC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;QAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAC1B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;QACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;KAC9B,CAAC;IACF,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC;IACjG,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC;IACjG,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC;IAChG,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC/G,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAClF,oBAAoB,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAChF,uBAAuB,IAAI,OAAO,CAAC;CACpC;AAED,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,6BAA6B,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,sBAAsB,GAAG,IAAI,CAAC;CACzF;AAyHD,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,qBAAqB,GAC7B,IAAI,CACL,sBAAsB,EACpB,sBAAsB,GACtB,oBAAoB,GACpB,oBAAoB,GACpB,oBAAoB,GACpB,qBAAqB,GACrB,4BAA4B,GAC5B,wBAAwB,GACxB,sBAAsB,GAClB,yBAAyB,CAChC,CAiGA"}
1
+ {"version":3,"file":"telemetry-routes.d.ts","sourceRoot":"","sources":["../src/telemetry-routes.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,15 @@
1
1
  import { buildMissingScopeBody } from './http-policy.js';
2
- function parseNumber(value) {
2
+ import { decodeOtlpProtobuf } from './otlp-protobuf.js';
3
+ import { DaemonErrorCategory } from '@pellux/goodvibes-errors';
4
+ import { readBoundedPositiveInteger, readOptionalBoundedInteger } from './route-helpers.js';
5
+ const MAX_TELEMETRY_TIMESTAMP_MS = Date.UTC(2100, 0, 1);
6
+ function parseLimit(value) {
3
7
  if (value === null || value.trim().length === 0)
4
8
  return undefined;
5
- const parsed = Number(value);
6
- return Number.isFinite(parsed) ? parsed : undefined;
9
+ return readBoundedPositiveInteger(value, 100, 1_000);
10
+ }
11
+ function parseTimestampMillis(value) {
12
+ return readOptionalBoundedInteger(value, 0, MAX_TELEMETRY_TIMESTAMP_MS);
7
13
  }
8
14
  function parseCsv(value) {
9
15
  if (!value)
@@ -23,27 +29,101 @@ function parseView(value) {
23
29
  return value === 'safe' || value === 'raw' ? value : undefined;
24
30
  }
25
31
  function buildFilter(url) {
32
+ const limit = parseLimit(url.searchParams.get('limit'));
33
+ const since = parseTimestampMillis(url.searchParams.get('since'));
34
+ const until = parseTimestampMillis(url.searchParams.get('until'));
35
+ const domains = parseCsv(url.searchParams.get('domains'));
36
+ const eventTypes = parseCsv(url.searchParams.get('types'));
37
+ const severity = parseSeverity(url.searchParams.get('severity'));
38
+ const view = parseView(url.searchParams.get('view'));
26
39
  return {
27
- ...(parseNumber(url.searchParams.get('limit')) !== undefined ? { limit: parseNumber(url.searchParams.get('limit')) } : {}),
28
- ...(parseNumber(url.searchParams.get('since')) !== undefined ? { since: parseNumber(url.searchParams.get('since')) } : {}),
29
- ...(parseNumber(url.searchParams.get('until')) !== undefined ? { until: parseNumber(url.searchParams.get('until')) } : {}),
30
- ...(parseCsv(url.searchParams.get('domains')) ? { domains: parseCsv(url.searchParams.get('domains')) } : {}),
31
- ...(parseCsv(url.searchParams.get('types')) ? { eventTypes: parseCsv(url.searchParams.get('types')) } : {}),
32
- ...(parseSeverity(url.searchParams.get('severity')) ? { severity: parseSeverity(url.searchParams.get('severity')) } : {}),
40
+ ...(limit !== undefined ? { limit } : {}),
41
+ ...(since !== undefined ? { since } : {}),
42
+ ...(until !== undefined ? { until } : {}),
43
+ ...(domains ? { domains } : {}),
44
+ ...(eventTypes ? { eventTypes } : {}),
45
+ ...(severity ? { severity } : {}),
33
46
  ...(url.searchParams.get('traceId') ? { traceId: url.searchParams.get('traceId') ?? undefined } : {}),
34
47
  ...(url.searchParams.get('sessionId') ? { sessionId: url.searchParams.get('sessionId') ?? undefined } : {}),
35
48
  ...(url.searchParams.get('turnId') ? { turnId: url.searchParams.get('turnId') ?? undefined } : {}),
36
49
  ...(url.searchParams.get('agentId') ? { agentId: url.searchParams.get('agentId') ?? undefined } : {}),
37
50
  ...(url.searchParams.get('taskId') ? { taskId: url.searchParams.get('taskId') ?? undefined } : {}),
38
51
  ...(url.searchParams.get('cursor') ? { cursor: url.searchParams.get('cursor') ?? undefined } : {}),
39
- ...(parseView(url.searchParams.get('view')) ? { view: parseView(url.searchParams.get('view')) } : {}),
52
+ ...(view ? { view } : {}),
40
53
  };
41
54
  }
55
+ // ---------------------------------------------------------------------------
56
+ // OTLP ingest helpers
57
+ // ---------------------------------------------------------------------------
58
+ /** Max ingest payload (4 MiB) — reject larger bodies with 413 */
59
+ const OTLP_INGEST_MAX_BODY_BYTES = 4 * 1024 * 1024;
60
+ /** Accepted content-types for OTLP/HTTP ingest. */
61
+ const OTLP_JSON_CONTENT_TYPE = 'application/json';
62
+ const OTLP_PROTOBUF_CONTENT_TYPES = new Set(['application/x-protobuf', 'application/protobuf']);
63
+ const OTLP_PARTIAL_SUCCESS_KEYS = {
64
+ logs: 'partialSuccess',
65
+ traces: 'partialSuccess',
66
+ metrics: 'partialSuccess',
67
+ };
68
+ /**
69
+ * Validate and parse an OTLP HTTP ingest request body.
70
+ * Returns a parsed JSON Record on success, or a Response (error) on failure.
71
+ *
72
+ * Protocol: OTLP/HTTP spec §4.2 — supports JSON and binary protobuf service
73
+ * requests for logs, traces, and metrics.
74
+ */
75
+ async function parseOtlpBody(req, kind) {
76
+ const contentType = (req.headers.get('content-type') ?? '').toLowerCase().split(';')[0].trim();
77
+ const acceptsJson = contentType === OTLP_JSON_CONTENT_TYPE;
78
+ const acceptsProtobuf = OTLP_PROTOBUF_CONTENT_TYPES.has(contentType);
79
+ if (!acceptsJson && !acceptsProtobuf) {
80
+ return Response.json({
81
+ error: `Unsupported Content-Type '${contentType}' for OTLP ingest`,
82
+ code: 'UNSUPPORTED_MEDIA_TYPE',
83
+ category: DaemonErrorCategory.BAD_REQUEST,
84
+ hint: `Use '${OTLP_JSON_CONTENT_TYPE}' or 'application/x-protobuf'.`,
85
+ }, { status: 415 });
86
+ }
87
+ const raw = await req.arrayBuffer();
88
+ if (raw.byteLength > OTLP_INGEST_MAX_BODY_BYTES) {
89
+ return Response.json({
90
+ error: `OTLP ingest payload too large (${raw.byteLength} > ${OTLP_INGEST_MAX_BODY_BYTES} bytes)`,
91
+ code: 'PAYLOAD_TOO_LARGE',
92
+ category: DaemonErrorCategory.BAD_REQUEST,
93
+ }, { status: 413 });
94
+ }
95
+ if (acceptsProtobuf) {
96
+ try {
97
+ return decodeOtlpProtobuf(kind, new Uint8Array(raw));
98
+ }
99
+ catch (error) {
100
+ void error;
101
+ return Response.json({ error: 'OTLP ingest body is not valid protobuf', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
102
+ }
103
+ }
104
+ try {
105
+ const text = new TextDecoder().decode(raw);
106
+ const parsed = JSON.parse(text);
107
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
108
+ return Response.json({ error: 'OTLP ingest body must be a JSON object', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
109
+ }
110
+ return parsed;
111
+ }
112
+ catch (error) {
113
+ void error;
114
+ return Response.json({ error: 'OTLP ingest body is not valid JSON', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
115
+ }
116
+ }
117
+ function otlpIngestSuccess(kind) {
118
+ // Per OTLP/HTTP spec: respond with ExportXxxServiceResponse shape.
119
+ // partialSuccess omitted when empty (all records accepted).
120
+ return Response.json({ [OTLP_PARTIAL_SUCCESS_KEYS[kind]]: {} });
121
+ }
42
122
  function unavailable() {
43
123
  return Response.json({
44
124
  error: 'Telemetry API unavailable',
45
125
  code: 'TELEMETRY_UNAVAILABLE',
46
- category: 'service',
126
+ category: DaemonErrorCategory.SERVICE,
47
127
  source: 'runtime',
48
128
  recoverable: true,
49
129
  hint: 'Start the daemon runtime and ensure the runtime store is available before reading telemetry.',
@@ -54,7 +134,7 @@ function invalidCursor(error) {
54
134
  return Response.json({
55
135
  error: error instanceof Error ? error.message : 'Invalid telemetry cursor',
56
136
  code: 'INVALID_CURSOR',
57
- category: 'bad_request',
137
+ category: DaemonErrorCategory.BAD_REQUEST,
58
138
  source: 'runtime',
59
139
  recoverable: false,
60
140
  hint: 'Use the nextCursor returned by the previous telemetry page, or omit cursor to start from the newest records.',
@@ -67,7 +147,7 @@ function authenticateTelemetryRequest(context, req, requestedView) {
67
147
  return Response.json({
68
148
  error: 'Authentication required for telemetry access',
69
149
  code: 'AUTH_REQUIRED',
70
- category: 'authentication',
150
+ category: DaemonErrorCategory.AUTHENTICATION,
71
151
  source: 'runtime',
72
152
  recoverable: false,
73
153
  hint: 'Authenticate with the operator shared token or an authenticated user session before calling telemetry APIs.',
@@ -79,7 +159,7 @@ function authenticateTelemetryRequest(context, req, requestedView) {
79
159
  return Response.json({
80
160
  error: missingRead.error,
81
161
  code: 'MISSING_SCOPE',
82
- category: 'authorization',
162
+ category: DaemonErrorCategory.AUTHORIZATION,
83
163
  source: 'permission',
84
164
  recoverable: false,
85
165
  hint: 'Use a token or session with the read:telemetry scope, or elevate to an admin/shared-token session.',
@@ -92,7 +172,7 @@ function authenticateTelemetryRequest(context, req, requestedView) {
92
172
  return Response.json({
93
173
  error: 'Raw telemetry view requires elevated telemetry scope',
94
174
  code: 'MISSING_SCOPE',
95
- category: 'authorization',
175
+ category: DaemonErrorCategory.AUTHORIZATION,
96
176
  source: 'permission',
97
177
  recoverable: false,
98
178
  hint: 'Use an admin/shared-token session or a token granted read:telemetry-sensitive to access raw telemetry payloads.',
@@ -223,5 +303,41 @@ export function createDaemonTelemetryRouteHandlers(context) {
223
303
  return access;
224
304
  return Response.json(context.telemetryApi.buildOtlpMetricDocument());
225
305
  },
306
+ // -------------------------------------------------------------------------
307
+ // OTLP POST ingest receivers
308
+ // -------------------------------------------------------------------------
309
+ postTelemetryOtlpLogs: async (req) => {
310
+ const auth = context.resolveAuthenticatedPrincipal(req);
311
+ if (!auth) {
312
+ return Response.json({ error: 'Authentication required for OTLP ingest', code: 'AUTH_REQUIRED', category: DaemonErrorCategory.AUTHENTICATION, status: 401 }, { status: 401 });
313
+ }
314
+ const bodyOrErr = await parseOtlpBody(req, 'logs');
315
+ if (bodyOrErr instanceof Response)
316
+ return bodyOrErr;
317
+ context.ingestSink?.ingestLogs(bodyOrErr);
318
+ return otlpIngestSuccess('logs');
319
+ },
320
+ postTelemetryOtlpTraces: async (req) => {
321
+ const auth = context.resolveAuthenticatedPrincipal(req);
322
+ if (!auth) {
323
+ return Response.json({ error: 'Authentication required for OTLP ingest', code: 'AUTH_REQUIRED', category: DaemonErrorCategory.AUTHENTICATION, status: 401 }, { status: 401 });
324
+ }
325
+ const bodyOrErr = await parseOtlpBody(req, 'traces');
326
+ if (bodyOrErr instanceof Response)
327
+ return bodyOrErr;
328
+ context.ingestSink?.ingestTraces(bodyOrErr);
329
+ return otlpIngestSuccess('traces');
330
+ },
331
+ postTelemetryOtlpMetrics: async (req) => {
332
+ const auth = context.resolveAuthenticatedPrincipal(req);
333
+ if (!auth) {
334
+ return Response.json({ error: 'Authentication required for OTLP ingest', code: 'AUTH_REQUIRED', category: DaemonErrorCategory.AUTHENTICATION, status: 401 }, { status: 401 });
335
+ }
336
+ const bodyOrErr = await parseOtlpBody(req, 'metrics');
337
+ if (bodyOrErr instanceof Response)
338
+ return bodyOrErr;
339
+ context.ingestSink?.ingestMetrics(bodyOrErr);
340
+ return otlpIngestSuccess('metrics');
341
+ },
226
342
  };
227
343
  }