@treeseed/core 0.4.9 → 0.4.10

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 (41) hide show
  1. package/README.md +1 -2
  2. package/dist/agent.d.ts +0 -1
  3. package/dist/agent.js +0 -2
  4. package/dist/agents/spec-types.d.ts +10 -10
  5. package/dist/api/agent-routes.d.ts +2 -2
  6. package/dist/api/agent-routes.js +51 -125
  7. package/dist/api/app.js +56 -4
  8. package/dist/api/auth/d1-store.d.ts +1 -0
  9. package/dist/api/auth/d1-store.js +21 -1
  10. package/dist/api/config.js +4 -0
  11. package/dist/api/http.d.ts +4 -0
  12. package/dist/api/http.js +7 -0
  13. package/dist/api/index.d.ts +1 -1
  14. package/dist/api/index.js +2 -2
  15. package/dist/api/operations-routes.d.ts +1 -0
  16. package/dist/api/operations-routes.js +6 -1
  17. package/dist/api/railway.d.ts +4 -0
  18. package/dist/api/sdk-dispatch.d.ts +2 -11
  19. package/dist/api/sdk-dispatch.js +1 -133
  20. package/dist/api/sdk-routes.d.ts +1 -0
  21. package/dist/api/sdk-routes.js +5 -1
  22. package/dist/api/types.d.ts +32 -16
  23. package/dist/dev.js +24 -1
  24. package/dist/index.d.ts +0 -1
  25. package/dist/index.js +0 -2
  26. package/dist/scripts/test-smoke.js +0 -1
  27. package/dist/services/common.d.ts +37 -4
  28. package/dist/services/common.js +135 -17
  29. package/dist/services/index.d.ts +1 -1
  30. package/dist/services/index.js +3 -2
  31. package/dist/services/remote-runner.d.ts +23 -0
  32. package/dist/services/remote-runner.js +105 -0
  33. package/dist/services/workday-report.js +13 -17
  34. package/dist/services/workday-start.d.ts +5 -1
  35. package/dist/services/workday-start.js +7 -13
  36. package/dist/services/worker.js +38 -57
  37. package/package.json +7 -11
  38. package/dist/api/gateway.d.ts +0 -5
  39. package/dist/api/gateway.js +0 -35
  40. package/dist/services/manager.d.ts +0 -4
  41. package/dist/services/manager.js +0 -199
package/README.md CHANGED
@@ -51,7 +51,6 @@ That means the fixture may reference package surfaces owned by `sdk`, `core`, an
51
51
  npm run dev
52
52
  npm run dev:web
53
53
  npm run dev:api
54
- npm run dev:manager
55
54
  npm run dev:worker
56
55
  npm run dev:workday-start
57
56
  npm run dev:workday-report
@@ -68,7 +67,7 @@ What they do:
68
67
  - `dev`: starts the integrated Astro UI and Hono API local runtime from `core`
69
68
  - `dev:web`: starts only the Astro UI dev surface through the `core` runtime
70
69
  - `dev:api`: starts only the Hono API dev surface through the `core` runtime
71
- - `dev:manager`, `dev:worker`, `dev:workday-start`, `dev:workday-report`: start the worker-service entrypoints from `core`
70
+ - `dev:worker`, `dev:workday-start`, `dev:workday-report`: start the worker-service entrypoints from `core`
72
71
  - `fixtures:check`: verifies that the pinned shared fixture is initialized and usable
73
72
  - `build:dist`: builds the publishable `dist/` package output
74
73
  - `test:unit`: runs package unit tests with Vitest
package/dist/agent.d.ts CHANGED
@@ -2,7 +2,6 @@ export { AgentKernel } from './agents/kernel/agent-kernel.ts';
2
2
  export { listTreeseedAgentCommands, renderTreeseedAgentHelp, runTreeseedAgentCli } from './agents/cli.ts';
3
3
  export { resolveAgentHandler, listRegisteredAgentHandlers } from './agents/registry.ts';
4
4
  export { resolveAgentRuntimeProviders } from './agent-runtime.ts';
5
- export { createManagerApp } from './services/manager.ts';
6
5
  export { runWorkerCycle, startWorkerLoop } from './services/worker.ts';
7
6
  export { runWorkdayStart } from './services/workday-start.ts';
8
7
  export { runWorkdayReport } from './services/workday-report.ts';
package/dist/agent.js CHANGED
@@ -2,7 +2,6 @@ import { AgentKernel } from "./agents/kernel/agent-kernel.js";
2
2
  import { listTreeseedAgentCommands, renderTreeseedAgentHelp, runTreeseedAgentCli } from "./agents/cli.js";
3
3
  import { resolveAgentHandler, listRegisteredAgentHandlers } from "./agents/registry.js";
4
4
  import { resolveAgentRuntimeProviders } from "./agent-runtime.js";
5
- import { createManagerApp } from "./services/manager.js";
6
5
  import { runWorkerCycle, startWorkerLoop } from "./services/worker.js";
7
6
  import { runWorkdayStart } from "./services/workday-start.js";
8
7
  import { runWorkdayReport } from "./services/workday-report.js";
@@ -10,7 +9,6 @@ import { parseAgentMessagePayload, AGENT_MESSAGE_TYPES } from "./agents/contract
10
9
  export {
11
10
  AGENT_MESSAGE_TYPES,
12
11
  AgentKernel,
13
- createManagerApp,
14
12
  listRegisteredAgentHandlers,
15
13
  listTreeseedAgentCommands,
16
14
  parseAgentMessagePayload,
@@ -1,4 +1,4 @@
1
- import type { AgentCliOptions, AgentExecutionConfig, AgentHandlerKind, AgentOutputContract, AgentPermissionConfig, AgentRuntimeSpec, AgentTriggerConfig } from '@treeseed/sdk/types/agents';
1
+ import type { AgentCliOptions, AgentExecutionConfig, AgentHandlerKind, AgentOutputContract, AgentPermissionConfig, AgentTriggerConfig } from '@treeseed/sdk/types/agents';
2
2
  export type AgentSpecDiagnosticSeverity = 'error' | 'warning';
3
3
  export interface AgentSpecDiagnostic {
4
4
  severity: AgentSpecDiagnosticSeverity;
@@ -6,15 +6,6 @@ export interface AgentSpecDiagnostic {
6
6
  field: string;
7
7
  message: string;
8
8
  }
9
- export interface NormalizedAgentRuntimeSpec extends AgentRuntimeSpec {
10
- name?: string;
11
- description?: string;
12
- summary?: string;
13
- operator?: string;
14
- runtimeStatus?: string;
15
- capabilities?: string[];
16
- tags?: string[];
17
- }
18
9
  export interface AgentSpecValidationContext {
19
10
  registeredHandlers: readonly AgentHandlerKind[];
20
11
  messageTypes: readonly string[];
@@ -62,3 +53,12 @@ export interface AgentSpecParts {
62
53
  execution: AgentExecutionConfig;
63
54
  outputs: AgentOutputContract;
64
55
  }
56
+ export type NormalizedAgentRuntimeSpec = AgentSpecParts & {
57
+ name?: string;
58
+ description?: string;
59
+ summary?: string;
60
+ operator?: string;
61
+ runtimeStatus?: string;
62
+ capabilities?: string[];
63
+ tags?: string[];
64
+ };
@@ -1,13 +1,13 @@
1
1
  import type { Hono } from 'hono';
2
2
  import type { AgentSdk } from '@treeseed/sdk';
3
- import type { GatewayQueueProducer } from './types.ts';
3
+ import type { ApiContext } from './http.ts';
4
4
  interface RegisterAgentRoutesOptions {
5
5
  sdk: AgentSdk;
6
6
  prefix?: string;
7
7
  scope?: string | null;
8
8
  projectId?: string;
9
- queueProducer?: GatewayQueueProducer;
10
9
  defaultActor?: string;
10
+ authorize?: (c: ApiContext) => Response | null;
11
11
  }
12
12
  export declare function registerAgentRoutes(app: Hono<any>, options: RegisterAgentRoutesOptions): void;
13
13
  export {};
@@ -1,58 +1,24 @@
1
- import crypto from "node:crypto";
2
1
  import { listRegisteredAgentHandlers as listCoreRegisteredAgentHandlers } from "../agents/registry.js";
2
+ import { buildTaskContext, enqueueTaskFromSdk } from "../services/common.js";
3
3
  import { jsonError, requireScope } from "./http.js";
4
4
  async function listRegisteredHandlers() {
5
5
  return listCoreRegisteredAgentHandlers();
6
6
  }
7
- function queueEnvelopeForTask(task) {
8
- return {
9
- messageId: crypto.randomUUID(),
10
- taskId: String(task.id ?? ""),
11
- workDayId: String(task.workDayId ?? task.work_day_id ?? ""),
12
- agentId: String(task.agentId ?? task.agent_id ?? ""),
13
- taskType: String(task.type ?? ""),
14
- idempotencyKey: String(task.idempotencyKey ?? task.idempotency_key ?? ""),
15
- attempt: Number(task.attemptCount ?? task.attempt_count ?? 0) + 1,
16
- payloadRef: `d1:tasks/${String(task.id ?? "")}`,
17
- graphVersion: task.graphVersion !== void 0 && task.graphVersion !== null ? String(task.graphVersion) : task.graph_version !== void 0 && task.graph_version !== null ? String(task.graph_version) : null,
18
- budgetHint: 1
19
- };
20
- }
21
7
  function withPrefix(prefix, path) {
22
8
  return `${prefix}${path}`.replace(/\/{2,}/g, "/");
23
9
  }
24
10
  function actor(body, fallback) {
25
11
  return String(body.actor ?? fallback);
26
12
  }
27
- async function enqueueTask(options, request) {
28
- if (!options.queueProducer) {
29
- throw new Error("Queue producer not configured.");
13
+ function authorizeRequest(c, options) {
14
+ const routeUnauthorized = options.authorize?.(c);
15
+ if (routeUnauthorized) {
16
+ return routeUnauthorized;
30
17
  }
31
- const task = await options.sdk.get({ model: "task", id: request.taskId });
32
- if (!task.payload) {
33
- throw new Error("Unknown task.");
18
+ if (options.scope) {
19
+ return requireScope(c, options.scope);
34
20
  }
35
- await options.queueProducer.enqueue({
36
- queueName: request.queueName,
37
- message: queueEnvelopeForTask(task.payload),
38
- delaySeconds: request.deliveryDelaySeconds ?? 0
39
- });
40
- await options.sdk.recordTaskProgress({
41
- id: request.taskId,
42
- state: "queued",
43
- appendEvent: { kind: "queued", data: { queueName: request.queueName ?? null } },
44
- actor: request.actor
45
- });
46
- return { ok: true, taskId: request.taskId, queued: true };
47
- }
48
- async function buildTaskContext(sdk, taskId) {
49
- const context = await sdk.getManagerContext(taskId);
50
- const task = context.payload.task;
51
- const agent = task ? (await sdk.get({ model: "agent", slug: String(task.agentId) })).payload : null;
52
- return {
53
- ...context.payload,
54
- agent
55
- };
21
+ return null;
56
22
  }
57
23
  function registerAgentRoutes(app, options) {
58
24
  const prefix = options.prefix ?? "/agent";
@@ -63,10 +29,8 @@ function registerAgentRoutes(app, options) {
63
29
  handlerCount: (await listRegisteredHandlers()).length
64
30
  }));
65
31
  app.get(withPrefix(prefix, "/specs"), async (c) => {
66
- if (options.scope) {
67
- const unauthorized = requireScope(c, options.scope);
68
- if (unauthorized) return unauthorized;
69
- }
32
+ const unauthorized = authorizeRequest(c, options);
33
+ if (unauthorized) return unauthorized;
70
34
  const payload = await options.sdk.listAgentSpecs({ enabled: true });
71
35
  return c.json({
72
36
  ok: true,
@@ -75,10 +39,8 @@ function registerAgentRoutes(app, options) {
75
39
  });
76
40
  });
77
41
  app.post(withPrefix(prefix, "/workdays/start"), async (c) => {
78
- if (options.scope) {
79
- const unauthorized = requireScope(c, options.scope);
80
- if (unauthorized) return unauthorized;
81
- }
42
+ const unauthorized = authorizeRequest(c, options);
43
+ if (unauthorized) return unauthorized;
82
44
  const body = await c.req.json().catch(() => ({}));
83
45
  const graphRefresh = await options.sdk.refreshGraph();
84
46
  const result = await options.sdk.startWorkDay({
@@ -92,10 +54,8 @@ function registerAgentRoutes(app, options) {
92
54
  return c.json(result);
93
55
  });
94
56
  app.post(withPrefix(prefix, "/workdays/:id/close"), async (c) => {
95
- if (options.scope) {
96
- const unauthorized = requireScope(c, options.scope);
97
- if (unauthorized) return unauthorized;
98
- }
57
+ const unauthorized = authorizeRequest(c, options);
58
+ if (unauthorized) return unauthorized;
99
59
  const body = await c.req.json().catch(() => ({}));
100
60
  const result = await options.sdk.closeWorkDay({
101
61
  id: c.req.param("id"),
@@ -106,10 +66,8 @@ function registerAgentRoutes(app, options) {
106
66
  return result.payload ? c.json(result) : jsonError(c, 404, "Unknown work day.");
107
67
  });
108
68
  app.post(withPrefix(prefix, "/tasks"), async (c) => {
109
- if (options.scope) {
110
- const unauthorized = requireScope(c, options.scope);
111
- if (unauthorized) return unauthorized;
112
- }
69
+ const unauthorized = authorizeRequest(c, options);
70
+ if (unauthorized) return unauthorized;
113
71
  const body = await c.req.json().catch(() => ({}));
114
72
  const result = await options.sdk.createTask({
115
73
  id: typeof body.id === "string" ? body.id : void 0,
@@ -130,10 +88,8 @@ function registerAgentRoutes(app, options) {
130
88
  return c.json(result);
131
89
  });
132
90
  app.post(withPrefix(prefix, "/tasks/search"), async (c) => {
133
- if (options.scope) {
134
- const unauthorized = requireScope(c, options.scope);
135
- if (unauthorized) return unauthorized;
136
- }
91
+ const unauthorized = authorizeRequest(c, options);
92
+ if (unauthorized) return unauthorized;
137
93
  const body = await c.req.json().catch(() => ({}));
138
94
  const result = await options.sdk.searchTasks({
139
95
  workDayId: typeof body.workDayId === "string" ? body.workDayId : void 0,
@@ -144,10 +100,8 @@ function registerAgentRoutes(app, options) {
144
100
  return c.json(result);
145
101
  });
146
102
  app.post(withPrefix(prefix, "/tasks/:id/claim"), async (c) => {
147
- if (options.scope) {
148
- const unauthorized = requireScope(c, options.scope);
149
- if (unauthorized) return unauthorized;
150
- }
103
+ const unauthorized = authorizeRequest(c, options);
104
+ if (unauthorized) return unauthorized;
151
105
  const body = await c.req.json().catch(() => ({}));
152
106
  const result = await options.sdk.claimTask({
153
107
  id: c.req.param("id"),
@@ -158,10 +112,8 @@ function registerAgentRoutes(app, options) {
158
112
  return result.payload ? c.json(result) : jsonError(c, 404, "Unknown task.");
159
113
  });
160
114
  app.post(withPrefix(prefix, "/tasks/:id/progress"), async (c) => {
161
- if (options.scope) {
162
- const unauthorized = requireScope(c, options.scope);
163
- if (unauthorized) return unauthorized;
164
- }
115
+ const unauthorized = authorizeRequest(c, options);
116
+ if (unauthorized) return unauthorized;
165
117
  const body = await c.req.json().catch(() => ({}));
166
118
  const result = await options.sdk.recordTaskProgress({
167
119
  id: c.req.param("id"),
@@ -174,10 +126,8 @@ function registerAgentRoutes(app, options) {
174
126
  return result.payload ? c.json(result) : jsonError(c, 404, "Unknown task.");
175
127
  });
176
128
  app.post(withPrefix(prefix, "/tasks/:id/complete"), async (c) => {
177
- if (options.scope) {
178
- const unauthorized = requireScope(c, options.scope);
179
- if (unauthorized) return unauthorized;
180
- }
129
+ const unauthorized = authorizeRequest(c, options);
130
+ if (unauthorized) return unauthorized;
181
131
  const body = await c.req.json().catch(() => ({}));
182
132
  const result = await options.sdk.completeTask({
183
133
  id: c.req.param("id"),
@@ -189,10 +139,8 @@ function registerAgentRoutes(app, options) {
189
139
  return result.payload ? c.json(result) : jsonError(c, 404, "Unknown task.");
190
140
  });
191
141
  app.post(withPrefix(prefix, "/tasks/:id/fail"), async (c) => {
192
- if (options.scope) {
193
- const unauthorized = requireScope(c, options.scope);
194
- if (unauthorized) return unauthorized;
195
- }
142
+ const unauthorized = authorizeRequest(c, options);
143
+ if (unauthorized) return unauthorized;
196
144
  const body = await c.req.json().catch(() => ({}));
197
145
  const result = await options.sdk.failTask({
198
146
  id: c.req.param("id"),
@@ -205,13 +153,11 @@ function registerAgentRoutes(app, options) {
205
153
  return result.payload ? c.json(result) : jsonError(c, 404, "Unknown task.");
206
154
  });
207
155
  app.post(withPrefix(prefix, "/tasks/:id/requeue"), async (c) => {
208
- if (options.scope) {
209
- const unauthorized = requireScope(c, options.scope);
210
- if (unauthorized) return unauthorized;
211
- }
156
+ const unauthorized = authorizeRequest(c, options);
157
+ if (unauthorized) return unauthorized;
212
158
  const body = await c.req.json().catch(() => ({}));
213
159
  try {
214
- return c.json(await enqueueTask(options, {
160
+ return c.json(await enqueueTaskFromSdk(options.sdk, {
215
161
  taskId: c.req.param("id"),
216
162
  queueName: typeof body.queueName === "string" ? body.queueName : void 0,
217
163
  deliveryDelaySeconds: body.delaySeconds === void 0 ? void 0 : Number(body.delaySeconds),
@@ -223,10 +169,8 @@ function registerAgentRoutes(app, options) {
223
169
  }
224
170
  });
225
171
  app.post(withPrefix(prefix, "/tasks/:id/followups"), async (c) => {
226
- if (options.scope) {
227
- const unauthorized = requireScope(c, options.scope);
228
- if (unauthorized) return unauthorized;
229
- }
172
+ const unauthorized = authorizeRequest(c, options);
173
+ if (unauthorized) return unauthorized;
230
174
  const body = await c.req.json().catch(() => ({}));
231
175
  const current = await options.sdk.get({ model: "task", id: c.req.param("id") });
232
176
  if (!current.payload) {
@@ -251,13 +195,11 @@ function registerAgentRoutes(app, options) {
251
195
  return c.json({ ok: true, payload: created.map((entry) => entry.payload) });
252
196
  });
253
197
  app.post(withPrefix(prefix, "/queue/enqueue"), async (c) => {
254
- if (options.scope) {
255
- const unauthorized = requireScope(c, options.scope);
256
- if (unauthorized) return unauthorized;
257
- }
198
+ const unauthorized = authorizeRequest(c, options);
199
+ if (unauthorized) return unauthorized;
258
200
  const body = await c.req.json().catch(() => ({}));
259
201
  try {
260
- return c.json(await enqueueTask(options, {
202
+ return c.json(await enqueueTaskFromSdk(options.sdk, {
261
203
  taskId: String(body.taskId ?? ""),
262
204
  queueName: typeof body.queueName === "string" ? body.queueName : void 0,
263
205
  deliveryDelaySeconds: body.deliveryDelaySeconds === void 0 ? void 0 : Number(body.deliveryDelaySeconds),
@@ -265,14 +207,12 @@ function registerAgentRoutes(app, options) {
265
207
  }));
266
208
  } catch (error) {
267
209
  const message = error instanceof Error ? error.message : String(error);
268
- return jsonError(c, /Unknown task/.test(message) ? 404 : /Queue producer/.test(message) ? 501 : 500, message);
210
+ return jsonError(c, /Unknown task/.test(message) ? 404 : /Queue push client/.test(message) ? 501 : 500, message);
269
211
  }
270
212
  });
271
213
  app.post(withPrefix(prefix, "/reports"), async (c) => {
272
- if (options.scope) {
273
- const unauthorized = requireScope(c, options.scope);
274
- if (unauthorized) return unauthorized;
275
- }
214
+ const unauthorized = authorizeRequest(c, options);
215
+ if (unauthorized) return unauthorized;
276
216
  const body = await c.req.json().catch(() => ({}));
277
217
  const result = await options.sdk.createReport({
278
218
  id: typeof body.id === "string" ? body.id : void 0,
@@ -286,10 +226,8 @@ function registerAgentRoutes(app, options) {
286
226
  return c.json(result);
287
227
  });
288
228
  app.post(withPrefix(prefix, "/context/resolve-task"), async (c) => {
289
- if (options.scope) {
290
- const unauthorized = requireScope(c, options.scope);
291
- if (unauthorized) return unauthorized;
292
- }
229
+ const unauthorized = authorizeRequest(c, options);
230
+ if (unauthorized) return unauthorized;
293
231
  const body = await c.req.json().catch(() => ({}));
294
232
  return c.json({
295
233
  ok: true,
@@ -297,10 +235,8 @@ function registerAgentRoutes(app, options) {
297
235
  });
298
236
  });
299
237
  app.post(withPrefix(prefix, "/graph/search"), async (c) => {
300
- if (options.scope) {
301
- const unauthorized = requireScope(c, options.scope);
302
- if (unauthorized) return unauthorized;
303
- }
238
+ const unauthorized = authorizeRequest(c, options);
239
+ if (unauthorized) return unauthorized;
304
240
  const body = await c.req.json().catch(() => ({}));
305
241
  const query = String(body.query ?? "");
306
242
  const scope = String(body.scope ?? "sections");
@@ -308,10 +244,8 @@ function registerAgentRoutes(app, options) {
308
244
  return c.json({ ok: true, payload });
309
245
  });
310
246
  app.post(withPrefix(prefix, "/graph/subgraph"), async (c) => {
311
- if (options.scope) {
312
- const unauthorized = requireScope(c, options.scope);
313
- if (unauthorized) return unauthorized;
314
- }
247
+ const unauthorized = authorizeRequest(c, options);
248
+ if (unauthorized) return unauthorized;
315
249
  const body = await c.req.json().catch(() => ({}));
316
250
  const payload = await options.sdk.getSubgraph(
317
251
  Array.isArray(body.seedIds) ? body.seedIds.map(String) : [],
@@ -320,10 +254,8 @@ function registerAgentRoutes(app, options) {
320
254
  return c.json({ ok: true, payload });
321
255
  });
322
256
  app.post(withPrefix(prefix, "/graph/query"), async (c) => {
323
- if (options.scope) {
324
- const unauthorized = requireScope(c, options.scope);
325
- if (unauthorized) return unauthorized;
326
- }
257
+ const unauthorized = authorizeRequest(c, options);
258
+ if (unauthorized) return unauthorized;
327
259
  const body = await c.req.json().catch(() => ({}));
328
260
  const payload = await options.sdk.queryGraph(body);
329
261
  if (typeof body.workDayId === "string" && body.workDayId) {
@@ -344,10 +276,8 @@ function registerAgentRoutes(app, options) {
344
276
  return c.json({ ok: true, payload });
345
277
  });
346
278
  app.post(withPrefix(prefix, "/graph/context-pack"), async (c) => {
347
- if (options.scope) {
348
- const unauthorized = requireScope(c, options.scope);
349
- if (unauthorized) return unauthorized;
350
- }
279
+ const unauthorized = authorizeRequest(c, options);
280
+ if (unauthorized) return unauthorized;
351
281
  const body = await c.req.json().catch(() => ({}));
352
282
  const payload = await options.sdk.buildContextPack(body);
353
283
  if (typeof body.workDayId === "string" && body.workDayId) {
@@ -372,19 +302,15 @@ function registerAgentRoutes(app, options) {
372
302
  return c.json({ ok: true, payload });
373
303
  });
374
304
  app.post(withPrefix(prefix, "/graph/parse-dsl"), async (c) => {
375
- if (options.scope) {
376
- const unauthorized = requireScope(c, options.scope);
377
- if (unauthorized) return unauthorized;
378
- }
305
+ const unauthorized = authorizeRequest(c, options);
306
+ if (unauthorized) return unauthorized;
379
307
  const body = await c.req.json().catch(() => ({}));
380
308
  const payload = await options.sdk.parseGraphDsl(String(body.source ?? body.query ?? ""));
381
309
  return c.json({ ok: true, payload });
382
310
  });
383
311
  app.get(withPrefix(prefix, "/graph/node/:id"), async (c) => {
384
- if (options.scope) {
385
- const unauthorized = requireScope(c, options.scope);
386
- if (unauthorized) return unauthorized;
387
- }
312
+ const unauthorized = authorizeRequest(c, options);
313
+ if (unauthorized) return unauthorized;
388
314
  const payload = await options.sdk.getGraphNode(c.req.param("id"));
389
315
  return payload ? c.json({ ok: true, payload }) : jsonError(c, 404, "Unknown graph node.");
390
316
  });
package/dist/api/app.js CHANGED
@@ -3,7 +3,7 @@ import { AgentSdk, TREESEED_REMOTE_CONTRACT_HEADER, TREESEED_REMOTE_CONTRACT_VER
3
3
  import { Hono } from "hono";
4
4
  import { registerAgentRoutes } from "./agent-routes.js";
5
5
  import { resolveApiConfig } from "./config.js";
6
- import { bearerTokenFromRequest, jsonError, requireAuthentication, requirePermission, requireScope } from "./http.js";
6
+ import { bearerTokenFromRequest, jsonError, requirePermission, requireScope } from "./http.js";
7
7
  import { registerOperationRoutes } from "./operations-routes.js";
8
8
  import { resolveApiRuntimeProviders } from "./providers.js";
9
9
  import { registerSdkRoutes } from "./sdk-routes.js";
@@ -40,11 +40,42 @@ function mergeApiOptions(options) {
40
40
  }
41
41
  };
42
42
  }
43
+ function normalizePrefix(prefix) {
44
+ if (!prefix?.trim()) return "";
45
+ const normalized = prefix.trim().replace(/\/+$/u, "");
46
+ return normalized.startsWith("/") ? normalized : `/${normalized}`;
47
+ }
48
+ function principalScopes(permissions) {
49
+ const scopes = /* @__PURE__ */ new Set(["auth:me"]);
50
+ if (permissions.includes("*:*:*") || permissions.includes("sdk:execute:global")) scopes.add("sdk");
51
+ if (permissions.includes("*:*:*") || permissions.includes("agent:execute:global")) scopes.add("agent");
52
+ if (permissions.includes("*:*:*") || permissions.includes("operations:execute:global")) scopes.add("operations");
53
+ return [...scopes];
54
+ }
55
+ function buildProjectApiPrincipal(config) {
56
+ return {
57
+ id: `project:${config.projectId}`,
58
+ displayName: config.projectApiLabel,
59
+ roles: ["project_api"],
60
+ permissions: [...config.projectApiPermissions],
61
+ scopes: principalScopes(config.projectApiPermissions),
62
+ metadata: {
63
+ projectId: config.projectId
64
+ }
65
+ };
66
+ }
67
+ function matchesProjectApiKey(token, projectApiKey) {
68
+ if (!projectApiKey) return false;
69
+ const left = Buffer.from(token);
70
+ const right = Buffer.from(projectApiKey);
71
+ return left.length === right.length && crypto.timingSafeEqual(left, right);
72
+ }
43
73
  function createTreeseedApiApp(options = {}) {
44
74
  const resolved = mergeApiOptions(options);
45
75
  const runtimeProviders = resolveApiRuntimeProviders(resolved.config, options.runtimeProviders);
46
76
  const sharedSdk = options.sdk ?? new AgentSdk({ repoRoot: resolved.config.repoRoot });
47
77
  const app = new Hono();
78
+ const internalPrefix = normalizePrefix(options.internalPrefix);
48
79
  app.use("*", async (c, next) => {
49
80
  c.set("requestId", crypto.randomUUID());
50
81
  c.set("config", resolved.config);
@@ -74,6 +105,19 @@ function createTreeseedApiApp(options = {}) {
74
105
  app.use("*", async (c, next) => {
75
106
  const token = bearerTokenFromRequest(c.req.raw);
76
107
  if (token) {
108
+ if (matchesProjectApiKey(token, resolved.config.projectApiKey)) {
109
+ const principal = buildProjectApiPrincipal(resolved.config);
110
+ c.set("principal", principal);
111
+ c.set("credential", {
112
+ type: "project_api_key",
113
+ id: resolved.config.projectId,
114
+ label: resolved.config.projectApiLabel
115
+ });
116
+ c.set("actorType", "project");
117
+ c.set("permissionGrants", principal.permissions);
118
+ await next();
119
+ return;
120
+ }
77
121
  const result = await runtimeProviders.auth.authenticateBearerToken(token);
78
122
  if (result) {
79
123
  c.set("principal", result.principal);
@@ -244,24 +288,32 @@ function createTreeseedApiApp(options = {}) {
244
288
  registerSdkRoutes(app, {
245
289
  config: resolved.config,
246
290
  sharedSdk,
247
- scope: resolved.scopes.sdk
291
+ scope: resolved.scopes.sdk,
292
+ prefix: internalPrefix
248
293
  });
249
294
  }
250
295
  if (resolved.surfaces.agent) {
251
296
  registerAgentRoutes(app, {
252
297
  sdk: sharedSdk,
253
- prefix: "/agent",
298
+ prefix: `${internalPrefix}/agent`,
254
299
  scope: resolved.scopes.agent,
255
- projectId: "treeseed-market",
300
+ projectId: resolved.config.projectId,
256
301
  defaultActor: "api"
257
302
  });
258
303
  }
259
304
  if (resolved.surfaces.operations) {
260
305
  registerOperationRoutes(app, {
261
306
  scope: resolved.scopes.operations,
307
+ prefix: internalPrefix,
262
308
  executeOperation: options.workflowExecutor
263
309
  });
264
310
  }
311
+ options.extendApp?.(app, {
312
+ resolved,
313
+ runtimeProviders,
314
+ sharedSdk,
315
+ internalPrefix
316
+ });
265
317
  app.notFound((c) => jsonError(c, 404, "Not found."));
266
318
  return app;
267
319
  }
@@ -27,6 +27,7 @@ export declare class D1AuthStore {
27
27
  private loadIdentityByProvider;
28
28
  private rolesForUser;
29
29
  private permissionsForUser;
30
+ private permissionsForRoles;
30
31
  private scopesForPrincipal;
31
32
  private principalForUser;
32
33
  private assignRole;
@@ -119,6 +119,21 @@ class D1AuthStore {
119
119
  );
120
120
  return rows.map((row) => row.key);
121
121
  }
122
+ async permissionsForRoles(roleKeys) {
123
+ if (roleKeys.length === 0) {
124
+ return [];
125
+ }
126
+ const placeholders = roleKeys.map(() => "?").join(", ");
127
+ const rows = await this.all(
128
+ `SELECT DISTINCT permissions.key AS key
129
+ FROM roles
130
+ INNER JOIN role_permissions ON role_permissions.role_id = roles.id
131
+ INNER JOIN permissions ON permissions.id = role_permissions.permission_id
132
+ WHERE roles.key IN (${placeholders})`,
133
+ roleKeys
134
+ );
135
+ return rows.map((row) => row.key);
136
+ }
122
137
  scopesForPrincipal(permissions) {
123
138
  const scopes = /* @__PURE__ */ new Set(["auth:me"]);
124
139
  if (permissions.includes("*:*:*") || permissions.includes("sdk:execute:global")) scopes.add("sdk");
@@ -573,7 +588,12 @@ class D1AuthStore {
573
588
  if (!equalHash(row.secret_hash, incomingHash)) return null;
574
589
  await this.run(`UPDATE service_credentials SET last_used_at = ?, updated_at = ? WHERE id = ?`, [isoNow(), isoNow(), row.id]);
575
590
  const roles = parseJson(row.roles_json, []);
576
- const permissions = parseJson(row.permissions_json, []);
591
+ const permissions = [
592
+ .../* @__PURE__ */ new Set([
593
+ ...await this.permissionsForRoles(roles),
594
+ ...parseJson(row.permissions_json, [])
595
+ ])
596
+ ];
577
597
  return {
578
598
  principal: {
579
599
  id: serviceId,
@@ -32,7 +32,11 @@ function resolveApiConfig(env = process.env) {
32
32
  baseUrl,
33
33
  issuer,
34
34
  repoRoot,
35
+ projectId: env.TREESEED_PROJECT_ID?.trim() || "treeseed-project",
35
36
  authSecret: env.TREESEED_API_AUTH_SECRET?.trim() || "treeseed-api-dev-secret",
37
+ projectApiKey: env.TREESEED_API_PROJECT_KEY?.trim() || void 0,
38
+ projectApiLabel: env.TREESEED_API_PROJECT_LABEL?.trim() || "Project API Key",
39
+ projectApiPermissions: parseCsv(env.TREESEED_API_PROJECT_KEY_PERMISSIONS).length > 0 ? parseCsv(env.TREESEED_API_PROJECT_KEY_PERMISSIONS) : ["sdk:execute:global", "agent:execute:global", "operations:execute:global"],
36
40
  cloudflareAccountId: env.CLOUDFLARE_ACCOUNT_ID?.trim() || void 0,
37
41
  cloudflareApiToken: env.CLOUDFLARE_API_TOKEN?.trim() || void 0,
38
42
  d1DatabaseId: env.TREESEED_API_D1_DATABASE_ID?.trim() || void 0,
@@ -18,6 +18,10 @@ export declare function requireAuthentication(c: ApiContext): Response & import(
18
18
  ok: false;
19
19
  error: string;
20
20
  }, never, "json">;
21
+ export declare function requireActorType(c: ApiContext, actorType: 'anonymous' | 'user' | 'service' | 'project', message?: string): Response & import("hono").TypedResponse<{
22
+ ok: false;
23
+ error: string;
24
+ }, never, "json">;
21
25
  export declare function requirePermission(c: ApiContext, permission: string): Response & import("hono").TypedResponse<{
22
26
  ok: false;
23
27
  error: string;
package/dist/api/http.js CHANGED
@@ -27,6 +27,12 @@ function requireAuthentication(c) {
27
27
  }
28
28
  return null;
29
29
  }
30
+ function requireActorType(c, actorType, message = "Trusted service authentication required.") {
31
+ if (c.get("actorType") !== actorType) {
32
+ return jsonError(c, 401, message);
33
+ }
34
+ return null;
35
+ }
30
36
  function requirePermission(c, permission) {
31
37
  const principal = c.get("principal");
32
38
  if (!principal || !permissionGranted(c.get("permissionGrants"), permission)) {
@@ -38,6 +44,7 @@ export {
38
44
  bearerTokenFromRequest,
39
45
  hasScope,
40
46
  jsonError,
47
+ requireActorType,
41
48
  requireAuthentication,
42
49
  requirePermission,
43
50
  requireScope
@@ -1,8 +1,8 @@
1
1
  export { createTreeseedApiApp } from './app.ts';
2
- export { createTreeseedGatewayApp } from './gateway.ts';
3
2
  export { resolveApiConfig } from './config.ts';
4
3
  export { createRailwayTreeseedApiServer } from './railway.ts';
5
4
  export { resolveApiRuntimeProviders } from './providers.ts';
5
+ export { resolveApiD1Database } from './auth/d1-database.ts';
6
6
  export { loadTemplateCatalog } from './templates.ts';
7
7
  export { MemoryDeviceCodeAuthProvider } from './auth/memory-provider.ts';
8
8
  export { D1AuthProvider } from './auth/d1-provider.ts';
package/dist/api/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { createTreeseedApiApp } from "./app.js";
2
- import { createTreeseedGatewayApp } from "./gateway.js";
3
2
  import { resolveApiConfig } from "./config.js";
4
3
  import { createRailwayTreeseedApiServer } from "./railway.js";
5
4
  import { resolveApiRuntimeProviders } from "./providers.js";
5
+ import { resolveApiD1Database } from "./auth/d1-database.js";
6
6
  import { loadTemplateCatalog } from "./templates.js";
7
7
  import { MemoryDeviceCodeAuthProvider } from "./auth/memory-provider.js";
8
8
  import { D1AuthProvider } from "./auth/d1-provider.js";
@@ -11,8 +11,8 @@ export {
11
11
  MemoryDeviceCodeAuthProvider,
12
12
  createRailwayTreeseedApiServer,
13
13
  createTreeseedApiApp,
14
- createTreeseedGatewayApp,
15
14
  loadTemplateCatalog,
16
15
  resolveApiConfig,
16
+ resolveApiD1Database,
17
17
  resolveApiRuntimeProviders
18
18
  };