@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
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { AgentSdk, RemoteTreeseedClient, RemoteTreeseedRunnerClient } from "@treeseed/sdk";
4
+ import { createServiceSdk } from "./common.js";
5
+ function integerFromEnv(name, fallback) {
6
+ const value = process.env[name];
7
+ if (!value) return fallback;
8
+ const parsed = Number.parseInt(value, 10);
9
+ return Number.isFinite(parsed) ? parsed : fallback;
10
+ }
11
+ function envValue(name) {
12
+ const value = process.env[name]?.trim();
13
+ return value ? value : "";
14
+ }
15
+ function resolveRemoteRunnerConfig() {
16
+ return {
17
+ marketBaseUrl: envValue("TREESEED_MARKET_API_BASE_URL") || envValue("TREESEED_API_BASE_URL"),
18
+ projectId: envValue("TREESEED_PROJECT_ID") || "treeseed-market",
19
+ runnerToken: envValue("TREESEED_PROJECT_RUNNER_TOKEN"),
20
+ runnerId: envValue("TREESEED_REMOTE_RUNNER_ID") || `remote-runner-${process.pid}`,
21
+ batchSize: integerFromEnv("TREESEED_REMOTE_RUNNER_BATCH_SIZE", 1),
22
+ pollIntervalMs: integerFromEnv("TREESEED_REMOTE_RUNNER_POLL_INTERVAL_MS", 5e3)
23
+ };
24
+ }
25
+ function createRunnerClient(config, fetchImpl) {
26
+ if (!config.marketBaseUrl || !config.runnerToken) {
27
+ throw new Error(
28
+ "Remote runner requires TREESEED_MARKET_API_BASE_URL (or TREESEED_API_BASE_URL) and TREESEED_PROJECT_RUNNER_TOKEN."
29
+ );
30
+ }
31
+ return new RemoteTreeseedRunnerClient(new RemoteTreeseedClient({
32
+ hosts: [{ id: "market", baseUrl: config.marketBaseUrl }],
33
+ activeHostId: "market",
34
+ auth: {
35
+ accessToken: config.runnerToken
36
+ }
37
+ }, {
38
+ fetchImpl
39
+ }));
40
+ }
41
+ async function runRemoteRunnerCycle(options = {}) {
42
+ const config = options.config ?? resolveRemoteRunnerConfig();
43
+ const sdk = options.sdk ?? createServiceSdk();
44
+ const runner = createRunnerClient(config, options.fetchImpl);
45
+ const pulled = await runner.pull(config.projectId, {
46
+ limit: config.batchSize,
47
+ runnerId: config.runnerId
48
+ });
49
+ if (pulled.payload.length === 0) {
50
+ return { ok: true, processed: 0 };
51
+ }
52
+ let processed = 0;
53
+ for (const job of pulled.payload) {
54
+ try {
55
+ await runner.progress(job.id, {
56
+ summary: `Running ${job.namespace}:${job.operation}`,
57
+ data: {
58
+ runnerId: config.runnerId,
59
+ status: "running"
60
+ }
61
+ });
62
+ const result = await sdk.dispatch({
63
+ namespace: job.namespace,
64
+ operation: job.operation,
65
+ input: job.input ?? {},
66
+ preferredMode: "prefer_local"
67
+ });
68
+ await runner.complete(job.id, {
69
+ output: result.mode === "inline" ? result.payload : result
70
+ });
71
+ processed += 1;
72
+ } catch (error) {
73
+ await runner.fail(job.id, {
74
+ code: "runner_execution_failed",
75
+ message: error instanceof Error ? error.message : String(error)
76
+ }).catch(() => null);
77
+ }
78
+ }
79
+ return { ok: true, processed };
80
+ }
81
+ async function startRemoteRunnerLoop(options = {}) {
82
+ const config = options.config ?? resolveRemoteRunnerConfig();
83
+ for (; ; ) {
84
+ try {
85
+ await runRemoteRunnerCycle({
86
+ ...options,
87
+ config
88
+ });
89
+ } catch (error) {
90
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
91
+ `);
92
+ }
93
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, config.pollIntervalMs));
94
+ }
95
+ }
96
+ const currentFile = fileURLToPath(import.meta.url);
97
+ const entryFile = process.argv[1] ?? "";
98
+ if (entryFile === currentFile) {
99
+ await startRemoteRunnerLoop();
100
+ }
101
+ export {
102
+ resolveRemoteRunnerConfig,
103
+ runRemoteRunnerCycle,
104
+ startRemoteRunnerLoop
105
+ };
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
- import { createGatewayClient, createServiceSdk } from "./common.js";
3
+ import { createServiceSdk } from "./common.js";
4
4
  async function runWorkdayReport() {
5
5
  const sdk = createServiceSdk();
6
- const gateway = createGatewayClient();
7
6
  const workDays = await sdk.search({ model: "work_day", limit: 1 });
8
7
  const active = workDays.payload[0];
9
8
  if (!active || typeof active.id !== "string") {
@@ -16,21 +15,18 @@ async function runWorkdayReport() {
16
15
  failedTasks: tasks.payload.filter((entry) => entry.state === "failed").length,
17
16
  pendingTasks: tasks.payload.filter((entry) => entry.state !== "completed" && entry.state !== "failed").length
18
17
  };
19
- if (gateway) {
20
- await gateway.requestJson("/reports", {
21
- body: {
22
- workDayId: active.id,
23
- kind: "workday_summary",
24
- body: summary
25
- }
26
- });
27
- await gateway.requestJson(`/workdays/${encodeURIComponent(active.id)}/close`, {
28
- body: {
29
- state: "completed",
30
- summary
31
- }
32
- });
33
- }
18
+ await sdk.createReport({
19
+ workDayId: active.id,
20
+ kind: "workday_summary",
21
+ body: summary,
22
+ actor: "workday-report"
23
+ });
24
+ await sdk.closeWorkDay({
25
+ id: active.id,
26
+ state: "completed",
27
+ summary,
28
+ actor: "workday-report"
29
+ });
34
30
  return { ok: true, workDayId: active.id, summary };
35
31
  }
36
32
  const currentFile = fileURLToPath(import.meta.url);
@@ -1,2 +1,6 @@
1
1
  #!/usr/bin/env node
2
- export declare function runWorkdayStart(): Promise<any>;
2
+ export declare function runWorkdayStart(): Promise<{
3
+ ok: boolean;
4
+ workDay: import("@treeseed/sdk").SdkWorkDayEntity;
5
+ seededTasks: any[];
6
+ }>;
@@ -1,20 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
- import { resolveWorkerConfig } from "./common.js";
3
+ import { createServiceSdk, resolveManagerConfig, startAndSeedWorkday } from "./common.js";
4
4
  async function runWorkdayStart() {
5
- const managerBaseUrl = resolveWorkerConfig().managerBaseUrl;
6
- const response = await fetch(`${managerBaseUrl}/internal/workdays/start`, {
7
- method: "POST",
8
- headers: {
9
- accept: "application/json",
10
- "content-type": "application/json"
11
- },
12
- body: JSON.stringify({})
5
+ const sdk = createServiceSdk();
6
+ const config = resolveManagerConfig();
7
+ return startAndSeedWorkday(sdk, {
8
+ projectId: config.projectId,
9
+ capacityBudget: config.defaultCapacityBudget,
10
+ actor: "manager"
13
11
  });
14
- if (!response.ok) {
15
- throw new Error(`Workday start failed with ${response.status}.`);
16
- }
17
- return response.json();
18
12
  }
19
13
  const currentFile = fileURLToPath(import.meta.url);
20
14
  const entryFile = process.argv[1] ?? "";
@@ -1,27 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
- import { createGatewayClient, createQueueClient, resolveWorkerConfig } from "./common.js";
4
- async function managerRequest(baseUrl, path, body) {
5
- const response = await fetch(`${baseUrl}${path}`, {
6
- method: "POST",
7
- headers: {
8
- accept: "application/json",
9
- "content-type": "application/json"
10
- },
11
- body: JSON.stringify(body)
12
- });
13
- const payload = await response.json().catch(() => ({}));
14
- if (!response.ok) {
15
- throw new Error(typeof payload.error === "string" ? payload.error : `Manager request failed with ${response.status}.`);
16
- }
17
- return payload;
18
- }
3
+ import { buildTaskContext, createQueueClient, createServiceSdk, resolveWorkerConfig } from "./common.js";
19
4
  async function runWorkerCycle() {
20
- const gateway = createGatewayClient();
5
+ const sdk = createServiceSdk();
21
6
  const queue = createQueueClient();
22
7
  const config = resolveWorkerConfig();
23
- if (!gateway || !queue) {
24
- throw new Error("Worker requires TREESEED_GATEWAY_BASE_URL, TREESEED_GATEWAY_BEARER_TOKEN, CLOUDFLARE_ACCOUNT_ID, TREESEED_QUEUE_ID, and TREESEED_QUEUE_PULL_TOKEN.");
8
+ if (!queue) {
9
+ throw new Error("Worker requires CLOUDFLARE_ACCOUNT_ID, TREESEED_QUEUE_ID, and TREESEED_QUEUE_PULL_TOKEN.");
25
10
  }
26
11
  const pulled = await queue.pull({
27
12
  batchSize: config.batchSize,
@@ -33,52 +18,48 @@ async function runWorkerCycle() {
33
18
  let processed = 0;
34
19
  for (const message of pulled.messages) {
35
20
  try {
36
- await gateway.requestJson(`/tasks/${encodeURIComponent(message.body.taskId)}/claim`, {
37
- body: {
38
- workerId: config.workerId,
39
- leaseSeconds: config.leaseSeconds
40
- }
21
+ await sdk.claimTask({
22
+ id: message.body.taskId,
23
+ workerId: config.workerId,
24
+ leaseSeconds: config.leaseSeconds,
25
+ actor: "worker"
41
26
  });
42
- const context = await managerRequest(
43
- config.managerBaseUrl,
44
- "/internal/context/resolve-task",
45
- { taskId: message.body.taskId }
46
- );
47
- const task = context.payload.task;
27
+ const context = await buildTaskContext(sdk, message.body.taskId);
28
+ const task = context.task;
48
29
  const payload = task && typeof task.payloadJson === "string" ? JSON.parse(task.payloadJson) : {};
49
- await gateway.requestJson(`/tasks/${encodeURIComponent(message.body.taskId)}/progress`, {
50
- body: {
51
- workerId: config.workerId,
52
- state: "running",
53
- appendEvent: {
54
- kind: "worker_started",
55
- data: { workerId: config.workerId, queueAttempt: message.attempts }
56
- }
57
- }
30
+ await sdk.recordTaskProgress({
31
+ id: message.body.taskId,
32
+ workerId: config.workerId,
33
+ state: "running",
34
+ appendEvent: {
35
+ kind: "worker_started",
36
+ data: { workerId: config.workerId, queueAttempt: message.attempts }
37
+ },
38
+ actor: "worker"
58
39
  });
59
- await gateway.requestJson(`/tasks/${encodeURIComponent(message.body.taskId)}/complete`, {
60
- body: {
61
- output: {
62
- workerId: config.workerId,
63
- queueAttempt: message.attempts,
64
- payload
65
- },
66
- summary: {
67
- status: "completed",
68
- workerId: config.workerId
69
- }
70
- }
40
+ await sdk.completeTask({
41
+ id: message.body.taskId,
42
+ output: {
43
+ workerId: config.workerId,
44
+ queueAttempt: message.attempts,
45
+ payload
46
+ },
47
+ summary: {
48
+ status: "completed",
49
+ workerId: config.workerId
50
+ },
51
+ actor: "worker"
71
52
  });
72
53
  await queue.ack([message.leaseId]);
73
54
  processed += 1;
74
55
  } catch (error) {
75
56
  const retryDelaySeconds = Math.min(300, Math.max(15, message.attempts * 30));
76
- await gateway.requestJson(`/tasks/${encodeURIComponent(message.body.taskId)}/fail`, {
77
- body: {
78
- errorMessage: error instanceof Error ? error.message : String(error),
79
- retryable: true,
80
- nextVisibleAt: new Date(Date.now() + retryDelaySeconds * 1e3).toISOString()
81
- }
57
+ await sdk.failTask({
58
+ id: message.body.taskId,
59
+ errorMessage: error instanceof Error ? error.message : String(error),
60
+ retryable: true,
61
+ nextVisibleAt: new Date(Date.now() + retryDelaySeconds * 1e3).toISOString(),
62
+ actor: "worker"
82
63
  }).catch(() => null);
83
64
  await queue.retry([{ leaseId: message.leaseId, delaySeconds: retryDelaySeconds }]);
84
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/core",
3
- "version": "0.4.9",
3
+ "version": "0.4.10",
4
4
  "description": "Treeseed integrated platform starter for Astro/Starlight web runtimes and Hono API runtimes.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -40,8 +40,8 @@
40
40
  "dev": "node ./scripts/run-ts.mjs ./scripts/dev-platform.ts",
41
41
  "dev:web": "node ./scripts/run-ts.mjs ./scripts/dev-platform.ts --surface web",
42
42
  "dev:api": "node ./scripts/run-ts.mjs ./scripts/dev-platform.ts --surface api",
43
- "dev:manager": "node ./scripts/run-ts.mjs ./src/services/manager.ts",
44
43
  "dev:worker": "node ./scripts/run-ts.mjs ./src/services/worker.ts",
44
+ "dev:remote-runner": "node ./scripts/run-ts.mjs ./src/services/remote-runner.ts",
45
45
  "dev:workday-start": "node ./scripts/run-ts.mjs ./src/services/workday-start.ts",
46
46
  "dev:workday-report": "node ./scripts/run-ts.mjs ./src/services/workday-report.ts",
47
47
  "dev:watch": "node ./scripts/run-ts.mjs ./scripts/dev-platform.ts --watch",
@@ -67,7 +67,7 @@
67
67
  "release:publish": "node ./scripts/run-ts.mjs ./scripts/publish-package.ts"
68
68
  },
69
69
  "dependencies": {
70
- "@treeseed/sdk": "^0.4.8",
70
+ "@treeseed/sdk": "^0.4.9",
71
71
  "@astrojs/check": "^0.9.8",
72
72
  "@astrojs/cloudflare": "^12.6.13",
73
73
  "@astrojs/sitemap": "3.7.0",
@@ -159,14 +159,14 @@
159
159
  "./tenant": "./dist/tenant/bridge.js",
160
160
  "./scripts/dev-platform": "./dist/scripts/dev-platform.js",
161
161
  "./scripts/workspace-bootstrap": "./dist/scripts/workspace-bootstrap.js",
162
- "./services/manager": {
163
- "types": "./dist/services/manager.d.ts",
164
- "default": "./dist/services/manager.js"
165
- },
166
162
  "./services/worker": {
167
163
  "types": "./dist/services/worker.d.ts",
168
164
  "default": "./dist/services/worker.js"
169
165
  },
166
+ "./services/remote-runner": {
167
+ "types": "./dist/services/remote-runner.d.ts",
168
+ "default": "./dist/services/remote-runner.js"
169
+ },
170
170
  "./services/workday-start": {
171
171
  "types": "./dist/services/workday-start.d.ts",
172
172
  "default": "./dist/services/workday-start.js"
@@ -179,10 +179,6 @@
179
179
  "types": "./dist/api/app.d.ts",
180
180
  "default": "./dist/api/app.js"
181
181
  },
182
- "./api/gateway": {
183
- "types": "./dist/api/gateway.d.ts",
184
- "default": "./dist/api/gateway.js"
185
- },
186
182
  "./site-resources": {
187
183
  "types": "./dist/site-resources.d.ts",
188
184
  "default": "./dist/site-resources.js"
@@ -1,5 +0,0 @@
1
- import { Hono } from 'hono';
2
- import type { AppVariables, GatewayServerOptions } from './types.ts';
3
- export declare function createTreeseedGatewayApp(options: GatewayServerOptions): Hono<{
4
- Variables: AppVariables;
5
- }, import("hono/types").BlankSchema, "/">;
@@ -1,35 +0,0 @@
1
- import { Hono } from "hono";
2
- import { AgentSdk } from "@treeseed/sdk";
3
- import { registerAgentRoutes } from "./agent-routes.js";
4
- import { bearerTokenFromRequest, jsonError } from "./http.js";
5
- function createTreeseedGatewayApp(options) {
6
- const sdk = options.sdk instanceof AgentSdk ? options.sdk : new AgentSdk();
7
- const app = new Hono();
8
- app.use("*", async (c, next) => {
9
- const token = bearerTokenFromRequest(c.req.raw);
10
- if (token !== options.bearerToken) {
11
- return jsonError(c, 401, "Unauthorized gateway request.");
12
- }
13
- c.set("requestId", "gateway");
14
- c.set("config", null);
15
- c.set("principal", null);
16
- c.set("actingUser", null);
17
- c.set("credential", null);
18
- c.set("actorType", "service");
19
- c.set("permissionGrants", []);
20
- await next();
21
- });
22
- app.get("/healthz", (c) => c.json({ ok: true, service: "treeseed-agent-gateway" }));
23
- registerAgentRoutes(app, {
24
- sdk,
25
- prefix: "",
26
- scope: null,
27
- projectId: options.projectId,
28
- queueProducer: options.queueProducer,
29
- defaultActor: "gateway"
30
- });
31
- return app;
32
- }
33
- export {
34
- createTreeseedGatewayApp
35
- };
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Hono } from 'hono';
3
- import { AgentSdk } from '@treeseed/sdk/sdk';
4
- export declare function createManagerApp(sdk?: AgentSdk): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
@@ -1,199 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createServer } from "node:http";
3
- import { Readable } from "node:stream";
4
- import { fileURLToPath } from "node:url";
5
- import { Hono } from "hono";
6
- import { AgentSdk } from "@treeseed/sdk/sdk";
7
- import { createServiceSdk, resolveManagerConfig } from "./common.js";
8
- async function honoNodeHandler(app, request, response) {
9
- const origin = request.headers.host ? `http://${request.headers.host}` : "http://127.0.0.1";
10
- const url = new URL(request.url ?? "/", origin);
11
- const webRequest = new Request(url, {
12
- method: request.method,
13
- headers: request.headers,
14
- body: request.method !== "GET" && request.method !== "HEAD" ? request : void 0,
15
- duplex: "half"
16
- });
17
- const webResponse = await app.fetch(webRequest);
18
- response.statusCode = webResponse.status;
19
- webResponse.headers.forEach((value, key) => response.setHeader(key, value));
20
- if (!webResponse.body) {
21
- response.end();
22
- return;
23
- }
24
- Readable.fromWeb(webResponse.body).pipe(response);
25
- }
26
- async function seedRootTasks(sdk, workDayId) {
27
- const specs = await sdk.listAgentSpecs({ enabled: true });
28
- const created = [];
29
- for (const spec of specs) {
30
- const hasStartTrigger = spec.triggers.some((trigger) => trigger.type === "startup" || trigger.type === "schedule");
31
- if (!hasStartTrigger) continue;
32
- created.push(await sdk.createTask({
33
- workDayId,
34
- agentId: spec.slug,
35
- type: "agent_root",
36
- priority: 100,
37
- idempotencyKey: `${workDayId}:${spec.slug}:root`,
38
- payload: {
39
- agentSlug: spec.slug,
40
- handler: spec.handler,
41
- triggerKinds: spec.triggers.map((entry) => entry.type)
42
- },
43
- graphVersion: null,
44
- actor: "manager"
45
- }));
46
- }
47
- return created;
48
- }
49
- function createManagerApp(sdk = createServiceSdk()) {
50
- const config = resolveManagerConfig();
51
- const app = new Hono();
52
- app.get("/internal/healthz", (c) => c.json({ ok: true, service: "manager" }));
53
- app.post("/internal/workdays/start", async (c) => {
54
- const body = await c.req.json().catch(() => ({}));
55
- const graphRefresh = await sdk.refreshGraph();
56
- const workDay = await sdk.startWorkDay({
57
- id: typeof body.id === "string" ? body.id : void 0,
58
- projectId: config.projectId,
59
- capacityBudget: Number(body.capacityBudget ?? config.defaultCapacityBudget),
60
- graphVersion: graphRefresh.snapshotRoot,
61
- summary: { graphRefresh },
62
- actor: "manager"
63
- });
64
- const tasks = workDay.payload ? await seedRootTasks(sdk, String(workDay.payload.id)) : [];
65
- return c.json({
66
- ok: true,
67
- workDay: workDay.payload,
68
- seededTasks: tasks.map((entry) => entry.payload).filter(Boolean)
69
- });
70
- });
71
- app.post("/internal/workdays/:id/close", async (c) => {
72
- const body = await c.req.json().catch(() => ({}));
73
- const result = await sdk.closeWorkDay({
74
- id: c.req.param("id"),
75
- state: body.state,
76
- summary: body.summary ?? null,
77
- actor: "manager"
78
- });
79
- return c.json({ ok: true, payload: result.payload });
80
- });
81
- app.post("/internal/context/resolve-task", async (c) => {
82
- const body = await c.req.json().catch(() => ({}));
83
- const taskId = String(body.taskId ?? "");
84
- const context = await sdk.getManagerContext(taskId);
85
- const task = context.payload.task;
86
- const agent = task ? (await sdk.get({ model: "agent", slug: String(task.agentId) })).payload : null;
87
- return c.json({
88
- ok: true,
89
- payload: {
90
- ...context.payload,
91
- agent
92
- }
93
- });
94
- });
95
- app.post("/internal/graph/search", async (c) => {
96
- const body = await c.req.json().catch(() => ({}));
97
- const query = String(body.query ?? "");
98
- const scope = String(body.scope ?? "sections");
99
- const payload = scope === "files" ? await sdk.searchFiles(query, body.options) : scope === "entities" ? await sdk.searchEntities(query, body.options) : await sdk.searchSections(query, body.options);
100
- return c.json({ ok: true, payload });
101
- });
102
- app.post("/internal/graph/subgraph", async (c) => {
103
- const body = await c.req.json().catch(() => ({}));
104
- const payload = await sdk.getSubgraph(
105
- Array.isArray(body.seedIds) ? body.seedIds.map(String) : [],
106
- body.options
107
- );
108
- return c.json({ ok: true, payload });
109
- });
110
- app.post("/internal/graph/query", async (c) => {
111
- const body = await c.req.json().catch(() => ({}));
112
- const payload = await sdk.queryGraph(body);
113
- if (typeof body.workDayId === "string" && body.workDayId) {
114
- await sdk.create({
115
- model: "graph_run",
116
- data: {
117
- workDayId: body.workDayId,
118
- corpusHash: String(body.corpusHash ?? "query-graph"),
119
- graphVersion: String(body.graphVersion ?? ""),
120
- queryJson: JSON.stringify(body ?? {}),
121
- seedIdsJson: JSON.stringify(payload.seedIds),
122
- selectedNodeIdsJson: JSON.stringify(payload.nodes.map((entry) => entry.node.id)),
123
- statsJson: JSON.stringify({ nodeCount: payload.nodes.length, edgeCount: payload.edges.length })
124
- },
125
- actor: "manager"
126
- });
127
- }
128
- return c.json({ ok: true, payload });
129
- });
130
- app.post("/internal/graph/context-pack", async (c) => {
131
- const body = await c.req.json().catch(() => ({}));
132
- const payload = await sdk.buildContextPack(body);
133
- if (typeof body.workDayId === "string" && body.workDayId) {
134
- await sdk.create({
135
- model: "graph_run",
136
- data: {
137
- workDayId: body.workDayId,
138
- corpusHash: String(body.corpusHash ?? "context-pack"),
139
- graphVersion: String(body.graphVersion ?? ""),
140
- queryJson: JSON.stringify(body ?? {}),
141
- seedIdsJson: JSON.stringify(payload.seedIds),
142
- selectedNodeIdsJson: JSON.stringify(payload.includedNodeIds),
143
- statsJson: JSON.stringify({ nodeCount: payload.nodes.length, edgeCount: payload.edges.length, totalTokenEstimate: payload.totalTokenEstimate })
144
- },
145
- actor: "manager"
146
- });
147
- }
148
- return c.json({ ok: true, payload });
149
- });
150
- app.post("/internal/graph/parse-dsl", async (c) => {
151
- const body = await c.req.json().catch(() => ({}));
152
- const payload = await sdk.parseGraphDsl(String(body.source ?? body.query ?? ""));
153
- return c.json({ ok: true, payload });
154
- });
155
- app.get("/internal/graph/node/:id", async (c) => {
156
- const payload = await sdk.getGraphNode(c.req.param("id"));
157
- return payload ? c.json({ ok: true, payload }) : c.json({ ok: false, error: "Unknown graph node." }, 404);
158
- });
159
- app.post("/internal/tasks/:id/followups", async (c) => {
160
- const body = await c.req.json().catch(() => ({}));
161
- const current = await sdk.get({ model: "task", id: c.req.param("id") });
162
- if (!current.payload) {
163
- return c.json({ ok: false, error: "Unknown task." }, 404);
164
- }
165
- const followups = Array.isArray(body.followups) ? body.followups : [];
166
- const created = [];
167
- for (const followup of followups) {
168
- created.push(await sdk.createTask({
169
- workDayId: String(current.payload.workDayId ?? ""),
170
- agentId: String(followup.agentId ?? current.payload.agentId ?? ""),
171
- type: String(followup.type ?? "followup"),
172
- priority: Number(followup.priority ?? 0),
173
- idempotencyKey: String(followup.idempotencyKey ?? `${c.req.param("id")}:${created.length}`),
174
- payload: followup.payload ?? {},
175
- graphVersion: typeof followup.graphVersion === "string" ? followup.graphVersion : null,
176
- parentTaskId: c.req.param("id"),
177
- actor: "manager"
178
- }));
179
- }
180
- return c.json({ ok: true, payload: created.map((entry) => entry.payload) });
181
- });
182
- return app;
183
- }
184
- const currentFile = fileURLToPath(import.meta.url);
185
- const entryFile = process.argv[1] ?? "";
186
- if (entryFile === currentFile) {
187
- const config = resolveManagerConfig();
188
- const app = createManagerApp();
189
- const server = createServer((req, res) => {
190
- void honoNodeHandler(app, req, res);
191
- });
192
- server.listen(config.port, config.host, () => {
193
- process.stdout.write(`Treeseed manager listening on http://${config.host}:${config.port}
194
- `);
195
- });
196
- }
197
- export {
198
- createManagerApp
199
- };