@treeseed/core 0.4.13 → 0.5.3

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 (81) hide show
  1. package/dist/agents/adapters/notification.d.ts +16 -1
  2. package/dist/agents/adapters/notification.js +31 -1
  3. package/dist/agents/adapters/research.d.ts +13 -1
  4. package/dist/agents/adapters/research.js +35 -1
  5. package/dist/agents/contracts/run.d.ts +1 -0
  6. package/dist/agents/kernel/agent-kernel.d.ts +2 -2
  7. package/dist/agents/kernel/agent-kernel.js +10 -3
  8. package/dist/agents/kernel/trigger-resolver.d.ts +1 -0
  9. package/dist/agents/kernel/trigger-resolver.js +5 -1
  10. package/dist/agents/runtime-types.d.ts +1 -0
  11. package/dist/api/app.js +10 -0
  12. package/dist/api/auth/d1-store.js +5 -0
  13. package/dist/api/auth/memory-provider.js +6 -1
  14. package/dist/api/auth/rbac.d.ts +2 -2
  15. package/dist/api/auth/rbac.js +2 -0
  16. package/dist/api/capabilities.d.ts +9 -0
  17. package/dist/api/capabilities.js +33 -0
  18. package/dist/api/operations-routes.d.ts +4 -0
  19. package/dist/api/operations-routes.js +49 -1
  20. package/dist/api/project-routes.d.ts +8 -0
  21. package/dist/api/project-routes.js +586 -0
  22. package/dist/api/types.d.ts +7 -0
  23. package/dist/components/site/NotesList.astro +13 -2
  24. package/dist/components/site/PublishedContentBody.astro +5 -0
  25. package/dist/content.js +77 -9
  26. package/dist/dev.d.ts +2 -2
  27. package/dist/dev.js +0 -15
  28. package/dist/env.yaml +39 -26
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +7 -1
  31. package/dist/launch.d.ts +3 -0
  32. package/dist/launch.js +8 -0
  33. package/dist/layouts/AuthoredEntryLayout.astro +76 -28
  34. package/dist/layouts/ProfileLayout.astro +9 -5
  35. package/dist/middleware.js +11 -0
  36. package/dist/pages/[slug].astro +10 -6
  37. package/dist/pages/agents/[slug].astro +17 -7
  38. package/dist/pages/agents/index.astro +2 -1
  39. package/dist/pages/books/[slug].astro +10 -5
  40. package/dist/pages/books/index.astro +4 -1
  41. package/dist/pages/decisions/[slug].astro +73 -0
  42. package/dist/pages/decisions/index.astro +47 -0
  43. package/dist/pages/docs-runtime/[...slug].astro +102 -0
  44. package/dist/pages/docs-runtime/index.astro +89 -0
  45. package/dist/pages/feed.xml.js +2 -1
  46. package/dist/pages/index.astro +160 -16
  47. package/dist/pages/notes/[slug].astro +10 -5
  48. package/dist/pages/notes/index.astro +6 -3
  49. package/dist/pages/objectives/[slug].astro +27 -9
  50. package/dist/pages/objectives/index.astro +19 -2
  51. package/dist/pages/people/[slug].astro +17 -7
  52. package/dist/pages/people/index.astro +2 -1
  53. package/dist/pages/proposals/[slug].astro +72 -0
  54. package/dist/pages/proposals/index.astro +47 -0
  55. package/dist/pages/questions/[slug].astro +27 -9
  56. package/dist/pages/questions/index.astro +19 -2
  57. package/dist/scripts/dev-platform.js +0 -1
  58. package/dist/scripts/release-verify.js +29 -2
  59. package/dist/scripts/tenant-build.js +4 -1
  60. package/dist/scripts/tenant-check.js +4 -1
  61. package/dist/services/agents.d.ts +1 -12
  62. package/dist/services/agents.js +28 -9
  63. package/dist/services/index.d.ts +0 -2
  64. package/dist/services/index.js +0 -6
  65. package/dist/services/manager.d.ts +4 -4
  66. package/dist/services/manager.js +123 -50
  67. package/dist/services/workday-report.d.ts +3 -3
  68. package/dist/services/workday-start.d.ts +3 -3
  69. package/dist/services/worker-capacity.d.ts +58 -0
  70. package/dist/services/worker-capacity.js +208 -0
  71. package/dist/services/worker.js +70 -13
  72. package/dist/site.js +18 -5
  73. package/dist/tenant/runtime-config.js +8 -1
  74. package/dist/utils/hub-content.js +14 -0
  75. package/dist/utils/published-content.js +13 -0
  76. package/dist/utils/site-config.js +20 -0
  77. package/dist/utils/site-content-runtime.js +185 -0
  78. package/dist/utils/web-cache.js +149 -0
  79. package/package.json +11 -6
  80. package/scripts/verify-driver.mjs +34 -0
  81. package/templates/github/deploy.workflow.yml +11 -1
@@ -8,4 +8,19 @@ export declare class StubNotificationAdapter implements AgentNotificationAdapter
8
8
  deliveredCount: number;
9
9
  }>;
10
10
  }
11
- export declare function createNotificationAdapter(): StubNotificationAdapter;
11
+ export declare class SdkMessageNotificationAdapter implements AgentNotificationAdapter {
12
+ deliver(input: {
13
+ agent: {
14
+ slug: string;
15
+ };
16
+ runId: string;
17
+ recipients: string[];
18
+ subject: string;
19
+ body: string;
20
+ }): Promise<{
21
+ status: "completed";
22
+ summary: string;
23
+ deliveredCount: number;
24
+ }>;
25
+ }
26
+ export declare function createNotificationAdapter(): StubNotificationAdapter | SdkMessageNotificationAdapter;
@@ -1,3 +1,6 @@
1
+ import { AgentSdk } from "@treeseed/sdk";
2
+ import { resolveTreeseedTenantRoot } from "@treeseed/sdk/platform/tenant-config";
3
+ import { getTreeseedAgentProviderSelections } from "@treeseed/sdk/platform/deploy-runtime";
1
4
  class StubNotificationAdapter {
2
5
  async deliver(input) {
3
6
  return {
@@ -7,10 +10,37 @@ class StubNotificationAdapter {
7
10
  };
8
11
  }
9
12
  }
13
+ class SdkMessageNotificationAdapter {
14
+ async deliver(input) {
15
+ const sdk = AgentSdk.createLocal({ repoRoot: resolveTreeseedTenantRoot() });
16
+ await sdk.createMessage({
17
+ type: "agent.notification",
18
+ payload: {
19
+ agentSlug: input.agent.slug,
20
+ runId: input.runId,
21
+ recipients: input.recipients,
22
+ subject: input.subject,
23
+ body: input.body,
24
+ summary: `Prepared ${input.recipients.length} notification(s).`
25
+ },
26
+ relatedModel: "agent",
27
+ relatedId: input.agent.slug,
28
+ actor: "agent"
29
+ });
30
+ return {
31
+ status: "completed",
32
+ summary: input.recipients.length ? `Prepared ${input.recipients.length} notification(s).` : "Notification recorded without recipients.",
33
+ deliveredCount: input.recipients.length
34
+ };
35
+ }
36
+ }
10
37
  function createNotificationAdapter() {
11
- return new StubNotificationAdapter();
38
+ return String(
39
+ process.env.TREESEED_AGENT_NOTIFICATION_PROVIDER ?? getTreeseedAgentProviderSelections().notification
40
+ ).toLowerCase() === "sdk_message" ? new SdkMessageNotificationAdapter() : new StubNotificationAdapter();
12
41
  }
13
42
  export {
43
+ SdkMessageNotificationAdapter,
14
44
  StubNotificationAdapter,
15
45
  createNotificationAdapter
16
46
  };
@@ -11,4 +11,16 @@ export declare class StubResearchAdapter implements AgentResearchAdapter {
11
11
  sources: any[];
12
12
  }>;
13
13
  }
14
- export declare function createResearchAdapter(): StubResearchAdapter;
14
+ export declare class ProjectGraphResearchAdapter implements AgentResearchAdapter {
15
+ research(input: {
16
+ questionId: string;
17
+ reason: string | null;
18
+ runId: string;
19
+ }): Promise<{
20
+ status: "completed";
21
+ summary: string;
22
+ markdown: string;
23
+ sources: any;
24
+ }>;
25
+ }
26
+ export declare function createResearchAdapter(): StubResearchAdapter | ProjectGraphResearchAdapter;
@@ -1,3 +1,6 @@
1
+ import { AgentSdk } from "@treeseed/sdk";
2
+ import { resolveTreeseedTenantRoot } from "@treeseed/sdk/platform/tenant-config";
3
+ import { getTreeseedAgentProviderSelections } from "@treeseed/sdk/platform/deploy-runtime";
1
4
  class StubResearchAdapter {
2
5
  async research(input) {
3
6
  return {
@@ -16,10 +19,41 @@ class StubResearchAdapter {
16
19
  };
17
20
  }
18
21
  }
22
+ class ProjectGraphResearchAdapter {
23
+ async research(input) {
24
+ const repoRoot = resolveTreeseedTenantRoot();
25
+ const sdk = AgentSdk.createLocal({ repoRoot });
26
+ const graphResult = await sdk.queryGraph({
27
+ query: input.questionId,
28
+ options: {
29
+ limit: 5
30
+ }
31
+ }).catch(() => null);
32
+ const items = Array.isArray(graphResult?.items) ? graphResult.items : [];
33
+ return {
34
+ status: "completed",
35
+ summary: `Graph-backed research prepared for ${input.questionId}.`,
36
+ markdown: [
37
+ "# Research Summary",
38
+ "",
39
+ `Question: ${input.questionId}`,
40
+ `Reason: ${input.reason ?? "not provided"}`,
41
+ `Run: ${input.runId}`,
42
+ "",
43
+ items.length ? "Relevant graph context:" : "No ranked graph context was available. The question is still recorded for follow-up.",
44
+ ...items.map((item) => `- ${String(item.title ?? item.id ?? "context")}`)
45
+ ].join("\n"),
46
+ sources: items.map((item) => String(item.id ?? item.title ?? "")).filter(Boolean)
47
+ };
48
+ }
49
+ }
19
50
  function createResearchAdapter() {
20
- return new StubResearchAdapter();
51
+ return String(
52
+ process.env.TREESEED_AGENT_RESEARCH_PROVIDER ?? getTreeseedAgentProviderSelections().research
53
+ ).toLowerCase() === "project_graph" ? new ProjectGraphResearchAdapter() : new StubResearchAdapter();
21
54
  }
22
55
  export {
56
+ ProjectGraphResearchAdapter,
23
57
  StubResearchAdapter,
24
58
  createResearchAdapter
25
59
  };
@@ -18,3 +18,4 @@ export interface AgentRunTrace {
18
18
  finishedAt: string | null;
19
19
  status: string;
20
20
  }
21
+ export {};
@@ -1,4 +1,4 @@
1
- import type { AgentExecutionAdapter, AgentMutationAdapter, AgentNotificationAdapter, AgentRepositoryInspectionAdapter, AgentResearchAdapter, AgentVerificationAdapter } from '../runtime-types.ts';
1
+ import type { AgentExecutionAdapter, AgentMutationAdapter, AgentNotificationAdapter, AgentRepositoryInspectionAdapter, AgentResearchAdapter, AgentTriggerInvocation, AgentVerificationAdapter } from '../runtime-types.ts';
2
2
  import { AgentSdk } from '@treeseed/sdk/sdk';
3
3
  export declare class AgentKernel {
4
4
  private readonly sdk;
@@ -34,7 +34,7 @@ export declare class AgentKernel {
34
34
  private buildTrace;
35
35
  private categorizeError;
36
36
  private executeAgent;
37
- runAgent(slug: string, mode?: 'auto' | 'manual'): Promise<{
37
+ runAgent(slug: string, mode?: 'auto' | 'manual', invocation?: AgentTriggerInvocation | null): Promise<{
38
38
  status: string;
39
39
  summary: string;
40
40
  }>;
@@ -6,7 +6,7 @@ import { createResearchAdapter } from "../adapters/research.js";
6
6
  import { createVerificationAdapter } from "../adapters/verification.js";
7
7
  import { resolveAgentHandler } from "../registry.js";
8
8
  import { AgentSdk } from "@treeseed/sdk/sdk";
9
- import { resolveTriggerDecision } from "./trigger-resolver.js";
9
+ import { followCursorKey, resolveTriggerDecision } from "./trigger-resolver.js";
10
10
  import { loadActiveAgentSpecs, loadAllAgentSpecs, summarizeAgentSpec } from "../spec-loader.js";
11
11
  import { getTreeseedAgentProviderSelections } from "@treeseed/sdk/platform/deploy-runtime";
12
12
  import { resolveAgentRuntimeProviders } from "../../agent-runtime.js";
@@ -170,6 +170,13 @@ class AgentKernel {
170
170
  cursorKey: "last_run_at",
171
171
  cursorValue: nowIso()
172
172
  });
173
+ if (trigger.kind === "follow") {
174
+ await this.sdk.upsertCursor({
175
+ agentSlug: agent.slug,
176
+ cursorKey: followCursorKey(trigger.followModels),
177
+ cursorValue: nowIso()
178
+ });
179
+ }
173
180
  this.lastRunAt.set(agent.slug, Date.now());
174
181
  return output;
175
182
  } catch (error) {
@@ -192,7 +199,7 @@ class AgentKernel {
192
199
  this.activeRuns.delete(agent.slug);
193
200
  }
194
201
  }
195
- async runAgent(slug, mode = "manual") {
202
+ async runAgent(slug, mode = "manual", invocation) {
196
203
  const { specs, diagnostics } = await loadActiveAgentSpecs(this.sdk);
197
204
  const errors = diagnostics.filter((entry) => entry.severity === "error");
198
205
  if (errors.length) {
@@ -205,7 +212,7 @@ class AgentKernel {
205
212
  if (!agent) {
206
213
  throw new Error(`Unknown or disabled agent "${slug}".`);
207
214
  }
208
- const trigger = await this.resolveTrigger(agent, mode);
215
+ const trigger = invocation ?? await this.resolveTrigger(agent, mode);
209
216
  if (!trigger) {
210
217
  return {
211
218
  status: "waiting",
@@ -15,4 +15,5 @@ export interface TriggerResolverInput {
15
15
  lastRunAt?: number;
16
16
  sdk: ScopedAgentSdk;
17
17
  }
18
+ export declare function followCursorKey(models: string[] | undefined): string;
18
19
  export declare function resolveTriggerDecision(input: TriggerResolverInput): Promise<TriggerDecision>;
@@ -1,4 +1,7 @@
1
1
  import crypto from "node:crypto";
2
+ function followCursorKey(models) {
3
+ return `follow:${(models ?? []).join(",") || "all"}`;
4
+ }
2
5
  function buildManualInvocation(agent) {
3
6
  const scheduleLike = agent.triggers.find((trigger) => trigger.type === "schedule" || trigger.type === "startup");
4
7
  if (!scheduleLike) {
@@ -44,7 +47,7 @@ async function resolveFollowTrigger(agent, sdk, trigger) {
44
47
  const models = trigger.models ?? [];
45
48
  const since = (await sdk.getCursor({
46
49
  agentSlug: agent.slug,
47
- cursorKey: `follow:${models.join(",") || "all"}`
50
+ cursorKey: followCursorKey(models)
48
51
  })).payload ?? trigger.sinceField ?? (/* @__PURE__ */ new Date(0)).toISOString();
49
52
  for (const model of models) {
50
53
  const followed = await sdk.follow({
@@ -149,5 +152,6 @@ async function resolveTriggerDecision(input) {
149
152
  };
150
153
  }
151
154
  export {
155
+ followCursorKey,
152
156
  resolveTriggerDecision
153
157
  };
@@ -115,3 +115,4 @@ export interface AgentHandler<TInputs = unknown, TResult = unknown> {
115
115
  execute(context: AgentContext, inputs: TInputs): Promise<TResult>;
116
116
  emitOutputs(context: AgentContext, result: TResult): Promise<AgentExecutionResult>;
117
117
  }
118
+ export {};
package/dist/api/app.js CHANGED
@@ -6,6 +6,7 @@ import { resolveApiConfig } from "./config.js";
6
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
+ import { registerProjectRoutes } from "./project-routes.js";
9
10
  import { registerSdkRoutes } from "./sdk-routes.js";
10
11
  import { loadTemplateCatalog } from "./templates.js";
11
12
  function mergeApiOptions(options) {
@@ -29,6 +30,7 @@ function mergeApiOptions(options) {
29
30
  sdk: true,
30
31
  agent: true,
31
32
  operations: true,
33
+ project: true,
32
34
  ...options.surfaces ?? {}
33
35
  },
34
36
  scopes: {
@@ -303,11 +305,19 @@ function createTreeseedApiApp(options = {}) {
303
305
  }
304
306
  if (resolved.surfaces.operations) {
305
307
  registerOperationRoutes(app, {
308
+ config: resolved.config,
306
309
  scope: resolved.scopes.operations,
307
310
  prefix: internalPrefix,
311
+ sdk: sharedSdk,
308
312
  executeOperation: options.workflowExecutor
309
313
  });
310
314
  }
315
+ if (resolved.surfaces.project) {
316
+ registerProjectRoutes(app, {
317
+ config: resolved.config,
318
+ sharedSdk
319
+ });
320
+ }
311
321
  options.extendApp?.(app, {
312
322
  resolved,
313
323
  runtimeProviders,
@@ -620,6 +620,11 @@ class D1AuthStore {
620
620
  ...principalRecord.principal.metadata,
621
621
  actingSessionId: claims.sessionId,
622
622
  identityId: claims.identityId,
623
+ teamId: claims.teamId ?? null,
624
+ projectId: claims.projectId ?? null,
625
+ membershipId: claims.membershipId ?? null,
626
+ teamRoles: [...new Set((claims.teamRoles ?? []).filter((entry) => typeof entry === "string" && entry.trim()))],
627
+ teamCapabilities: [...new Set((claims.teamCapabilities ?? []).filter((entry) => typeof entry === "string" && entry.trim()))],
623
628
  authTime: claims.authTime
624
629
  },
625
630
  iat: Math.floor(Date.now() / 1e3),
@@ -208,7 +208,12 @@ class MemoryDeviceCodeAuthProvider {
208
208
  permissions: ["auth:read:self"],
209
209
  metadata: {
210
210
  sessionId: claims.sessionId,
211
- identityId: claims.identityId
211
+ identityId: claims.identityId,
212
+ teamId: claims.teamId ?? null,
213
+ projectId: claims.projectId ?? null,
214
+ membershipId: claims.membershipId ?? null,
215
+ teamRoles: claims.teamRoles ?? [],
216
+ teamCapabilities: claims.teamCapabilities ?? []
212
217
  }
213
218
  };
214
219
  const expiresAt = nowSeconds() + this.config.accessTokenTtlSeconds;
@@ -1,6 +1,6 @@
1
- export declare const CONTENT_RESOURCES: readonly ["pages", "notes", "questions", "objectives", "people", "agents", "books", "templates", "knowledge_packs", "workdays"];
1
+ export declare const CONTENT_RESOURCES: readonly ["pages", "notes", "questions", "objectives", "proposals", "decisions", "people", "agents", "books", "templates", "knowledge_packs", "workdays"];
2
2
  export declare const PLATFORM_RESOURCES: readonly ["users", "roles", "api_tokens", "services", "jobs", "audit", "auth", "sdk", "agent", "operations"];
3
- export declare const ALL_PERMISSION_RESOURCES: readonly ["pages", "notes", "questions", "objectives", "people", "agents", "books", "templates", "knowledge_packs", "workdays", "users", "roles", "api_tokens", "services", "jobs", "audit", "auth", "sdk", "agent", "operations"];
3
+ export declare const ALL_PERMISSION_RESOURCES: readonly ["pages", "notes", "questions", "objectives", "proposals", "decisions", "people", "agents", "books", "templates", "knowledge_packs", "workdays", "users", "roles", "api_tokens", "services", "jobs", "audit", "auth", "sdk", "agent", "operations"];
4
4
  export type PermissionResource = (typeof ALL_PERMISSION_RESOURCES)[number];
5
5
  export type PermissionAction = 'read' | 'create' | 'update' | 'delete' | 'manage' | 'execute' | 'impersonate';
6
6
  export type PermissionScope = 'self' | 'global';
@@ -3,6 +3,8 @@ const CONTENT_RESOURCES = [
3
3
  "notes",
4
4
  "questions",
5
5
  "objectives",
6
+ "proposals",
7
+ "decisions",
6
8
  "people",
7
9
  "agents",
8
10
  "books",
@@ -0,0 +1,9 @@
1
+ import type { ApiPrincipal } from '@treeseed/sdk/remote';
2
+ import { type ApiContext } from './http.ts';
3
+ export declare function principalTeamCapabilities(principal: ApiPrincipal | null): string[];
4
+ export declare function principalTeamRoles(principal: ApiPrincipal | null): string[];
5
+ export declare function hasTeamCapability(principal: ApiPrincipal | null, capability: string): boolean;
6
+ export declare function requireTeamCapability(c: ApiContext, capability: string): Response & import("hono").TypedResponse<{
7
+ ok: false;
8
+ error: string;
9
+ }, never, "json">;
@@ -0,0 +1,33 @@
1
+ import { jsonError } from "./http.js";
2
+ function stringArray(value) {
3
+ return Array.isArray(value) ? [...new Set(value.filter((entry) => typeof entry === "string" && entry.trim().length > 0))] : [];
4
+ }
5
+ function principalTeamCapabilities(principal) {
6
+ return stringArray(principal?.metadata?.teamCapabilities);
7
+ }
8
+ function principalTeamRoles(principal) {
9
+ return stringArray(principal?.metadata?.teamRoles);
10
+ }
11
+ function hasTeamCapability(principal, capability) {
12
+ if (!principal) return false;
13
+ if (principal.permissions.includes("*:*:*") || principal.roles.includes("project_api") || principal.roles.includes("platform_admin")) {
14
+ return true;
15
+ }
16
+ const capabilities = principalTeamCapabilities(principal);
17
+ if (capabilities.includes(capability)) {
18
+ return true;
19
+ }
20
+ return principalTeamRoles(principal).includes("team_owner");
21
+ }
22
+ function requireTeamCapability(c, capability) {
23
+ if (!hasTeamCapability(c.get("principal"), capability)) {
24
+ return jsonError(c, 403, "Permission denied.", { capability });
25
+ }
26
+ return null;
27
+ }
28
+ export {
29
+ hasTeamCapability,
30
+ principalTeamCapabilities,
31
+ principalTeamRoles,
32
+ requireTeamCapability
33
+ };
@@ -1,7 +1,11 @@
1
1
  import type { Hono } from 'hono';
2
+ import type { AgentSdk } from '@treeseed/sdk';
2
3
  import { executeHttpWorkflowOperation } from './operations.ts';
4
+ import type { ApiConfig } from './types.ts';
3
5
  export declare function registerOperationRoutes(app: Hono<any>, options: {
6
+ config: ApiConfig;
4
7
  scope: string;
5
8
  prefix?: string;
9
+ sdk?: AgentSdk;
6
10
  executeOperation?: typeof executeHttpWorkflowOperation;
7
11
  }): void;
@@ -1,5 +1,8 @@
1
- import { findTreeseedOperation } from "@treeseed/sdk";
1
+ import crypto from "node:crypto";
2
+ import { findDispatchCapability, findTreeseedOperation } from "@treeseed/sdk";
2
3
  import { executeHttpWorkflowOperation, isHttpWorkflowOperationAllowed } from "./operations.js";
4
+ import { enqueueTaskFromSdk } from "../services/common.js";
5
+ import { enqueueTaskAndEnsureCapacity } from "../services/worker-capacity.js";
3
6
  import { jsonError, requireScope } from "./http.js";
4
7
  function registerOperationRoutes(app, options) {
5
8
  const executeOperation = options.executeOperation ?? executeHttpWorkflowOperation;
@@ -25,6 +28,51 @@ function registerOperationRoutes(app, options) {
25
28
  }
26
29
  const body = await c.req.json().catch(() => ({}));
27
30
  try {
31
+ const capability = findDispatchCapability("workflow", resolvedOperation.name);
32
+ if (capability?.executionClass === "remote_job" && options.sdk) {
33
+ const input = body && typeof body.input === "object" ? body.input : {};
34
+ const idempotencyKey = typeof body.idempotencyKey === "string" && body.idempotencyKey.trim() ? body.idempotencyKey.trim() : `workflow:${resolvedOperation.name}:${crypto.createHash("sha256").update(JSON.stringify(input)).digest("hex")}`;
35
+ const created = await options.sdk.createTask({
36
+ workDayId: typeof body.workDayId === "string" ? body.workDayId : "",
37
+ agentId: "workflow-dispatch",
38
+ type: "workflow_dispatch",
39
+ priority: typeof body.priority === "number" ? body.priority : 75,
40
+ idempotencyKey,
41
+ payload: {
42
+ executionKind: "workflow_dispatch",
43
+ namespace: "workflow",
44
+ operation: resolvedOperation.name,
45
+ input,
46
+ requestedByType: c.get("actorType"),
47
+ requestedById: c.get("principal")?.id ?? null
48
+ },
49
+ actor: c.get("principal")?.id ?? "api"
50
+ });
51
+ if (!created.payload) {
52
+ return jsonError(c, 500, `Failed to create workflow task for "${resolvedOperation.name}".`, {
53
+ operation: resolvedOperation.name
54
+ });
55
+ }
56
+ const capacity = await enqueueTaskAndEnsureCapacity(options.sdk, {
57
+ taskId: String(created.payload.id ?? ""),
58
+ actor: c.get("principal")?.id ?? "api",
59
+ priorityClass: "interactive",
60
+ projectId: options.config.projectId,
61
+ enqueueTask: enqueueTaskFromSdk
62
+ });
63
+ return c.json({
64
+ ok: true,
65
+ mode: "task",
66
+ operation: resolvedOperation.name,
67
+ payload: created.payload,
68
+ workerState: capacity.workerState,
69
+ capacity: {
70
+ desiredWorkers: capacity.desiredWorkers,
71
+ scaleApplied: capacity.scaleApplied,
72
+ reason: capacity.scaleReason
73
+ }
74
+ }, { status: 202 });
75
+ }
28
76
  const result = await executeOperation(resolvedOperation.name, body);
29
77
  return c.json(result, { status: result.ok ? 200 : 400 });
30
78
  } catch (error) {
@@ -0,0 +1,8 @@
1
+ import type { Hono } from 'hono';
2
+ import type { AgentSdk } from '@treeseed/sdk';
3
+ import type { ApiConfig } from './types.ts';
4
+ export declare function registerProjectRoutes(app: Hono<any>, options: {
5
+ config: ApiConfig;
6
+ sharedSdk: AgentSdk;
7
+ prefix?: string;
8
+ }): void;