@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.
- package/README.md +1 -2
- package/dist/agent.d.ts +0 -1
- package/dist/agent.js +0 -2
- package/dist/agents/spec-types.d.ts +10 -10
- package/dist/api/agent-routes.d.ts +2 -2
- package/dist/api/agent-routes.js +51 -125
- package/dist/api/app.js +56 -4
- package/dist/api/auth/d1-store.d.ts +1 -0
- package/dist/api/auth/d1-store.js +21 -1
- package/dist/api/config.js +4 -0
- package/dist/api/http.d.ts +4 -0
- package/dist/api/http.js +7 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +2 -2
- package/dist/api/operations-routes.d.ts +1 -0
- package/dist/api/operations-routes.js +6 -1
- package/dist/api/railway.d.ts +4 -0
- package/dist/api/sdk-dispatch.d.ts +2 -11
- package/dist/api/sdk-dispatch.js +1 -133
- package/dist/api/sdk-routes.d.ts +1 -0
- package/dist/api/sdk-routes.js +5 -1
- package/dist/api/types.d.ts +32 -16
- package/dist/dev.js +24 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/scripts/test-smoke.js +0 -1
- package/dist/services/common.d.ts +37 -4
- package/dist/services/common.js +135 -17
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +3 -2
- package/dist/services/remote-runner.d.ts +23 -0
- package/dist/services/remote-runner.js +105 -0
- package/dist/services/workday-report.js +13 -17
- package/dist/services/workday-start.d.ts +5 -1
- package/dist/services/workday-start.js +7 -13
- package/dist/services/worker.js +38 -57
- package/package.json +7 -11
- package/dist/api/gateway.d.ts +0 -5
- package/dist/api/gateway.js +0 -35
- package/dist/services/manager.d.ts +0 -4
- 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:
|
|
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,
|
|
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 {
|
|
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 {};
|
package/dist/api/agent-routes.js
CHANGED
|
@@ -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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
13
|
+
function authorizeRequest(c, options) {
|
|
14
|
+
const routeUnauthorized = options.authorize?.(c);
|
|
15
|
+
if (routeUnauthorized) {
|
|
16
|
+
return routeUnauthorized;
|
|
30
17
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
throw new Error("Unknown task.");
|
|
18
|
+
if (options.scope) {
|
|
19
|
+
return requireScope(c, options.scope);
|
|
34
20
|
}
|
|
35
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
|
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
|
-
|
|
227
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
290
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
|
|
324
|
-
|
|
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
|
-
|
|
348
|
-
|
|
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
|
-
|
|
376
|
-
|
|
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
|
-
|
|
385
|
-
|
|
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,
|
|
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:
|
|
298
|
+
prefix: `${internalPrefix}/agent`,
|
|
254
299
|
scope: resolved.scopes.agent,
|
|
255
|
-
projectId:
|
|
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
|
}
|
|
@@ -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 =
|
|
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,
|
package/dist/api/config.js
CHANGED
|
@@ -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,
|
package/dist/api/http.d.ts
CHANGED
|
@@ -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
|
package/dist/api/index.d.ts
CHANGED
|
@@ -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
|
};
|