@pellux/goodvibes-daemon-sdk 0.30.3 → 0.33.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 (72) hide show
  1. package/README.md +2 -2
  2. package/dist/api-router.d.ts +4 -2
  3. package/dist/api-router.d.ts.map +1 -1
  4. package/dist/api-router.js +18 -10
  5. package/dist/artifact-upload.d.ts +9 -9
  6. package/dist/artifact-upload.d.ts.map +1 -1
  7. package/dist/artifact-upload.js +27 -19
  8. package/dist/auth-helpers.d.ts +9 -0
  9. package/dist/auth-helpers.d.ts.map +1 -0
  10. package/dist/auth-helpers.js +10 -0
  11. package/dist/automation.js +10 -10
  12. package/dist/channel-route-types.d.ts +22 -23
  13. package/dist/channel-route-types.d.ts.map +1 -1
  14. package/dist/channel-routes.d.ts.map +1 -1
  15. package/dist/channel-routes.js +37 -42
  16. package/dist/context.d.ts +27 -27
  17. package/dist/context.d.ts.map +1 -1
  18. package/dist/control-routes.d.ts +12 -13
  19. package/dist/control-routes.d.ts.map +1 -1
  20. package/dist/control-routes.js +55 -26
  21. package/dist/error-response.d.ts +10 -3
  22. package/dist/error-response.d.ts.map +1 -1
  23. package/dist/error-response.js +102 -11
  24. package/dist/http-policy.d.ts +3 -4
  25. package/dist/http-policy.d.ts.map +1 -1
  26. package/dist/http-policy.js +2 -1
  27. package/dist/index.d.ts +11 -8
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +2 -1
  30. package/dist/integration-routes.d.ts +1 -1
  31. package/dist/integration-routes.d.ts.map +1 -1
  32. package/dist/integration-routes.js +72 -33
  33. package/dist/knowledge-refinement-routes.d.ts.map +1 -1
  34. package/dist/knowledge-refinement-routes.js +22 -15
  35. package/dist/knowledge-route-types.d.ts +16 -15
  36. package/dist/knowledge-route-types.d.ts.map +1 -1
  37. package/dist/knowledge-routes.d.ts.map +1 -1
  38. package/dist/knowledge-routes.js +144 -89
  39. package/dist/media-route-types.d.ts +2 -1
  40. package/dist/media-route-types.d.ts.map +1 -1
  41. package/dist/media-routes.d.ts.map +1 -1
  42. package/dist/media-routes.js +119 -55
  43. package/dist/operator.d.ts +16 -0
  44. package/dist/operator.d.ts.map +1 -1
  45. package/dist/operator.js +105 -61
  46. package/dist/otlp-protobuf.d.ts.map +1 -1
  47. package/dist/otlp-protobuf.js +28 -10
  48. package/dist/remote-routes.d.ts +9 -3
  49. package/dist/remote-routes.d.ts.map +1 -1
  50. package/dist/remote-routes.js +259 -163
  51. package/dist/route-helpers.d.ts +13 -2
  52. package/dist/route-helpers.d.ts.map +1 -1
  53. package/dist/route-helpers.js +38 -1
  54. package/dist/runtime-automation-routes.d.ts.map +1 -1
  55. package/dist/runtime-automation-routes.js +88 -54
  56. package/dist/runtime-route-types.d.ts +87 -93
  57. package/dist/runtime-route-types.d.ts.map +1 -1
  58. package/dist/runtime-session-routes.d.ts +6 -4
  59. package/dist/runtime-session-routes.d.ts.map +1 -1
  60. package/dist/runtime-session-routes.js +132 -88
  61. package/dist/sessions.js +3 -3
  62. package/dist/system-route-types.d.ts +25 -24
  63. package/dist/system-route-types.d.ts.map +1 -1
  64. package/dist/system-routes.d.ts +1 -1
  65. package/dist/system-routes.d.ts.map +1 -1
  66. package/dist/system-routes.js +126 -92
  67. package/dist/tasks.d.ts.map +1 -1
  68. package/dist/tasks.js +2 -0
  69. package/dist/telemetry-routes.d.ts +14 -14
  70. package/dist/telemetry-routes.d.ts.map +1 -1
  71. package/dist/telemetry-routes.js +54 -29
  72. package/package.json +5 -4
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @pellux/goodvibes-daemon-sdk
2
2
 
3
- Internal workspace package backing `@pellux/goodvibes-sdk/daemon`.
3
+ Public GoodVibes daemon package for embeddable route contracts, dispatchers, handler builders, and daemon-side helpers.
4
4
 
5
- Consumers should install `@pellux/goodvibes-sdk` and import this surface from the umbrella package.
5
+ Most applications should install `@pellux/goodvibes-sdk` and import `@pellux/goodvibes-sdk/daemon`. Install this package directly when you only need the daemon embedding subset.
6
6
 
7
7
  Consumer import:
8
8
 
@@ -2,8 +2,10 @@ import type { DaemonApiRouteHandlers } from './context.js';
2
2
  /**
3
3
  * Optional extension dispatchers injected alongside the standard route set.
4
4
  * Each dispatcher is tried in order after the built-in routes; the first
5
- * non-null result wins. Use this to wire companion-chat, provider, or other
6
- * feature routes into the standalone daemon without modifying core route files.
5
+ * non-null result wins. Exceptions thrown by an extension propagate to the
6
+ * caller, matching built-in route behavior. Use this to wire companion-chat,
7
+ * provider, or other feature routes into the standalone daemon without
8
+ * modifying core route files.
7
9
  */
8
10
  export type DaemonApiRouteExtension = (req: Request) => Promise<Response | null> | Response | null;
9
11
  export declare function dispatchDaemonApiRoutes(req: Request, handlers: DaemonApiRouteHandlers, extensions?: readonly DaemonApiRouteExtension[]): Promise<Response | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"api-router.d.ts","sourceRoot":"","sources":["../src/api-router.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEnG,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,sBAAsB,EAChC,UAAU,CAAC,EAAE,SAAS,uBAAuB,EAAE,GAC9C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAgB1B"}
1
+ {"version":3,"file":"api-router.d.ts","sourceRoot":"","sources":["../src/api-router.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEnG,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,sBAAsB,EAChC,UAAU,CAAC,EAAE,SAAS,uBAAuB,EAAE,GAC9C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAkB1B"}
@@ -4,18 +4,26 @@ import { dispatchRemoteRoutes } from './remote.js';
4
4
  import { dispatchSessionRoutes } from './sessions.js';
5
5
  import { dispatchTaskRoutes } from './tasks.js';
6
6
  export async function dispatchDaemonApiRoutes(req, handlers, extensions) {
7
- const coreResult = (await dispatchRemoteRoutes(req, handlers)
8
- ?? await dispatchOperatorRoutes(req, handlers)
9
- ?? await dispatchAutomationRoutes(req, handlers)
10
- ?? await dispatchSessionRoutes(req, handlers)
11
- ?? await dispatchTaskRoutes(req, handlers));
12
- if (coreResult !== null)
13
- return coreResult;
7
+ let result = await dispatchRemoteRoutes(req, handlers);
8
+ if (result !== null)
9
+ return result;
10
+ result = await dispatchOperatorRoutes(req, handlers);
11
+ if (result !== null)
12
+ return result;
13
+ result = await dispatchAutomationRoutes(req, handlers);
14
+ if (result !== null)
15
+ return result;
16
+ result = await dispatchSessionRoutes(req, handlers);
17
+ if (result !== null)
18
+ return result;
19
+ result = await dispatchTaskRoutes(req, handlers);
20
+ if (result !== null)
21
+ return result;
14
22
  if (extensions) {
15
23
  for (const extension of extensions) {
16
- const result = await extension(req);
17
- if (result !== null)
18
- return result;
24
+ const extResult = await extension(req);
25
+ if (extResult !== null)
26
+ return extResult;
19
27
  }
20
28
  }
21
29
  return null;
@@ -4,15 +4,15 @@ export interface ArtifactStoreUploadLike {
4
4
  getMaxBytes?(): number;
5
5
  createFromStream?(input: {
6
6
  readonly stream: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array | Buffer | string> | Iterable<Uint8Array | Buffer | string>;
7
- readonly kind?: string;
8
- readonly mimeType?: string;
9
- readonly filename?: string;
10
- readonly sourceUri?: string;
11
- readonly sizeBytes?: number;
12
- readonly retentionMs?: number;
13
- readonly acquisitionMode?: string;
14
- readonly fetchMode?: string;
15
- readonly metadata?: Record<string, unknown>;
7
+ readonly kind?: string | undefined;
8
+ readonly mimeType?: string | undefined;
9
+ readonly filename?: string | undefined;
10
+ readonly sourceUri?: string | undefined;
11
+ readonly sizeBytes?: number | undefined;
12
+ readonly retentionMs?: number | undefined;
13
+ readonly acquisitionMode?: string | undefined;
14
+ readonly fetchMode?: string | undefined;
15
+ readonly metadata?: Record<string, unknown> | undefined;
16
16
  }): Promise<unknown>;
17
17
  }
18
18
  export interface ArtifactUploadResult {
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-upload.d.ts","sourceRoot":"","sources":["../src/artifact-upload.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE7D,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,WAAW,CAAC,IAAI,MAAM,CAAC;IACvB,gBAAgB,CAAC,CAAC,KAAK,EAAE;QACvB,QAAQ,CAAC,MAAM,EACX,cAAc,CAAC,UAAU,CAAC,GAC1B,aAAa,CAAC,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,GAC3C,QAAQ,CAAC,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;QAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC7C,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;CACzC;AA+BD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAGrE;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED,wBAAsB,+BAA+B,CACnD,aAAa,EAAE,uBAAuB,EACtC,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,oBAAoB,GAAG,QAAQ,CAAC,CAM1C"}
1
+ {"version":3,"file":"artifact-upload.d.ts","sourceRoot":"","sources":["../src/artifact-upload.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE7D,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,WAAW,CAAC,IAAI,MAAM,CAAC;IACvB,gBAAgB,CAAC,CAAC,KAAK,EAAE;QACvB,QAAQ,CAAC,MAAM,EACX,cAAc,CAAC,UAAU,CAAC,GAC1B,aAAa,CAAC,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,GAC3C,QAAQ,CAAC,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACvC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC1C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC9C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;KACzD,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;CACzC;AA+BD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAGrE;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED,wBAAsB,+BAA+B,CACnD,aAAa,EAAE,uBAAuB,EACtC,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,oBAAoB,GAAG,QAAQ,CAAC,CAM1C"}
@@ -3,6 +3,7 @@ import { mkdtemp, open, rm } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { GoodVibesSdkError } from '@pellux/goodvibes-errors';
6
+ import { jsonErrorResponse } from './error-response.js';
6
7
  const JSON_FIELD_NAMES = new Set(['metadata', 'target', 'options']);
7
8
  const MAX_MULTIPART_FIELD_BYTES = 1024 * 1024;
8
9
  const MAX_MULTIPART_BODY_OVERHEAD_BYTES = MAX_MULTIPART_FIELD_BYTES;
@@ -61,8 +62,7 @@ function parseJsonField(value, fieldName) {
61
62
  try {
62
63
  return JSON.parse(value);
63
64
  }
64
- catch (error) {
65
- void error;
65
+ catch {
66
66
  throw artifactUploadError(`Invalid JSON in multipart field ${fieldName}.`);
67
67
  }
68
68
  }
@@ -86,7 +86,7 @@ function readArtifactId(artifact) {
86
86
  if (typeof id === 'string' && id.trim().length > 0)
87
87
  return id;
88
88
  }
89
- return Response.json({ error: 'Artifact store returned an artifact without an id.' }, { status: 500 });
89
+ return jsonErrorResponse({ error: 'Artifact store returned an artifact without an id.' }, { status: 500 });
90
90
  }
91
91
  function isFileLike(value) {
92
92
  return typeof value === 'object'
@@ -101,21 +101,23 @@ async function createArtifactFromMultipart(artifactStore, req) {
101
101
  try {
102
102
  form = await req.formData();
103
103
  }
104
- catch (error) {
105
- void error;
106
- return Response.json({ error: 'Invalid multipart upload.' }, { status: 400 });
104
+ catch {
105
+ return jsonErrorResponse({ error: 'Invalid multipart upload.' }, { status: 400 });
107
106
  }
108
107
  const fields = {};
109
108
  let file = null;
110
109
  for (const [name, value] of form.entries()) {
111
110
  if (typeof value === 'string') {
111
+ if (Buffer.byteLength(value, 'utf8') > MAX_MULTIPART_FIELD_BYTES) {
112
+ return jsonErrorResponse({ error: `Multipart field ${name} is too large.` }, { status: 413 });
113
+ }
112
114
  try {
113
115
  const parsed = parseUploadField(name, value);
114
116
  if (parsed !== undefined)
115
117
  fields[name] = parsed;
116
118
  }
117
119
  catch (error) {
118
- return Response.json({ error: error instanceof Error ? error.message : String(error) }, { status: 400 });
120
+ return jsonErrorResponse({ error: error instanceof Error ? error.message : String(error) }, { status: 400 });
119
121
  }
120
122
  continue;
121
123
  }
@@ -124,11 +126,13 @@ async function createArtifactFromMultipart(artifactStore, req) {
124
126
  }
125
127
  }
126
128
  if (!file)
127
- return Response.json({ error: 'Multipart upload requires a file field.' }, { status: 400 });
129
+ return jsonErrorResponse({ error: 'Multipart upload requires a file field.' }, { status: 400 });
128
130
  const maxBytes = artifactStore.getMaxBytes?.();
129
131
  if (typeof maxBytes === 'number' && typeof file.size === 'number' && file.size > maxBytes) {
130
- return Response.json({ error: `Artifact exceeds the ${maxBytes}-byte limit.` }, { status: 413 });
132
+ return jsonErrorResponse({ error: `Artifact exceeds the ${maxBytes}-byte limit.` }, { status: 413 });
131
133
  }
134
+ // Some FormData implementations omit `File.size`; createFromStream performs
135
+ // the authoritative streaming byte-count check for those uploads.
132
136
  const artifact = await createArtifactFromStream(artifactStore, {
133
137
  stream: file.stream(),
134
138
  fields,
@@ -156,7 +160,7 @@ async function createArtifactFromStreamingMultipart(artifactStore, req) {
156
160
  return artifactId instanceof Response ? artifactId : { artifact, artifactId, fields: spooled.fields };
157
161
  }
158
162
  catch (error) {
159
- return Response.json({ error: error instanceof Error ? error.message : String(error) }, { status: 400 });
163
+ return jsonErrorResponse(error, { status: error instanceof GoodVibesSdkError && error.status ? error.status : 400 });
160
164
  }
161
165
  finally {
162
166
  await spooled?.cleanup();
@@ -164,14 +168,14 @@ async function createArtifactFromStreamingMultipart(artifactStore, req) {
164
168
  }
165
169
  async function createArtifactFromRawBody(artifactStore, req) {
166
170
  if (!req.body)
167
- return Response.json({ error: 'Raw artifact upload requires a request body.' }, { status: 400 });
171
+ return jsonErrorResponse({ error: 'Raw artifact upload requires a request body.' }, { status: 400 });
168
172
  const url = new URL(req.url);
169
173
  const fields = readFieldsFromSearchParams(url.searchParams);
170
174
  const contentType = req.headers.get('content-type')?.split(';')[0]?.trim();
171
175
  const maxBytes = artifactStore.getMaxBytes?.();
172
176
  const sizeBytes = readContentLength(req);
173
177
  if (typeof maxBytes === 'number' && typeof sizeBytes === 'number' && sizeBytes > maxBytes) {
174
- return Response.json({ error: `Artifact exceeds the ${maxBytes}-byte limit.` }, { status: 413 });
178
+ return jsonErrorResponse({ error: `Artifact exceeds the ${maxBytes}-byte limit.` }, { status: 413 });
175
179
  }
176
180
  const artifact = await createArtifactFromStream(artifactStore, {
177
181
  stream: req.body,
@@ -209,8 +213,7 @@ function readFieldsFromSearchParams(params) {
209
213
  if (parsed !== undefined)
210
214
  fields[name] = parsed;
211
215
  }
212
- catch (error) {
213
- void error;
216
+ catch {
214
217
  fields[name] = value;
215
218
  }
216
219
  }
@@ -223,14 +226,16 @@ function readContentLength(req) {
223
226
  function filenameFromContentDisposition(header) {
224
227
  if (!header)
225
228
  return undefined;
229
+ // Minimal RFC 5987 support for filename*=UTF-8''... values. Complex
230
+ // parameter continuations are intentionally unsupported by this lightweight
231
+ // daemon helper; callers should prefer multipart filename metadata.
226
232
  const match = header.match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);
227
233
  if (!match?.[1])
228
234
  return undefined;
229
235
  try {
230
236
  return decodeURIComponent(match[1].replace(/^"|"$/g, ''));
231
237
  }
232
- catch (error) {
233
- void error;
238
+ catch {
234
239
  return match[1].replace(/^"|"$/g, '');
235
240
  }
236
241
  }
@@ -244,8 +249,7 @@ function decodeHeaderValue(value) {
244
249
  try {
245
250
  return decodeURIComponent(unquoted.replace(/^UTF-8''/i, ''));
246
251
  }
247
- catch (error) {
248
- void error;
252
+ catch {
249
253
  return unquoted;
250
254
  }
251
255
  }
@@ -259,6 +263,10 @@ function parseMultipartPartHeaders(headerText) {
259
263
  }
260
264
  const disposition = headers.get('content-disposition') ?? '';
261
265
  const output = {};
266
+ // Lightweight multipart parser: supports ordinary parameters without
267
+ // embedded semicolons plus filename*=UTF-8''... values. RFC 5987
268
+ // continuations and semicolons inside quoted values are intentionally out of
269
+ // scope for this daemon helper.
262
270
  for (const segment of disposition.split(';').slice(1)) {
263
271
  const [rawKey, ...rawValue] = segment.split('=');
264
272
  const key = rawKey?.trim().toLowerCase();
@@ -272,7 +280,7 @@ function parseMultipartPartHeaders(headerText) {
272
280
  }
273
281
  const contentType = headers.get('content-type');
274
282
  if (contentType)
275
- output.contentType = contentType.split(';')[0]?.trim();
283
+ output.contentType = contentType.split(';')[0]?.trim() ?? '';
276
284
  return output;
277
285
  }
278
286
  async function spoolMultipartUpload(req, maxFileBytes) {
@@ -0,0 +1,9 @@
1
+ import type { DaemonRuntimeRouteContext } from './runtime-route-types.js';
2
+ /**
3
+ * Guard helper: returns the admin-denied response if the caller lacks admin
4
+ * privileges, otherwise calls `next()` and returns its result.
5
+ *
6
+ * Shared across all daemon runtime route files to avoid duplication.
7
+ */
8
+ export declare function withAdmin(context: DaemonRuntimeRouteContext, req: Request, next: () => Response | Promise<Response>): Response | Promise<Response>;
9
+ //# sourceMappingURL=auth-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-helpers.d.ts","sourceRoot":"","sources":["../src/auth-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAE1E;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,yBAAyB,EAClC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GACvC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAG9B"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Guard helper: returns the admin-denied response if the caller lacks admin
3
+ * privileges, otherwise calls `next()` and returns its result.
4
+ *
5
+ * Shared across all daemon runtime route files to avoid duplication.
6
+ */
7
+ export function withAdmin(context, req, next) {
8
+ const denied = context.requireAdmin(req);
9
+ return denied ?? next();
10
+ }
@@ -25,32 +25,32 @@ export async function dispatchAutomationRoutes(req, handlers) {
25
25
  if (automationJobMatch && method === 'PATCH')
26
26
  return handlers.patchAutomationJob(automationJobMatch[1], req);
27
27
  if (automationJobMatch && method === 'DELETE')
28
- return handlers.deleteAutomationJob(automationJobMatch[1]);
28
+ return handlers.deleteAutomationJob(automationJobMatch[1], req);
29
29
  const automationJobActionMatch = pathname.match(/^\/api\/automation\/jobs\/([^/]+)\/(enable|disable|pause|resume|run)$/);
30
30
  if (automationJobActionMatch && method === 'POST') {
31
31
  const [, jobId, action] = automationJobActionMatch;
32
32
  if (action === 'run')
33
- return handlers.runAutomationJobNow(jobId);
34
- return handlers.setAutomationJobEnabled(jobId, action === 'enable' || action === 'resume');
33
+ return handlers.runAutomationJobNow(jobId, req);
34
+ return handlers.setAutomationJobEnabled(jobId, action === 'enable' || action === 'resume', req);
35
35
  }
36
36
  if (pathname === '/api/deliveries' && method === 'GET')
37
37
  return handlers.getDeliveries();
38
38
  const deliveryMatch = pathname.match(/^\/api\/deliveries\/([^/]+)$/);
39
39
  if (deliveryMatch && method === 'GET')
40
40
  return handlers.getDelivery(deliveryMatch[1]);
41
- if (pathname === '/schedules' && method === 'GET')
41
+ if (pathname === '/api/automation/schedules' && method === 'GET')
42
42
  return handlers.getSchedules();
43
- if (pathname === '/schedules' && method === 'POST')
43
+ if (pathname === '/api/automation/schedules' && method === 'POST')
44
44
  return handlers.postSchedule(req);
45
- const scheduleIdMatch = pathname.match(/^\/schedules\/([^/]+)$/);
45
+ const scheduleIdMatch = pathname.match(/^\/api\/automation\/schedules\/([^/]+)$/);
46
46
  if (scheduleIdMatch && method === 'DELETE')
47
- return handlers.deleteSchedule(scheduleIdMatch[1]);
48
- const scheduleActionMatch = pathname.match(/^\/schedules\/([^/]+)\/(enable|disable|run)$/);
47
+ return handlers.deleteSchedule(scheduleIdMatch[1], req);
48
+ const scheduleActionMatch = pathname.match(/^\/api\/automation\/schedules\/([^/]+)\/(enable|disable|run)$/);
49
49
  if (scheduleActionMatch && method === 'POST') {
50
50
  const [, scheduleId, action] = scheduleActionMatch;
51
51
  if (action === 'run')
52
- return handlers.runScheduleNow(scheduleId);
53
- return handlers.setScheduleEnabled(scheduleId, action === 'enable');
52
+ return handlers.runScheduleNow(scheduleId, req);
53
+ return handlers.setScheduleEnabled(scheduleId, action === 'enable', req);
54
54
  }
55
55
  return null;
56
56
  }
@@ -6,36 +6,36 @@ export interface ChannelAgentToolDefinitionLike {
6
6
  }
7
7
  export interface ChannelTargetResolutionInput {
8
8
  readonly input: string;
9
- readonly accountId?: string;
10
- readonly preferredKind?: ChannelConversationKind;
11
- readonly threadId?: string;
12
- readonly sessionId?: string;
13
- readonly createIfMissing?: boolean;
14
- readonly live?: boolean;
15
- readonly metadata?: Record<string, unknown>;
9
+ readonly accountId?: string | undefined;
10
+ readonly preferredKind?: ChannelConversationKind | undefined;
11
+ readonly threadId?: string | undefined;
12
+ readonly sessionId?: string | undefined;
13
+ readonly createIfMissing?: boolean | undefined;
14
+ readonly live?: boolean | undefined;
15
+ readonly metadata?: Record<string, unknown> | undefined;
16
16
  }
17
17
  export interface ChannelAuthorizeActionInput {
18
18
  readonly actionId: string;
19
- readonly actorId?: string;
20
- readonly accountId?: string;
21
- readonly target?: unknown;
22
- readonly metadata?: Record<string, unknown>;
19
+ readonly actorId?: string | undefined;
20
+ readonly accountId?: string | undefined;
21
+ readonly target?: unknown | undefined;
22
+ readonly metadata?: Record<string, unknown> | undefined;
23
23
  }
24
24
  export interface ChannelAllowlistInput {
25
- readonly add?: readonly string[];
26
- readonly remove?: readonly string[];
27
- readonly groupId?: string;
28
- readonly channelId?: string;
29
- readonly workspaceId?: string;
30
- readonly kind?: 'user' | 'channel' | 'group';
31
- readonly metadata?: Record<string, unknown>;
25
+ readonly add?: readonly string[] | undefined;
26
+ readonly remove?: readonly string[] | undefined;
27
+ readonly groupId?: string | undefined;
28
+ readonly channelId?: string | undefined;
29
+ readonly workspaceId?: string | undefined;
30
+ readonly kind?: 'user' | 'channel' | 'group' | undefined;
31
+ readonly metadata?: Record<string, unknown> | undefined;
32
32
  }
33
33
  export interface ChannelDirectoryQuery {
34
34
  readonly query: string;
35
- readonly scope?: ChannelDirectoryScope;
36
- readonly groupId?: string;
37
- readonly limit?: number;
38
- readonly live?: boolean;
35
+ readonly scope?: ChannelDirectoryScope | undefined;
36
+ readonly groupId?: string | undefined;
37
+ readonly limit?: number | undefined;
38
+ readonly live?: boolean | undefined;
39
39
  }
40
40
  export interface ChannelPluginServiceLike {
41
41
  listAccounts(surface?: ChannelSurface): Promise<readonly unknown[]>;
@@ -44,7 +44,6 @@ export interface ChannelPluginServiceLike {
44
44
  doctor(surface: ChannelSurface, accountId?: string): Promise<unknown | null>;
45
45
  listRepairActions(surface: ChannelSurface, accountId?: string): Promise<readonly unknown[]>;
46
46
  getLifecycleState(surface: ChannelSurface, accountId?: string): Promise<unknown | null>;
47
- migrateLifecycle(surface: ChannelSurface, accountId?: string, input?: JsonRecord | null): Promise<unknown | null>;
48
47
  runAccountAction(surface: ChannelSurface, action: ChannelLifecycleAction, accountId: string | undefined, input?: JsonRecord): Promise<unknown | null>;
49
48
  listCapabilities(surface?: ChannelSurface): Promise<readonly unknown[]>;
50
49
  listTools(surface?: ChannelSurface): Promise<readonly unknown[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"channel-route-types.d.ts","sourceRoot":"","sources":["../src/channel-route-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"channel-route-types.d.ts","sourceRoot":"","sources":["../src/channel-route-types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"channel-routes.d.ts","sourceRoot":"","sources":["../src/channel-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,KAAK,EAAyC,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAEjH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,yBAAyB,GACjC,0BAA0B,CA+T5B"}
1
+ {"version":3,"file":"channel-routes.d.ts","sourceRoot":"","sources":["../src/channel-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAU/D,OAAO,KAAK,EAAyC,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAkBjH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,yBAAyB,GACjC,0BAA0B,CAyS5B"}
@@ -1,5 +1,17 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { readBoundedPositiveInteger, readChannelConversationKind, readChannelLifecycleAction } from './route-helpers.js';
2
+ import { jsonErrorResponse } from './error-response.js';
3
+ import { createRouteBodySchema, createRouteBodySchemaRegistry, readBoundedPositiveInteger, readChannelConversationKind, readChannelLifecycleAction, } from './route-helpers.js';
4
+ const channelBodySchemas = createRouteBodySchemaRegistry({
5
+ optional: createRouteBodySchema('POST /api/channels/* optional body', (body) => body),
6
+ });
7
+ async function readOptionalChannelBody(context, req) {
8
+ const body = await context.parseOptionalJsonBody(req);
9
+ if (body instanceof Response)
10
+ return body;
11
+ if (body === null)
12
+ return undefined;
13
+ return channelBodySchemas.optional.parse(body);
14
+ }
3
15
  export function createDaemonChannelRouteHandlers(context) {
4
16
  return {
5
17
  getSurfaces: () => Response.json({ surfaces: context.surfaceRegistry.list() }),
@@ -11,19 +23,19 @@ export function createDaemonChannelRouteHandlers(context) {
11
23
  const account = await context.channelPlugins.getAccount(surface, accountId);
12
24
  return account
13
25
  ? Response.json(account)
14
- : Response.json({ error: 'Unknown channel account' }, { status: 404 });
26
+ : jsonErrorResponse({ error: 'Unknown channel account' }, { status: 404 });
15
27
  },
16
28
  getChannelSetupSchema: async (surface, url) => {
17
29
  const schema = await context.channelPlugins.getSetupSchema(surface, url.searchParams.get('accountId') ?? undefined);
18
30
  return schema
19
31
  ? Response.json(schema)
20
- : Response.json({ error: 'Unknown channel setup schema' }, { status: 404 });
32
+ : jsonErrorResponse({ error: 'Unknown channel setup schema' }, { status: 404 });
21
33
  },
22
34
  getChannelDoctor: async (surface, url) => {
23
35
  const report = await context.channelPlugins.doctor(surface, url.searchParams.get('accountId') ?? undefined);
24
36
  return report
25
37
  ? Response.json(report)
26
- : Response.json({ error: 'Unknown channel doctor surface' }, { status: 404 });
38
+ : jsonErrorResponse({ error: 'Unknown channel doctor surface' }, { status: 404 });
27
39
  },
28
40
  getChannelRepairActions: async (surface, url) => Response.json({
29
41
  actions: await context.channelPlugins.listRepairActions(surface, url.searchParams.get('accountId') ?? undefined),
@@ -32,37 +44,23 @@ export function createDaemonChannelRouteHandlers(context) {
32
44
  const state = await context.channelPlugins.getLifecycleState(surface, url.searchParams.get('accountId') ?? undefined);
33
45
  return state
34
46
  ? Response.json(state)
35
- : Response.json({ error: 'Unknown channel lifecycle surface' }, { status: 404 });
36
- },
37
- postChannelLifecycleMigrate: async (surface, req) => {
38
- const admin = context.requireAdmin(req);
39
- if (admin)
40
- return admin;
41
- const body = await context.parseOptionalJsonBody(req);
42
- if (body instanceof Response)
43
- return body;
44
- const state = await context.channelPlugins.migrateLifecycle(surface, typeof body?.accountId === 'string' ? body.accountId : undefined, body ?? undefined);
45
- return state
46
- ? Response.json(state)
47
- : Response.json({ error: 'Unknown channel lifecycle surface' }, { status: 404 });
47
+ : jsonErrorResponse({ error: 'Unknown channel lifecycle surface' }, { status: 404 });
48
48
  },
49
49
  postChannelAccountAction: async (surface, accountId, action, req) => {
50
50
  const admin = context.requireAdmin(req);
51
51
  if (admin)
52
52
  return admin;
53
- const body = await context.parseJsonBody(req);
54
- const input = body instanceof Response ? undefined : body;
55
- if (body instanceof Response && req.headers.get('content-length') && req.headers.get('content-length') !== '0') {
56
- return body;
57
- }
53
+ const input = await readOptionalChannelBody(context, req);
54
+ if (input instanceof Response)
55
+ return input;
58
56
  const lifecycleAction = readChannelLifecycleAction(action);
59
57
  if (!lifecycleAction) {
60
- return Response.json({ error: 'Unknown channel account action' }, { status: 400 });
58
+ return jsonErrorResponse({ error: 'Unknown channel account action' }, { status: 400 });
61
59
  }
62
60
  const result = await context.channelPlugins.runAccountAction(surface, lifecycleAction, accountId ?? (typeof input?.accountId === 'string' ? input.accountId : undefined), input);
63
61
  return result !== null
64
62
  ? Response.json({ surface, accountId, action: lifecycleAction, result })
65
- : Response.json({ error: 'Unknown channel account action' }, { status: 404 });
63
+ : jsonErrorResponse({ error: 'Unknown channel account action' }, { status: 404 });
66
64
  },
67
65
  getChannelCapabilities: () => context.channelPlugins.listCapabilities().then((capabilities) => Response.json({ capabilities })),
68
66
  getChannelSurfaceCapabilities: (surface) => context.channelPlugins
@@ -82,15 +80,13 @@ export function createDaemonChannelRouteHandlers(context) {
82
80
  const admin = context.requireAdmin(req);
83
81
  if (admin)
84
82
  return admin;
85
- const body = await context.parseJsonBody(req);
86
- const input = body instanceof Response ? undefined : body;
87
- if (body instanceof Response && req.headers.get('content-length') && req.headers.get('content-length') !== '0') {
88
- return body;
89
- }
83
+ const input = await readOptionalChannelBody(context, req);
84
+ if (input instanceof Response)
85
+ return input;
90
86
  const result = await context.channelPlugins.runTool(surface, toolId, input);
91
87
  return result !== null
92
88
  ? Response.json({ toolId, surface, result })
93
- : Response.json({ error: 'Unknown channel tool' }, { status: 404 });
89
+ : jsonErrorResponse({ error: 'Unknown channel tool' }, { status: 404 });
94
90
  },
95
91
  getChannelActions: () => context.channelPlugins.listOperatorActions().then((actions) => Response.json({ actions })),
96
92
  getChannelSurfaceActions: (surface) => context.channelPlugins
@@ -100,15 +96,13 @@ export function createDaemonChannelRouteHandlers(context) {
100
96
  const admin = context.requireAdmin(req);
101
97
  if (admin)
102
98
  return admin;
103
- const body = await context.parseJsonBody(req);
104
- const input = body instanceof Response ? undefined : body;
105
- if (body instanceof Response && req.headers.get('content-length') && req.headers.get('content-length') !== '0') {
106
- return body;
107
- }
99
+ const input = await readOptionalChannelBody(context, req);
100
+ if (input instanceof Response)
101
+ return input;
108
102
  const result = await context.channelPlugins.runOperatorAction(surface, actionId, input);
109
103
  return result !== null
110
104
  ? Response.json({ actionId, surface, result })
111
- : Response.json({ error: 'Unknown channel action' }, { status: 404 });
105
+ : jsonErrorResponse({ error: 'Unknown channel action' }, { status: 404 });
112
106
  },
113
107
  postChannelResolveTarget: async (surface, req) => {
114
108
  const admin = context.requireAdmin(req);
@@ -125,7 +119,7 @@ export function createDaemonChannelRouteHandlers(context) {
125
119
  ? body.query
126
120
  : '';
127
121
  if (!targetInput.trim()) {
128
- return Response.json({ error: 'Target resolution requires target, input, or query.' }, { status: 400 });
122
+ return jsonErrorResponse({ error: 'Target resolution requires target, input, or query.' }, { status: 400 });
129
123
  }
130
124
  const preferredKind = readChannelConversationKind(body.preferredKind);
131
125
  const target = await context.channelPlugins.resolveTarget(surface, {
@@ -140,7 +134,7 @@ export function createDaemonChannelRouteHandlers(context) {
140
134
  });
141
135
  return target
142
136
  ? Response.json({ surface, target })
143
- : Response.json({ error: 'Unable to resolve channel target' }, { status: 404 });
137
+ : jsonErrorResponse({ error: 'Unable to resolve channel target' }, { status: 404 });
144
138
  },
145
139
  postChannelAuthorize: async (surface, req) => {
146
140
  const admin = context.requireAdmin(req);
@@ -165,7 +159,7 @@ export function createDaemonChannelRouteHandlers(context) {
165
159
  });
166
160
  return result
167
161
  ? Response.json({ surface, result })
168
- : Response.json({ error: 'Unable to authorize channel action' }, { status: 404 });
162
+ : jsonErrorResponse({ error: 'Unable to authorize channel action' }, { status: 404 });
169
163
  },
170
164
  postChannelAllowlistResolve: async (surface, req) => {
171
165
  const admin = context.requireAdmin(req);
@@ -185,7 +179,7 @@ export function createDaemonChannelRouteHandlers(context) {
185
179
  });
186
180
  return result
187
181
  ? Response.json(result)
188
- : Response.json({ error: 'Unknown channel allowlist surface' }, { status: 404 });
182
+ : jsonErrorResponse({ error: 'Unknown channel allowlist surface' }, { status: 404 });
189
183
  },
190
184
  postChannelAllowlistEdit: async (surface, req) => {
191
185
  const admin = context.requireAdmin(req);
@@ -205,7 +199,7 @@ export function createDaemonChannelRouteHandlers(context) {
205
199
  });
206
200
  return result
207
201
  ? Response.json(result)
208
- : Response.json({ error: 'Unknown channel allowlist surface' }, { status: 404 });
202
+ : jsonErrorResponse({ error: 'Unknown channel allowlist surface' }, { status: 404 });
209
203
  },
210
204
  getChannelPolicies: () => Response.json({ policies: context.channelPolicy.listPolicies() }),
211
205
  patchChannelPolicy: async (surface, req) => {
@@ -256,7 +250,8 @@ export function createDaemonChannelRouteHandlers(context) {
256
250
  groupPolicies: body.groupPolicies
257
251
  .filter((value) => typeof value === 'object' && value !== null)
258
252
  .map((value) => ({
259
- id: typeof value.id === 'string' ? value.id : `group-policy-${randomUUID().slice(0, 8)}`,
253
+ // use the full UUID to avoid birthday-paradox collisions at scale.
254
+ id: typeof value.id === 'string' ? value.id : `group-policy-${randomUUID()}`,
260
255
  ...(typeof value.label === 'string' ? { label: value.label } : {}),
261
256
  ...(typeof value.groupId === 'string' ? { groupId: value.groupId } : {}),
262
257
  ...(typeof value.channelId === 'string' ? { channelId: value.channelId } : {}),