@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.
- package/dist/agents/adapters/notification.d.ts +16 -1
- package/dist/agents/adapters/notification.js +31 -1
- package/dist/agents/adapters/research.d.ts +13 -1
- package/dist/agents/adapters/research.js +35 -1
- package/dist/agents/contracts/run.d.ts +1 -0
- package/dist/agents/kernel/agent-kernel.d.ts +2 -2
- package/dist/agents/kernel/agent-kernel.js +10 -3
- package/dist/agents/kernel/trigger-resolver.d.ts +1 -0
- package/dist/agents/kernel/trigger-resolver.js +5 -1
- package/dist/agents/runtime-types.d.ts +1 -0
- package/dist/api/app.js +10 -0
- package/dist/api/auth/d1-store.js +5 -0
- package/dist/api/auth/memory-provider.js +6 -1
- package/dist/api/auth/rbac.d.ts +2 -2
- package/dist/api/auth/rbac.js +2 -0
- package/dist/api/capabilities.d.ts +9 -0
- package/dist/api/capabilities.js +33 -0
- package/dist/api/operations-routes.d.ts +4 -0
- package/dist/api/operations-routes.js +49 -1
- package/dist/api/project-routes.d.ts +8 -0
- package/dist/api/project-routes.js +586 -0
- package/dist/api/types.d.ts +7 -0
- package/dist/components/site/NotesList.astro +13 -2
- package/dist/components/site/PublishedContentBody.astro +5 -0
- package/dist/content.js +77 -9
- package/dist/dev.d.ts +2 -2
- package/dist/dev.js +0 -15
- package/dist/env.yaml +39 -26
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -1
- package/dist/launch.d.ts +3 -0
- package/dist/launch.js +8 -0
- package/dist/layouts/AuthoredEntryLayout.astro +76 -28
- package/dist/layouts/ProfileLayout.astro +9 -5
- package/dist/middleware.js +11 -0
- package/dist/pages/[slug].astro +10 -6
- package/dist/pages/agents/[slug].astro +17 -7
- package/dist/pages/agents/index.astro +2 -1
- package/dist/pages/books/[slug].astro +10 -5
- package/dist/pages/books/index.astro +4 -1
- package/dist/pages/decisions/[slug].astro +73 -0
- package/dist/pages/decisions/index.astro +47 -0
- package/dist/pages/docs-runtime/[...slug].astro +102 -0
- package/dist/pages/docs-runtime/index.astro +89 -0
- package/dist/pages/feed.xml.js +2 -1
- package/dist/pages/index.astro +160 -16
- package/dist/pages/notes/[slug].astro +10 -5
- package/dist/pages/notes/index.astro +6 -3
- package/dist/pages/objectives/[slug].astro +27 -9
- package/dist/pages/objectives/index.astro +19 -2
- package/dist/pages/people/[slug].astro +17 -7
- package/dist/pages/people/index.astro +2 -1
- package/dist/pages/proposals/[slug].astro +72 -0
- package/dist/pages/proposals/index.astro +47 -0
- package/dist/pages/questions/[slug].astro +27 -9
- package/dist/pages/questions/index.astro +19 -2
- package/dist/scripts/dev-platform.js +0 -1
- package/dist/scripts/release-verify.js +29 -2
- package/dist/scripts/tenant-build.js +4 -1
- package/dist/scripts/tenant-check.js +4 -1
- package/dist/services/agents.d.ts +1 -12
- package/dist/services/agents.js +28 -9
- package/dist/services/index.d.ts +0 -2
- package/dist/services/index.js +0 -6
- package/dist/services/manager.d.ts +4 -4
- package/dist/services/manager.js +123 -50
- package/dist/services/workday-report.d.ts +3 -3
- package/dist/services/workday-start.d.ts +3 -3
- package/dist/services/worker-capacity.d.ts +58 -0
- package/dist/services/worker-capacity.js +208 -0
- package/dist/services/worker.js +70 -13
- package/dist/site.js +18 -5
- package/dist/tenant/runtime-config.js +8 -1
- package/dist/utils/hub-content.js +14 -0
- package/dist/utils/published-content.js +13 -0
- package/dist/utils/site-config.js +20 -0
- package/dist/utils/site-content-runtime.js +185 -0
- package/dist/utils/web-cache.js +149 -0
- package/package.json +11 -6
- package/scripts/verify-driver.mjs +34 -0
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
};
|
|
@@ -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:
|
|
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
|
};
|
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;
|
package/dist/api/auth/rbac.d.ts
CHANGED
|
@@ -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';
|
package/dist/api/auth/rbac.js
CHANGED
|
@@ -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
|
|
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;
|