@graphorin/server 0.5.0
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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/app.d.ts +174 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +684 -0
- package/dist/app.js.map +1 -0
- package/dist/commentary/audit-bridge.d.ts +57 -0
- package/dist/commentary/audit-bridge.d.ts.map +1 -0
- package/dist/commentary/audit-bridge.js +92 -0
- package/dist/commentary/audit-bridge.js.map +1 -0
- package/dist/commentary/built-in-patterns.d.ts +24 -0
- package/dist/commentary/built-in-patterns.d.ts.map +1 -0
- package/dist/commentary/built-in-patterns.js +62 -0
- package/dist/commentary/built-in-patterns.js.map +1 -0
- package/dist/commentary/index.d.ts +5 -0
- package/dist/commentary/index.js +5 -0
- package/dist/commentary/sanitizer.d.ts +37 -0
- package/dist/commentary/sanitizer.d.ts.map +1 -0
- package/dist/commentary/sanitizer.js +182 -0
- package/dist/commentary/sanitizer.js.map +1 -0
- package/dist/commentary/types.d.ts +120 -0
- package/dist/commentary/types.d.ts.map +1 -0
- package/dist/config.d.ts +760 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +217 -0
- package/dist/config.js.map +1 -0
- package/dist/consolidator/daemon.d.ts +88 -0
- package/dist/consolidator/daemon.d.ts.map +1 -0
- package/dist/consolidator/daemon.js +54 -0
- package/dist/consolidator/daemon.js.map +1 -0
- package/dist/consolidator/index.d.ts +2 -0
- package/dist/consolidator/index.js +3 -0
- package/dist/errors/index.d.ts +104 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +131 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/health/checks.d.ts +120 -0
- package/dist/health/checks.d.ts.map +1 -0
- package/dist/health/checks.js +127 -0
- package/dist/health/checks.js.map +1 -0
- package/dist/health/index.d.ts +3 -0
- package/dist/health/index.js +4 -0
- package/dist/health/routes.d.ts +66 -0
- package/dist/health/routes.d.ts.map +1 -0
- package/dist/health/routes.js +96 -0
- package/dist/health/routes.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/context.d.ts +66 -0
- package/dist/internal/context.d.ts.map +1 -0
- package/dist/internal/ids.js +21 -0
- package/dist/internal/ids.js.map +1 -0
- package/dist/internal/json.js +46 -0
- package/dist/internal/json.js.map +1 -0
- package/dist/lifecycle/hooks.d.ts +57 -0
- package/dist/lifecycle/hooks.d.ts.map +1 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +3 -0
- package/dist/lifecycle/pre-bind.d.ts +38 -0
- package/dist/lifecycle/pre-bind.d.ts.map +1 -0
- package/dist/lifecycle/pre-bind.js +62 -0
- package/dist/lifecycle/pre-bind.js.map +1 -0
- package/dist/metrics/catalog.d.ts +34 -0
- package/dist/metrics/catalog.d.ts.map +1 -0
- package/dist/metrics/catalog.js +61 -0
- package/dist/metrics/catalog.js.map +1 -0
- package/dist/metrics/index.d.ts +3 -0
- package/dist/metrics/index.js +4 -0
- package/dist/metrics/registry.d.ts +63 -0
- package/dist/metrics/registry.d.ts.map +1 -0
- package/dist/metrics/registry.js +222 -0
- package/dist/metrics/registry.js.map +1 -0
- package/dist/middleware/audit.d.ts +47 -0
- package/dist/middleware/audit.d.ts.map +1 -0
- package/dist/middleware/audit.js +71 -0
- package/dist/middleware/audit.js.map +1 -0
- package/dist/middleware/auth.d.ts +50 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +164 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.d.ts +15 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +45 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/csrf.d.ts +15 -0
- package/dist/middleware/csrf.d.ts.map +1 -0
- package/dist/middleware/csrf.js +77 -0
- package/dist/middleware/csrf.js.map +1 -0
- package/dist/middleware/idempotency.d.ts +41 -0
- package/dist/middleware/idempotency.d.ts.map +1 -0
- package/dist/middleware/idempotency.js +198 -0
- package/dist/middleware/idempotency.js.map +1 -0
- package/dist/middleware/index.d.ts +9 -0
- package/dist/middleware/index.js +10 -0
- package/dist/middleware/rate-limit.d.ts +17 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +47 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/middleware/request-state.d.ts +27 -0
- package/dist/middleware/request-state.d.ts.map +1 -0
- package/dist/middleware/request-state.js +42 -0
- package/dist/middleware/request-state.js.map +1 -0
- package/dist/middleware/scope.d.ts +24 -0
- package/dist/middleware/scope.d.ts.map +1 -0
- package/dist/middleware/scope.js +50 -0
- package/dist/middleware/scope.js.map +1 -0
- package/dist/registry/index.d.ts +135 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +96 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/replay/index.d.ts +2 -0
- package/dist/replay/index.js +3 -0
- package/dist/replay/routes.d.ts +46 -0
- package/dist/replay/routes.d.ts.map +1 -0
- package/dist/replay/routes.js +189 -0
- package/dist/replay/routes.js.map +1 -0
- package/dist/routes/agents.d.ts +41 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +213 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/audit.d.ts +57 -0
- package/dist/routes/audit.d.ts.map +1 -0
- package/dist/routes/audit.js +116 -0
- package/dist/routes/audit.js.map +1 -0
- package/dist/routes/auth.d.ts +26 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +54 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/health.d.ts +22 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +33 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/index.d.ts +10 -0
- package/dist/routes/index.js +12 -0
- package/dist/routes/mcp.d.ts +32 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +61 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/memory.d.ts +141 -0
- package/dist/routes/memory.d.ts.map +1 -0
- package/dist/routes/memory.js +102 -0
- package/dist/routes/memory.js.map +1 -0
- package/dist/routes/sessions.d.ts +54 -0
- package/dist/routes/sessions.d.ts.map +1 -0
- package/dist/routes/sessions.js +135 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/routes/skills.d.ts +34 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +54 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/tokens.d.ts +25 -0
- package/dist/routes/tokens.d.ts.map +1 -0
- package/dist/routes/tokens.js +87 -0
- package/dist/routes/tokens.js.map +1 -0
- package/dist/routes/workflows.d.ts +27 -0
- package/dist/routes/workflows.d.ts.map +1 -0
- package/dist/routes/workflows.js +220 -0
- package/dist/routes/workflows.js.map +1 -0
- package/dist/runtime/run-state.d.ts +158 -0
- package/dist/runtime/run-state.d.ts.map +1 -0
- package/dist/runtime/run-state.js +182 -0
- package/dist/runtime/run-state.js.map +1 -0
- package/dist/sse/events.d.ts +44 -0
- package/dist/sse/events.d.ts.map +1 -0
- package/dist/sse/events.js +200 -0
- package/dist/sse/events.js.map +1 -0
- package/dist/sse/index.d.ts +2 -0
- package/dist/sse/index.js +3 -0
- package/dist/triggers/daemon.d.ts +74 -0
- package/dist/triggers/daemon.d.ts.map +1 -0
- package/dist/triggers/daemon.js +114 -0
- package/dist/triggers/daemon.js.map +1 -0
- package/dist/triggers/index.d.ts +3 -0
- package/dist/triggers/index.js +4 -0
- package/dist/triggers/routes.d.ts +21 -0
- package/dist/triggers/routes.d.ts.map +1 -0
- package/dist/triggers/routes.js +117 -0
- package/dist/triggers/routes.js.map +1 -0
- package/dist/ws/dispatcher.d.ts +169 -0
- package/dist/ws/dispatcher.d.ts.map +1 -0
- package/dist/ws/dispatcher.js +303 -0
- package/dist/ws/dispatcher.js.map +1 -0
- package/dist/ws/index.d.ts +6 -0
- package/dist/ws/index.js +7 -0
- package/dist/ws/replay-buffer.d.ts +47 -0
- package/dist/ws/replay-buffer.d.ts.map +1 -0
- package/dist/ws/replay-buffer.js +88 -0
- package/dist/ws/replay-buffer.js.map +1 -0
- package/dist/ws/subjects.d.ts +71 -0
- package/dist/ws/subjects.d.ts.map +1 -0
- package/dist/ws/subjects.js +112 -0
- package/dist/ws/subjects.js.map +1 -0
- package/dist/ws/ticket.d.ts +74 -0
- package/dist/ws/ticket.d.ts.map +1 -0
- package/dist/ws/ticket.js +112 -0
- package/dist/ws/ticket.js.map +1 -0
- package/dist/ws/upgrade.d.ts +63 -0
- package/dist/ws/upgrade.d.ts.map +1 -0
- package/dist/ws/upgrade.js +324 -0
- package/dist/ws/upgrade.js.map +1 -0
- package/package.json +156 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createScopeMiddleware } from "../middleware/scope.js";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/routes/skills.ts
|
|
6
|
+
const InstallBodySchema = z.object({
|
|
7
|
+
source: z.string().min(1),
|
|
8
|
+
trust: z.enum(["verified", "unverified"]).optional()
|
|
9
|
+
}).strict();
|
|
10
|
+
/**
|
|
11
|
+
* @stable
|
|
12
|
+
*/
|
|
13
|
+
function createSkillsRoutes(deps) {
|
|
14
|
+
const app = new Hono();
|
|
15
|
+
app.get("/", createScopeMiddleware("skills:read"), async (c) => {
|
|
16
|
+
return c.json({ skills: await deps.skills.list() });
|
|
17
|
+
});
|
|
18
|
+
app.get("/:name", createScopeMiddleware("skills:read"), async (c) => {
|
|
19
|
+
const name = c.req.param("name");
|
|
20
|
+
const skill = await deps.skills.get(name);
|
|
21
|
+
if (skill === null) return c.json({
|
|
22
|
+
error: "skill-not-found",
|
|
23
|
+
message: `Skill '${name}' not found.`
|
|
24
|
+
}, 404);
|
|
25
|
+
return c.json({ skill });
|
|
26
|
+
});
|
|
27
|
+
app.post("/install", createScopeMiddleware("skills:install"), async (c) => {
|
|
28
|
+
const parsed = InstallBodySchema.safeParse(await safelyParseJson(c));
|
|
29
|
+
if (!parsed.success) return c.json({
|
|
30
|
+
error: "config-invalid",
|
|
31
|
+
message: "Invalid install body.",
|
|
32
|
+
issues: parsed.error.issues.map((i) => ({
|
|
33
|
+
path: i.path,
|
|
34
|
+
message: i.message
|
|
35
|
+
}))
|
|
36
|
+
}, 400);
|
|
37
|
+
const installInput = { source: parsed.data.source };
|
|
38
|
+
if (parsed.data.trust !== void 0) installInput.trust = parsed.data.trust;
|
|
39
|
+
const out = await deps.skills.install(installInput);
|
|
40
|
+
return c.json({ skill: out }, 201);
|
|
41
|
+
});
|
|
42
|
+
return app;
|
|
43
|
+
}
|
|
44
|
+
async function safelyParseJson(c) {
|
|
45
|
+
try {
|
|
46
|
+
return await c.req.json();
|
|
47
|
+
} catch {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
export { createSkillsRoutes };
|
|
54
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","names":["installInput: { source: string; trust?: 'verified' | 'unverified' }"],"sources":["../../src/routes/skills.ts"],"sourcesContent":["/**\n * Skill REST routes.\n *\n * GET /skills (scope `skills:read`)\n * POST /skills/install (idempotent; scope `skills:install`)\n * GET /skills/:name (scope `skills:read`)\n *\n * @packageDocumentation\n */\n\nimport type { Context } from 'hono';\nimport { Hono } from 'hono';\nimport { z } from 'zod';\n\nimport type { ServerVariables } from '../internal/context.js';\nimport { createScopeMiddleware } from '../middleware/scope.js';\n\n/**\n * @stable\n */\nexport interface SkillsApi {\n list(): Promise<ReadonlyArray<{ readonly name: string; readonly version?: string }>>;\n get(name: string): Promise<unknown | null>;\n install(input: {\n readonly source: string;\n readonly trust?: 'verified' | 'unverified';\n }): Promise<unknown>;\n}\n\nconst InstallBodySchema = z\n .object({\n source: z.string().min(1),\n trust: z.enum(['verified', 'unverified']).optional(),\n })\n .strict();\n\n/**\n * @stable\n */\nexport interface SkillsRoutesDeps {\n readonly skills: SkillsApi;\n}\n\n/**\n * @stable\n */\nexport function createSkillsRoutes(deps: SkillsRoutesDeps): Hono<{ Variables: ServerVariables }> {\n const app = new Hono<{ Variables: ServerVariables }>();\n\n app.get('/', createScopeMiddleware('skills:read'), async (c) => {\n return c.json({ skills: await deps.skills.list() });\n });\n\n app.get('/:name', createScopeMiddleware('skills:read'), async (c) => {\n const name = c.req.param('name');\n const skill = await deps.skills.get(name);\n if (skill === null)\n return c.json({ error: 'skill-not-found', message: `Skill '${name}' not found.` }, 404);\n return c.json({ skill });\n });\n\n app.post('/install', createScopeMiddleware('skills:install'), async (c) => {\n const parsed = InstallBodySchema.safeParse(await safelyParseJson(c));\n if (!parsed.success) {\n return c.json(\n {\n error: 'config-invalid',\n message: 'Invalid install body.',\n issues: parsed.error.issues.map((i) => ({ path: i.path, message: i.message })),\n },\n 400,\n );\n }\n const installInput: { source: string; trust?: 'verified' | 'unverified' } = {\n source: parsed.data.source,\n };\n if (parsed.data.trust !== undefined) installInput.trust = parsed.data.trust;\n const out = await deps.skills.install(installInput);\n return c.json({ skill: out }, 201);\n });\n\n return app;\n}\n\nasync function safelyParseJson(c: Context<{ Variables: ServerVariables }>): Promise<unknown> {\n try {\n return await c.req.json();\n } catch {\n return {};\n }\n}\n"],"mappings":";;;;;AA6BA,MAAM,oBAAoB,EACvB,OAAO;CACN,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE;CACzB,OAAO,EAAE,KAAK,CAAC,YAAY,aAAa,CAAC,CAAC,UAAU;CACrD,CAAC,CACD,QAAQ;;;;AAYX,SAAgB,mBAAmB,MAA8D;CAC/F,MAAM,MAAM,IAAI,MAAsC;AAEtD,KAAI,IAAI,KAAK,sBAAsB,cAAc,EAAE,OAAO,MAAM;AAC9D,SAAO,EAAE,KAAK,EAAE,QAAQ,MAAM,KAAK,OAAO,MAAM,EAAE,CAAC;GACnD;AAEF,KAAI,IAAI,UAAU,sBAAsB,cAAc,EAAE,OAAO,MAAM;EACnE,MAAM,OAAO,EAAE,IAAI,MAAM,OAAO;EAChC,MAAM,QAAQ,MAAM,KAAK,OAAO,IAAI,KAAK;AACzC,MAAI,UAAU,KACZ,QAAO,EAAE,KAAK;GAAE,OAAO;GAAmB,SAAS,UAAU,KAAK;GAAe,EAAE,IAAI;AACzF,SAAO,EAAE,KAAK,EAAE,OAAO,CAAC;GACxB;AAEF,KAAI,KAAK,YAAY,sBAAsB,iBAAiB,EAAE,OAAO,MAAM;EACzE,MAAM,SAAS,kBAAkB,UAAU,MAAM,gBAAgB,EAAE,CAAC;AACpE,MAAI,CAAC,OAAO,QACV,QAAO,EAAE,KACP;GACE,OAAO;GACP,SAAS;GACT,QAAQ,OAAO,MAAM,OAAO,KAAK,OAAO;IAAE,MAAM,EAAE;IAAM,SAAS,EAAE;IAAS,EAAE;GAC/E,EACD,IACD;EAEH,MAAMA,eAAsE,EAC1E,QAAQ,OAAO,KAAK,QACrB;AACD,MAAI,OAAO,KAAK,UAAU,OAAW,cAAa,QAAQ,OAAO,KAAK;EACtE,MAAM,MAAM,MAAM,KAAK,OAAO,QAAQ,aAAa;AACnD,SAAO,EAAE,KAAK,EAAE,OAAO,KAAK,EAAE,IAAI;GAClC;AAEF,QAAO;;AAGT,eAAe,gBAAgB,GAA8D;AAC3F,KAAI;AACF,SAAO,MAAM,EAAE,IAAI,MAAM;SACnB;AACN,SAAO,EAAE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ServerVariables } from "../internal/context.js";
|
|
2
|
+
import { SecretValue } from "@graphorin/security";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { AuthTokenStore } from "@graphorin/core/contracts";
|
|
5
|
+
|
|
6
|
+
//#region src/routes/tokens.d.ts
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @stable
|
|
10
|
+
*/
|
|
11
|
+
interface TokensRoutesDeps {
|
|
12
|
+
readonly tokenStore: AuthTokenStore;
|
|
13
|
+
readonly pepper: SecretValue;
|
|
14
|
+
readonly defaultEnv: string;
|
|
15
|
+
readonly allowedEnvs: ReadonlyArray<string>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* @stable
|
|
19
|
+
*/
|
|
20
|
+
declare function createTokensRoutes(deps: TokensRoutesDeps): Hono<{
|
|
21
|
+
Variables: ServerVariables;
|
|
22
|
+
}>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { TokensRoutesDeps, createTokensRoutes };
|
|
25
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","names":[],"sources":["../../src/routes/tokens.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAwCgE,UAnB/C,gBAAA,CAmB+C;uBAlBzC;mBACJ;;wBAEK;;;;;iBAeR,kBAAA,OAAyB,mBAAmB;aAAkB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createScopeMiddleware } from "../middleware/scope.js";
|
|
2
|
+
import { createToken, listTokens, revokeToken } from "@graphorin/security";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
//#region src/routes/tokens.ts
|
|
7
|
+
const CreateBodySchema = z.object({
|
|
8
|
+
label: z.string().min(1).optional(),
|
|
9
|
+
scopes: z.array(z.string().min(3)).min(1),
|
|
10
|
+
env: z.string().min(1).optional(),
|
|
11
|
+
expiresInMs: z.number().int().positive().optional()
|
|
12
|
+
}).strict();
|
|
13
|
+
/**
|
|
14
|
+
* @stable
|
|
15
|
+
*/
|
|
16
|
+
function createTokensRoutes(deps) {
|
|
17
|
+
const app = new Hono();
|
|
18
|
+
app.get("/", createScopeMiddleware("tokens:list"), async (c) => {
|
|
19
|
+
const sanitized = (await listTokens(deps.tokenStore)).map((r) => ({
|
|
20
|
+
id: r.id,
|
|
21
|
+
label: r.label,
|
|
22
|
+
scopes: r.scopes,
|
|
23
|
+
createdAt: r.createdAt,
|
|
24
|
+
lastUsedAt: r.lastUsedAt,
|
|
25
|
+
expiresAt: r.expiresAt,
|
|
26
|
+
revokedAt: r.revokedAt
|
|
27
|
+
}));
|
|
28
|
+
return c.json({ tokens: sanitized });
|
|
29
|
+
});
|
|
30
|
+
app.post("/", createScopeMiddleware("tokens:create"), async (c) => {
|
|
31
|
+
const parsed = CreateBodySchema.safeParse(await safelyParseJson(c));
|
|
32
|
+
if (!parsed.success) return c.json({
|
|
33
|
+
error: "config-invalid",
|
|
34
|
+
message: "Invalid create-token body.",
|
|
35
|
+
issues: parsed.error.issues.map((i) => ({
|
|
36
|
+
path: i.path,
|
|
37
|
+
message: i.message
|
|
38
|
+
}))
|
|
39
|
+
}, 400);
|
|
40
|
+
const env = parsed.data.env ?? deps.defaultEnv;
|
|
41
|
+
if (!deps.allowedEnvs.includes(env)) return c.json({
|
|
42
|
+
error: "config-invalid",
|
|
43
|
+
message: `Environment '${env}' is not in the allowed set.`,
|
|
44
|
+
allowed: deps.allowedEnvs
|
|
45
|
+
}, 400);
|
|
46
|
+
const created = await createToken({
|
|
47
|
+
tokenStore: deps.tokenStore,
|
|
48
|
+
pepper: deps.pepper,
|
|
49
|
+
env,
|
|
50
|
+
scopes: parsed.data.scopes,
|
|
51
|
+
...parsed.data.label !== void 0 ? { label: parsed.data.label } : {},
|
|
52
|
+
...parsed.data.expiresInMs !== void 0 ? { expiresInMs: parsed.data.expiresInMs } : {}
|
|
53
|
+
});
|
|
54
|
+
const raw = await created.raw.use((value) => value);
|
|
55
|
+
return c.json({
|
|
56
|
+
token: {
|
|
57
|
+
id: created.record.id,
|
|
58
|
+
label: created.record.label,
|
|
59
|
+
scopes: created.record.scopes,
|
|
60
|
+
createdAt: created.record.createdAt,
|
|
61
|
+
expiresAt: created.record.expiresAt
|
|
62
|
+
},
|
|
63
|
+
raw,
|
|
64
|
+
warning: "Store this raw value securely; it is shown exactly once."
|
|
65
|
+
}, 201);
|
|
66
|
+
});
|
|
67
|
+
app.delete("/:id", createScopeMiddleware("tokens:revoke"), async (c) => {
|
|
68
|
+
const id = c.req.param("id");
|
|
69
|
+
if (await revokeToken(deps.tokenStore, id) === void 0) return c.json({
|
|
70
|
+
error: "token-not-found",
|
|
71
|
+
message: `Token '${id}' not found.`
|
|
72
|
+
}, 404);
|
|
73
|
+
return c.body(null, 204);
|
|
74
|
+
});
|
|
75
|
+
return app;
|
|
76
|
+
}
|
|
77
|
+
async function safelyParseJson(c) {
|
|
78
|
+
try {
|
|
79
|
+
return await c.req.json();
|
|
80
|
+
} catch {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
export { createTokensRoutes };
|
|
87
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","names":[],"sources":["../../src/routes/tokens.ts"],"sourcesContent":["/**\n * Token-management REST routes. The handlers wrap the\n * `@graphorin/security/auth` CRUD library functions so the operator\n * can mint / list / revoke tokens through the same API surface as\n * `graphorin token` (Phase 15).\n *\n * @packageDocumentation\n */\n\nimport type { AuthTokenStore } from '@graphorin/core/contracts';\nimport { createToken, listTokens, revokeToken, type SecretValue } from '@graphorin/security';\nimport type { Context } from 'hono';\nimport { Hono } from 'hono';\nimport { z } from 'zod';\n\nimport type { ServerVariables } from '../internal/context.js';\nimport { createScopeMiddleware } from '../middleware/scope.js';\n\n/**\n * @stable\n */\nexport interface TokensRoutesDeps {\n readonly tokenStore: AuthTokenStore;\n readonly pepper: SecretValue;\n readonly defaultEnv: string;\n readonly allowedEnvs: ReadonlyArray<string>;\n}\n\nconst CreateBodySchema = z\n .object({\n label: z.string().min(1).optional(),\n scopes: z.array(z.string().min(3)).min(1),\n env: z.string().min(1).optional(),\n expiresInMs: z.number().int().positive().optional(),\n })\n .strict();\n\n/**\n * @stable\n */\nexport function createTokensRoutes(deps: TokensRoutesDeps): Hono<{ Variables: ServerVariables }> {\n const app = new Hono<{ Variables: ServerVariables }>();\n\n app.get('/', createScopeMiddleware('tokens:list'), async (c) => {\n const records = await listTokens(deps.tokenStore);\n // Never reveal hashes or pepper-derived material.\n const sanitized = records.map((r) => ({\n id: r.id,\n label: r.label,\n scopes: r.scopes,\n createdAt: r.createdAt,\n lastUsedAt: r.lastUsedAt,\n expiresAt: r.expiresAt,\n revokedAt: r.revokedAt,\n }));\n return c.json({ tokens: sanitized });\n });\n\n app.post('/', createScopeMiddleware('tokens:create'), async (c) => {\n const parsed = CreateBodySchema.safeParse(await safelyParseJson(c));\n if (!parsed.success) {\n return c.json(\n {\n error: 'config-invalid',\n message: 'Invalid create-token body.',\n issues: parsed.error.issues.map((i) => ({ path: i.path, message: i.message })),\n },\n 400,\n );\n }\n const env = parsed.data.env ?? deps.defaultEnv;\n if (!deps.allowedEnvs.includes(env)) {\n return c.json(\n {\n error: 'config-invalid',\n message: `Environment '${env}' is not in the allowed set.`,\n allowed: deps.allowedEnvs,\n },\n 400,\n );\n }\n const created = await createToken({\n tokenStore: deps.tokenStore,\n pepper: deps.pepper,\n env,\n scopes: parsed.data.scopes,\n ...(parsed.data.label !== undefined ? { label: parsed.data.label } : {}),\n ...(parsed.data.expiresInMs !== undefined ? { expiresInMs: parsed.data.expiresInMs } : {}),\n });\n // Reveal the raw token exactly once — at creation.\n const raw = await created.raw.use((value) => value);\n return c.json(\n {\n token: {\n id: created.record.id,\n label: created.record.label,\n scopes: created.record.scopes,\n createdAt: created.record.createdAt,\n expiresAt: created.record.expiresAt,\n },\n raw,\n warning: 'Store this raw value securely; it is shown exactly once.',\n },\n 201,\n );\n });\n\n app.delete('/:id', createScopeMiddleware('tokens:revoke'), async (c) => {\n const id = c.req.param('id');\n const updated = await revokeToken(deps.tokenStore, id);\n if (updated === undefined) {\n return c.json({ error: 'token-not-found', message: `Token '${id}' not found.` }, 404);\n }\n return c.body(null, 204);\n });\n\n return app;\n}\n\nasync function safelyParseJson(c: Context<{ Variables: ServerVariables }>): Promise<unknown> {\n try {\n return await c.req.json();\n } catch {\n return {};\n }\n}\n"],"mappings":";;;;;;AA4BA,MAAM,mBAAmB,EACtB,OAAO;CACN,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU;CACnC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;CACzC,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU;CACjC,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU;CACpD,CAAC,CACD,QAAQ;;;;AAKX,SAAgB,mBAAmB,MAA8D;CAC/F,MAAM,MAAM,IAAI,MAAsC;AAEtD,KAAI,IAAI,KAAK,sBAAsB,cAAc,EAAE,OAAO,MAAM;EAG9D,MAAM,aAFU,MAAM,WAAW,KAAK,WAAW,EAEvB,KAAK,OAAO;GACpC,IAAI,EAAE;GACN,OAAO,EAAE;GACT,QAAQ,EAAE;GACV,WAAW,EAAE;GACb,YAAY,EAAE;GACd,WAAW,EAAE;GACb,WAAW,EAAE;GACd,EAAE;AACH,SAAO,EAAE,KAAK,EAAE,QAAQ,WAAW,CAAC;GACpC;AAEF,KAAI,KAAK,KAAK,sBAAsB,gBAAgB,EAAE,OAAO,MAAM;EACjE,MAAM,SAAS,iBAAiB,UAAU,MAAM,gBAAgB,EAAE,CAAC;AACnE,MAAI,CAAC,OAAO,QACV,QAAO,EAAE,KACP;GACE,OAAO;GACP,SAAS;GACT,QAAQ,OAAO,MAAM,OAAO,KAAK,OAAO;IAAE,MAAM,EAAE;IAAM,SAAS,EAAE;IAAS,EAAE;GAC/E,EACD,IACD;EAEH,MAAM,MAAM,OAAO,KAAK,OAAO,KAAK;AACpC,MAAI,CAAC,KAAK,YAAY,SAAS,IAAI,CACjC,QAAO,EAAE,KACP;GACE,OAAO;GACP,SAAS,gBAAgB,IAAI;GAC7B,SAAS,KAAK;GACf,EACD,IACD;EAEH,MAAM,UAAU,MAAM,YAAY;GAChC,YAAY,KAAK;GACjB,QAAQ,KAAK;GACb;GACA,QAAQ,OAAO,KAAK;GACpB,GAAI,OAAO,KAAK,UAAU,SAAY,EAAE,OAAO,OAAO,KAAK,OAAO,GAAG,EAAE;GACvE,GAAI,OAAO,KAAK,gBAAgB,SAAY,EAAE,aAAa,OAAO,KAAK,aAAa,GAAG,EAAE;GAC1F,CAAC;EAEF,MAAM,MAAM,MAAM,QAAQ,IAAI,KAAK,UAAU,MAAM;AACnD,SAAO,EAAE,KACP;GACE,OAAO;IACL,IAAI,QAAQ,OAAO;IACnB,OAAO,QAAQ,OAAO;IACtB,QAAQ,QAAQ,OAAO;IACvB,WAAW,QAAQ,OAAO;IAC1B,WAAW,QAAQ,OAAO;IAC3B;GACD;GACA,SAAS;GACV,EACD,IACD;GACD;AAEF,KAAI,OAAO,QAAQ,sBAAsB,gBAAgB,EAAE,OAAO,MAAM;EACtE,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK;AAE5B,MADgB,MAAM,YAAY,KAAK,YAAY,GAAG,KACtC,OACd,QAAO,EAAE,KAAK;GAAE,OAAO;GAAmB,SAAS,UAAU,GAAG;GAAe,EAAE,IAAI;AAEvF,SAAO,EAAE,KAAK,MAAM,IAAI;GACxB;AAEF,QAAO;;AAGT,eAAe,gBAAgB,GAA8D;AAC3F,KAAI;AACF,SAAO,MAAM,EAAE,IAAI,MAAM;SACnB;AACN,SAAO,EAAE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ServerVariables } from "../internal/context.js";
|
|
2
|
+
import { WorkflowRegistry } from "../registry/index.js";
|
|
3
|
+
import { WsDispatcher } from "../ws/dispatcher.js";
|
|
4
|
+
import { RunStateTracker } from "../runtime/run-state.js";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
|
|
7
|
+
//#region src/routes/workflows.d.ts
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
interface WorkflowRoutesDeps {
|
|
13
|
+
readonly workflows: WorkflowRegistry;
|
|
14
|
+
readonly runs: RunStateTracker;
|
|
15
|
+
readonly newRunId?: () => string;
|
|
16
|
+
/** Streaming dispatcher (IP-2): workflow events reach the run subject. */
|
|
17
|
+
readonly dispatcher?: WsDispatcher;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* @stable
|
|
21
|
+
*/
|
|
22
|
+
declare function createWorkflowRoutes(deps: WorkflowRoutesDeps): Hono<{
|
|
23
|
+
Variables: ServerVariables;
|
|
24
|
+
}>;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { WorkflowRoutesDeps, createWorkflowRoutes };
|
|
27
|
+
//# sourceMappingURL=workflows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflows.d.ts","names":[],"sources":["../../src/routes/workflows.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;UA0BiB,kBAAA;sBACK;iBACL;;;wBAAe;;;;;iBAiChB,oBAAA,OACR,qBACL;aAAkB"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { WorkflowNotFoundError } from "../errors/index.js";
|
|
2
|
+
import { createScopeMiddleware } from "../middleware/scope.js";
|
|
3
|
+
import { newRequestId } from "../internal/ids.js";
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
//#region src/routes/workflows.ts
|
|
8
|
+
const ExecuteBodySchema = z.object({
|
|
9
|
+
input: z.unknown().optional(),
|
|
10
|
+
threadId: z.string().min(1).optional(),
|
|
11
|
+
sessionId: z.string().min(1).optional(),
|
|
12
|
+
userId: z.string().min(1).optional()
|
|
13
|
+
}).strict().default({});
|
|
14
|
+
const ResumeBodySchema = z.object({
|
|
15
|
+
threadId: z.string().min(1),
|
|
16
|
+
resume: z.unknown().optional()
|
|
17
|
+
}).strict();
|
|
18
|
+
const ForkBodySchema = z.object({
|
|
19
|
+
fromThreadId: z.string().min(1),
|
|
20
|
+
fromCheckpointId: z.string().min(1).optional()
|
|
21
|
+
}).strict();
|
|
22
|
+
/**
|
|
23
|
+
* @stable
|
|
24
|
+
*/
|
|
25
|
+
function createWorkflowRoutes(deps) {
|
|
26
|
+
const app = new Hono();
|
|
27
|
+
const newRunId = deps.newRunId ?? newRequestId;
|
|
28
|
+
app.get("/", createScopeMiddleware("workflows:read"), (c) => c.json({ workflows: deps.workflows.list() }));
|
|
29
|
+
app.post("/:id/execute", createScopeMiddleware((_path, params) => `workflows:execute:${params.id}`), async (c) => {
|
|
30
|
+
const id = c.req.param("id");
|
|
31
|
+
const workflow = deps.workflows.get(id);
|
|
32
|
+
if (workflow === void 0) {
|
|
33
|
+
const err = new WorkflowNotFoundError(id);
|
|
34
|
+
return c.json({
|
|
35
|
+
error: err.kind,
|
|
36
|
+
message: err.message
|
|
37
|
+
}, 404);
|
|
38
|
+
}
|
|
39
|
+
const parsed = ExecuteBodySchema.safeParse(await safelyParseJson(c));
|
|
40
|
+
if (!parsed.success) return c.json({
|
|
41
|
+
error: "config-invalid",
|
|
42
|
+
message: "Invalid execute body.",
|
|
43
|
+
issues: parsed.error.issues.map((i) => ({
|
|
44
|
+
path: i.path,
|
|
45
|
+
message: i.message
|
|
46
|
+
}))
|
|
47
|
+
}, 400);
|
|
48
|
+
const runId = newRunId();
|
|
49
|
+
const tracker = deps.runs.start(runId, {
|
|
50
|
+
kind: "workflow",
|
|
51
|
+
workflowId: id,
|
|
52
|
+
...parsed.data.threadId !== void 0 ? { threadId: parsed.data.threadId } : {},
|
|
53
|
+
...parsed.data.sessionId !== void 0 ? { sessionId: parsed.data.sessionId } : {},
|
|
54
|
+
...parsed.data.userId !== void 0 ? { userId: parsed.data.userId } : {}
|
|
55
|
+
});
|
|
56
|
+
const subject = `workflow:${id}/runs/${runId}/events`;
|
|
57
|
+
backgroundExecute(workflow, parsed.data, tracker, deps.runs, runId, {
|
|
58
|
+
subject,
|
|
59
|
+
...deps.dispatcher !== void 0 ? { dispatcher: deps.dispatcher } : {}
|
|
60
|
+
});
|
|
61
|
+
return c.json({
|
|
62
|
+
runId,
|
|
63
|
+
status: "running",
|
|
64
|
+
subscribe: { websocket: subject }
|
|
65
|
+
}, 202);
|
|
66
|
+
});
|
|
67
|
+
app.post("/:id/resume", createScopeMiddleware((_path, params) => `workflows:resume:${params.id}`), async (c) => {
|
|
68
|
+
const id = c.req.param("id");
|
|
69
|
+
const workflow = deps.workflows.get(id);
|
|
70
|
+
if (workflow === void 0) {
|
|
71
|
+
const err = new WorkflowNotFoundError(id);
|
|
72
|
+
return c.json({
|
|
73
|
+
error: err.kind,
|
|
74
|
+
message: err.message
|
|
75
|
+
}, 404);
|
|
76
|
+
}
|
|
77
|
+
const parsed = ResumeBodySchema.safeParse(await safelyParseJson(c));
|
|
78
|
+
if (!parsed.success) return c.json({
|
|
79
|
+
error: "config-invalid",
|
|
80
|
+
message: "Invalid resume body.",
|
|
81
|
+
issues: parsed.error.issues.map((i) => ({
|
|
82
|
+
path: i.path,
|
|
83
|
+
message: i.message
|
|
84
|
+
}))
|
|
85
|
+
}, 400);
|
|
86
|
+
if (workflow.resume === void 0) return c.json({
|
|
87
|
+
error: "workflow-resume-unsupported",
|
|
88
|
+
message: `Workflow '${id}' does not implement resume().`
|
|
89
|
+
}, 400);
|
|
90
|
+
const runId = newRunId();
|
|
91
|
+
deps.runs.declare(runId, {
|
|
92
|
+
kind: "workflow",
|
|
93
|
+
workflowId: id,
|
|
94
|
+
threadId: parsed.data.threadId
|
|
95
|
+
});
|
|
96
|
+
return c.json({
|
|
97
|
+
runId,
|
|
98
|
+
threadId: parsed.data.threadId,
|
|
99
|
+
status: "pending",
|
|
100
|
+
subscribe: {
|
|
101
|
+
websocket: `workflow:${id}/runs/${runId}/events`,
|
|
102
|
+
sse: `/v1/runs/${runId}/events`
|
|
103
|
+
}
|
|
104
|
+
}, 202);
|
|
105
|
+
});
|
|
106
|
+
app.get("/:id/state", createScopeMiddleware((_path, params) => `workflows:read:${params.id}`), async (c) => {
|
|
107
|
+
const id = c.req.param("id");
|
|
108
|
+
const workflow = deps.workflows.get(id);
|
|
109
|
+
if (workflow === void 0) {
|
|
110
|
+
const err = new WorkflowNotFoundError(id);
|
|
111
|
+
return c.json({
|
|
112
|
+
error: err.kind,
|
|
113
|
+
message: err.message
|
|
114
|
+
}, 404);
|
|
115
|
+
}
|
|
116
|
+
const threadId = c.req.query("threadId");
|
|
117
|
+
if (threadId === void 0 || threadId.length === 0) return c.json({
|
|
118
|
+
error: "config-invalid",
|
|
119
|
+
message: "Query parameter `threadId` is required."
|
|
120
|
+
}, 400);
|
|
121
|
+
if (workflow.getState === void 0) return c.json({
|
|
122
|
+
error: "workflow-state-unsupported",
|
|
123
|
+
message: "Workflow does not implement getState()."
|
|
124
|
+
}, 400);
|
|
125
|
+
const state = await workflow.getState(threadId);
|
|
126
|
+
return c.json({
|
|
127
|
+
workflowId: id,
|
|
128
|
+
threadId,
|
|
129
|
+
state
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
app.get("/:id/checkpoints", createScopeMiddleware((_path, params) => `workflows:read:${params.id}`), async (c) => {
|
|
133
|
+
const id = c.req.param("id");
|
|
134
|
+
const workflow = deps.workflows.get(id);
|
|
135
|
+
if (workflow === void 0) {
|
|
136
|
+
const err = new WorkflowNotFoundError(id);
|
|
137
|
+
return c.json({
|
|
138
|
+
error: err.kind,
|
|
139
|
+
message: err.message
|
|
140
|
+
}, 404);
|
|
141
|
+
}
|
|
142
|
+
const threadId = c.req.query("threadId");
|
|
143
|
+
if (threadId === void 0 || threadId.length === 0) return c.json({
|
|
144
|
+
error: "config-invalid",
|
|
145
|
+
message: "Query parameter `threadId` is required."
|
|
146
|
+
}, 400);
|
|
147
|
+
if (workflow.listCheckpoints === void 0) return c.json({
|
|
148
|
+
error: "workflow-checkpoints-unsupported",
|
|
149
|
+
message: "Workflow does not implement listCheckpoints()."
|
|
150
|
+
}, 400);
|
|
151
|
+
const checkpoints = await workflow.listCheckpoints(threadId);
|
|
152
|
+
return c.json({
|
|
153
|
+
workflowId: id,
|
|
154
|
+
threadId,
|
|
155
|
+
checkpoints
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
app.post("/:id/fork", createScopeMiddleware((_path, params) => `workflows:execute:${params.id}`), async (c) => {
|
|
159
|
+
const id = c.req.param("id");
|
|
160
|
+
if (deps.workflows.get(id) === void 0) {
|
|
161
|
+
const err = new WorkflowNotFoundError(id);
|
|
162
|
+
return c.json({
|
|
163
|
+
error: err.kind,
|
|
164
|
+
message: err.message
|
|
165
|
+
}, 404);
|
|
166
|
+
}
|
|
167
|
+
const parsed = ForkBodySchema.safeParse(await safelyParseJson(c));
|
|
168
|
+
if (!parsed.success) return c.json({
|
|
169
|
+
error: "config-invalid",
|
|
170
|
+
message: "Invalid fork body.",
|
|
171
|
+
issues: parsed.error.issues.map((i) => ({
|
|
172
|
+
path: i.path,
|
|
173
|
+
message: i.message
|
|
174
|
+
}))
|
|
175
|
+
}, 400);
|
|
176
|
+
return c.json({
|
|
177
|
+
workflowId: id,
|
|
178
|
+
status: "pending",
|
|
179
|
+
fork: parsed.data
|
|
180
|
+
}, 202);
|
|
181
|
+
});
|
|
182
|
+
return app;
|
|
183
|
+
}
|
|
184
|
+
function backgroundExecute(workflow, body, tracker, runs, runId, streaming) {
|
|
185
|
+
const opts = { signal: tracker.signal };
|
|
186
|
+
if (body.threadId !== void 0) opts.threadId = body.threadId;
|
|
187
|
+
(async () => {
|
|
188
|
+
try {
|
|
189
|
+
for await (const ev of workflow.execute(body.input ?? {}, opts)) {
|
|
190
|
+
if (tracker.signal.aborted) break;
|
|
191
|
+
const type = typeof ev.type === "string" ? ev.type : "workflow.event";
|
|
192
|
+
streaming.dispatcher?.emit(streaming.subject, {
|
|
193
|
+
type,
|
|
194
|
+
payload: ev
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
runs.complete(runId, tracker.signal.aborted ? "aborted" : "completed");
|
|
198
|
+
} catch (err) {
|
|
199
|
+
streaming.dispatcher?.emit(streaming.subject, {
|
|
200
|
+
type: "workflow.error",
|
|
201
|
+
payload: {
|
|
202
|
+
runId,
|
|
203
|
+
message: err instanceof Error ? err.message : String(err)
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
runs.complete(runId, tracker.signal.aborted ? "aborted" : "failed", err);
|
|
207
|
+
}
|
|
208
|
+
})();
|
|
209
|
+
}
|
|
210
|
+
async function safelyParseJson(c) {
|
|
211
|
+
try {
|
|
212
|
+
return await c.req.json();
|
|
213
|
+
} catch {
|
|
214
|
+
return {};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
//#endregion
|
|
219
|
+
export { createWorkflowRoutes };
|
|
220
|
+
//# sourceMappingURL=workflows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflows.js","names":["opts: { signal?: AbortSignal; threadId?: string }"],"sources":["../../src/routes/workflows.ts"],"sourcesContent":["/**\n * Workflow REST routes.\n *\n * POST /workflows/:id/execute (idempotent; scope `workflows:execute`)\n * POST /workflows/:id/resume (scope `workflows:resume[:id]`)\n * GET /workflows/:id/state (scope `workflows:read`)\n * GET /workflows/:id/checkpoints (scope `workflows:read`)\n * POST /workflows/:id/fork (scope `workflows:execute`)\n *\n * @packageDocumentation\n */\n\nimport type { Context } from 'hono';\nimport { Hono } from 'hono';\nimport { z } from 'zod';\n\nimport { WorkflowNotFoundError } from '../errors/index.js';\nimport type { ServerVariables } from '../internal/context.js';\nimport { newRequestId } from '../internal/ids.js';\nimport { createScopeMiddleware } from '../middleware/scope.js';\nimport type { WorkflowRegistry } from '../registry/index.js';\nimport type { RunStateTracker } from '../runtime/run-state.js';\n\n/**\n * @stable\n */\nexport interface WorkflowRoutesDeps {\n readonly workflows: WorkflowRegistry;\n readonly runs: RunStateTracker;\n readonly newRunId?: () => string;\n /** Streaming dispatcher (IP-2): workflow events reach the run subject. */\n readonly dispatcher?: import('../ws/dispatcher.js').WsDispatcher;\n}\n\nconst ExecuteBodySchema = z\n .object({\n input: z.unknown().optional(),\n threadId: z.string().min(1).optional(),\n sessionId: z.string().min(1).optional(),\n userId: z.string().min(1).optional(),\n })\n .strict()\n .default({});\n\nconst ResumeBodySchema = z\n .object({\n threadId: z.string().min(1),\n resume: z.unknown().optional(),\n })\n .strict();\n\nconst ForkBodySchema = z\n .object({\n fromThreadId: z.string().min(1),\n fromCheckpointId: z.string().min(1).optional(),\n })\n .strict();\n\n/**\n * @stable\n */\nexport function createWorkflowRoutes(\n deps: WorkflowRoutesDeps,\n): Hono<{ Variables: ServerVariables }> {\n const app = new Hono<{ Variables: ServerVariables }>();\n const newRunId = deps.newRunId ?? newRequestId;\n\n app.get('/', createScopeMiddleware('workflows:read'), (c) =>\n c.json({ workflows: deps.workflows.list() }),\n );\n\n app.post(\n '/:id/execute',\n createScopeMiddleware((_path, params) => `workflows:execute:${params.id}`),\n async (c) => {\n const id = c.req.param('id');\n const workflow = deps.workflows.get(id);\n if (workflow === undefined) {\n const err = new WorkflowNotFoundError(id);\n return c.json({ error: err.kind, message: err.message }, 404);\n }\n const parsed = ExecuteBodySchema.safeParse(await safelyParseJson(c));\n if (!parsed.success) {\n return c.json(\n {\n error: 'config-invalid',\n message: 'Invalid execute body.',\n issues: parsed.error.issues.map((i) => ({ path: i.path, message: i.message })),\n },\n 400,\n );\n }\n const runId = newRunId();\n const tracker = deps.runs.start(runId, {\n kind: 'workflow',\n workflowId: id,\n ...(parsed.data.threadId !== undefined ? { threadId: parsed.data.threadId } : {}),\n ...(parsed.data.sessionId !== undefined ? { sessionId: parsed.data.sessionId } : {}),\n ...(parsed.data.userId !== undefined ? { userId: parsed.data.userId } : {}),\n });\n // Workflows are streaming-first; the REST endpoint kicks off the\n // run in the background and returns 202 + runId. The actual\n // event stream is consumed via WebSocket / SSE in Phase 14b.\n const subject = `workflow:${id}/runs/${runId}/events`;\n backgroundExecute(workflow, parsed.data, tracker, deps.runs, runId, {\n subject,\n ...(deps.dispatcher !== undefined ? { dispatcher: deps.dispatcher } : {}),\n });\n return c.json(\n {\n runId,\n status: 'running',\n subscribe: {\n // IP-2: the previously-advertised SSE URL was never\n // mounted; the WS subject (now in the grammar) delivers.\n websocket: subject,\n },\n },\n 202,\n );\n },\n );\n\n app.post(\n '/:id/resume',\n createScopeMiddleware((_path, params) => `workflows:resume:${params.id}`),\n async (c) => {\n const id = c.req.param('id');\n const workflow = deps.workflows.get(id);\n if (workflow === undefined) {\n const err = new WorkflowNotFoundError(id);\n return c.json({ error: err.kind, message: err.message }, 404);\n }\n const parsed = ResumeBodySchema.safeParse(await safelyParseJson(c));\n if (!parsed.success) {\n return c.json(\n {\n error: 'config-invalid',\n message: 'Invalid resume body.',\n issues: parsed.error.issues.map((i) => ({ path: i.path, message: i.message })),\n },\n 400,\n );\n }\n if (workflow.resume === undefined) {\n return c.json(\n {\n error: 'workflow-resume-unsupported',\n message: `Workflow '${id}' does not implement resume().`,\n },\n 400,\n );\n }\n const runId = newRunId();\n deps.runs.declare(runId, {\n kind: 'workflow',\n workflowId: id,\n threadId: parsed.data.threadId,\n });\n return c.json(\n {\n runId,\n threadId: parsed.data.threadId,\n status: 'pending',\n subscribe: {\n websocket: `workflow:${id}/runs/${runId}/events`,\n sse: `/v1/runs/${runId}/events`,\n },\n },\n 202,\n );\n },\n );\n\n app.get(\n '/:id/state',\n createScopeMiddleware((_path, params) => `workflows:read:${params.id}`),\n async (c) => {\n const id = c.req.param('id');\n const workflow = deps.workflows.get(id);\n if (workflow === undefined) {\n const err = new WorkflowNotFoundError(id);\n return c.json({ error: err.kind, message: err.message }, 404);\n }\n const threadId = c.req.query('threadId');\n if (threadId === undefined || threadId.length === 0) {\n return c.json(\n { error: 'config-invalid', message: 'Query parameter `threadId` is required.' },\n 400,\n );\n }\n if (workflow.getState === undefined) {\n return c.json(\n {\n error: 'workflow-state-unsupported',\n message: 'Workflow does not implement getState().',\n },\n 400,\n );\n }\n const state = await workflow.getState(threadId);\n return c.json({ workflowId: id, threadId, state });\n },\n );\n\n app.get(\n '/:id/checkpoints',\n createScopeMiddleware((_path, params) => `workflows:read:${params.id}`),\n async (c) => {\n const id = c.req.param('id');\n const workflow = deps.workflows.get(id);\n if (workflow === undefined) {\n const err = new WorkflowNotFoundError(id);\n return c.json({ error: err.kind, message: err.message }, 404);\n }\n const threadId = c.req.query('threadId');\n if (threadId === undefined || threadId.length === 0) {\n return c.json(\n { error: 'config-invalid', message: 'Query parameter `threadId` is required.' },\n 400,\n );\n }\n if (workflow.listCheckpoints === undefined) {\n return c.json(\n {\n error: 'workflow-checkpoints-unsupported',\n message: 'Workflow does not implement listCheckpoints().',\n },\n 400,\n );\n }\n const checkpoints = await workflow.listCheckpoints(threadId);\n return c.json({ workflowId: id, threadId, checkpoints });\n },\n );\n\n app.post(\n '/:id/fork',\n createScopeMiddleware((_path, params) => `workflows:execute:${params.id}`),\n async (c) => {\n const id = c.req.param('id');\n const workflow = deps.workflows.get(id);\n if (workflow === undefined) {\n const err = new WorkflowNotFoundError(id);\n return c.json({ error: err.kind, message: err.message }, 404);\n }\n const parsed = ForkBodySchema.safeParse(await safelyParseJson(c));\n if (!parsed.success) {\n return c.json(\n {\n error: 'config-invalid',\n message: 'Invalid fork body.',\n issues: parsed.error.issues.map((i) => ({ path: i.path, message: i.message })),\n },\n 400,\n );\n }\n // Fork delegates to the workflow's own primitives in Phase 14b/c.\n // Phase 14a returns a structured 202 with the fork descriptor so\n // the API contract is reachable today.\n return c.json(\n {\n workflowId: id,\n status: 'pending',\n fork: parsed.data,\n },\n 202,\n );\n },\n );\n\n return app;\n}\n\nfunction backgroundExecute(\n workflow: NonNullable<ReturnType<WorkflowRegistry['get']>>,\n body: z.infer<typeof ExecuteBodySchema>,\n tracker: { readonly signal: AbortSignal },\n runs: RunStateTracker,\n runId: string,\n streaming: {\n readonly subject: string;\n readonly dispatcher?: import('../ws/dispatcher.js').WsDispatcher;\n },\n): void {\n const opts: { signal?: AbortSignal; threadId?: string } = { signal: tracker.signal };\n if (body.threadId !== undefined) opts.threadId = body.threadId;\n void (async () => {\n try {\n // IP-2: every workflow event reaches the dispatcher — the old\n // loop iterated the stream and threw each event away.\n for await (const ev of workflow.execute(body.input ?? {}, opts)) {\n if (tracker.signal.aborted) break;\n const type =\n typeof (ev as { type?: unknown }).type === 'string'\n ? (ev as { type: string }).type\n : 'workflow.event';\n streaming.dispatcher?.emit(streaming.subject, { type, payload: ev });\n }\n runs.complete(runId, tracker.signal.aborted ? 'aborted' : 'completed');\n } catch (err) {\n streaming.dispatcher?.emit(streaming.subject, {\n type: 'workflow.error',\n payload: { runId, message: err instanceof Error ? err.message : String(err) },\n });\n runs.complete(runId, tracker.signal.aborted ? 'aborted' : 'failed', err);\n }\n })();\n}\n\nasync function safelyParseJson(c: Context<{ Variables: ServerVariables }>): Promise<unknown> {\n try {\n return await c.req.json();\n } catch {\n return {};\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
//#region src/runtime/run-state.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* In-memory bookkeeping for in-flight agent / workflow runs. Exposes
|
|
4
|
+
* a tiny CRUD surface route handlers consume to honour the
|
|
5
|
+
* `GET /runs/:runId/state` and `POST /runs/:runId/abort` endpoints
|
|
6
|
+
* declared in the runtime architecture.
|
|
7
|
+
*
|
|
8
|
+
* The full durable resume / replay path lives in Phase 14b/c on top
|
|
9
|
+
* of the WebSocket layer + the consolidator daemon. Phase 14a only
|
|
10
|
+
* needs enough state to:
|
|
11
|
+
* - mint a runId on every `POST /run` / `POST /stream`,
|
|
12
|
+
* - track its lifecycle (`pending` → `running` → `completed` / `failed` / `aborted`),
|
|
13
|
+
* - propagate `AbortController.signal` so handlers can cancel.
|
|
14
|
+
*
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Stable status discriminator for a run snapshot. Mirrors the values
|
|
19
|
+
* exposed on the public REST surface.
|
|
20
|
+
*
|
|
21
|
+
* @stable
|
|
22
|
+
*/
|
|
23
|
+
type RunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'aborted';
|
|
24
|
+
/**
|
|
25
|
+
* Identifying tag for the underlying execution kind. Workflows run
|
|
26
|
+
* on the durable engine in `@graphorin/workflow`; agents run on the
|
|
27
|
+
* `@graphorin/agent` runtime.
|
|
28
|
+
*
|
|
29
|
+
* @stable
|
|
30
|
+
*/
|
|
31
|
+
type RunKind = 'agent' | 'workflow';
|
|
32
|
+
/**
|
|
33
|
+
* Terminal status a run can settle into (never `pending` / `running`).
|
|
34
|
+
*
|
|
35
|
+
* @stable
|
|
36
|
+
*/
|
|
37
|
+
type TerminalRunStatus = Extract<RunStatus, 'completed' | 'failed' | 'aborted'>;
|
|
38
|
+
/**
|
|
39
|
+
* IP-15: payload handed to the {@link RunStateTracker} `onTerminal` callback
|
|
40
|
+
* the first time a run reaches a terminal state. The server turns this into
|
|
41
|
+
* the `graphorin_agent_runs_total` counter + `graphorin_agent_run_duration_seconds`
|
|
42
|
+
* summary. `durationMs` is omitted for a run that was aborted before it ever
|
|
43
|
+
* started (no meaningful wall-clock).
|
|
44
|
+
*
|
|
45
|
+
* @stable
|
|
46
|
+
*/
|
|
47
|
+
interface TerminalRunInfo {
|
|
48
|
+
readonly status: TerminalRunStatus;
|
|
49
|
+
readonly kind: RunKind;
|
|
50
|
+
readonly durationMs?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Snapshot returned by {@link RunStateTracker.snapshot}.
|
|
54
|
+
*
|
|
55
|
+
* @stable
|
|
56
|
+
*/
|
|
57
|
+
interface RunStateSnapshot {
|
|
58
|
+
readonly runId: string;
|
|
59
|
+
readonly kind: RunKind;
|
|
60
|
+
readonly status: RunStatus;
|
|
61
|
+
readonly startedAt?: number;
|
|
62
|
+
readonly completedAt?: number;
|
|
63
|
+
readonly error?: {
|
|
64
|
+
readonly message: string;
|
|
65
|
+
};
|
|
66
|
+
readonly agentId?: string;
|
|
67
|
+
readonly workflowId?: string;
|
|
68
|
+
readonly threadId?: string;
|
|
69
|
+
readonly sessionId?: string;
|
|
70
|
+
readonly userId?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Bookkeeping descriptor recorded at run start. Either an agent run
|
|
74
|
+
* (with `agentId`) or a workflow run (with `workflowId` + optional
|
|
75
|
+
* `threadId`).
|
|
76
|
+
*
|
|
77
|
+
* @stable
|
|
78
|
+
*/
|
|
79
|
+
type RunDescriptor = {
|
|
80
|
+
readonly kind: 'agent';
|
|
81
|
+
readonly agentId: string;
|
|
82
|
+
readonly sessionId?: string;
|
|
83
|
+
readonly userId?: string;
|
|
84
|
+
} | {
|
|
85
|
+
readonly kind: 'workflow';
|
|
86
|
+
readonly workflowId: string;
|
|
87
|
+
readonly threadId?: string;
|
|
88
|
+
readonly sessionId?: string;
|
|
89
|
+
readonly userId?: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* In-flight handle returned by {@link RunStateTracker.start}. Handlers
|
|
93
|
+
* pass `signal` into the underlying `agent.run / workflow.execute`
|
|
94
|
+
* invocation so cancellation propagates instantly.
|
|
95
|
+
*
|
|
96
|
+
* @stable
|
|
97
|
+
*/
|
|
98
|
+
interface RunHandle {
|
|
99
|
+
readonly runId: string;
|
|
100
|
+
readonly signal: AbortSignal;
|
|
101
|
+
cancel(reason?: unknown): void;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Pluggable tracker. The default in-memory implementation is the only
|
|
105
|
+
* one shipped in Phase 14a; future phases plug in a SQLite-backed
|
|
106
|
+
* variant so durable resume survives process restarts.
|
|
107
|
+
*
|
|
108
|
+
* @stable
|
|
109
|
+
*/
|
|
110
|
+
declare class RunStateTracker {
|
|
111
|
+
#private;
|
|
112
|
+
constructor(options?: {
|
|
113
|
+
readonly now?: () => number;
|
|
114
|
+
/**
|
|
115
|
+
* IP-15: invoked exactly once per run, the first time it settles into a
|
|
116
|
+
* terminal state. Used to drive the run-count + duration metrics. Never
|
|
117
|
+
* throws into the tracker — wrap your handler if it might.
|
|
118
|
+
*/
|
|
119
|
+
readonly onTerminal?: (info: TerminalRunInfo) => void;
|
|
120
|
+
});
|
|
121
|
+
/** Reserve a run id without taking ownership of an AbortSignal. */
|
|
122
|
+
declare(runId: string, descriptor: RunDescriptor): void;
|
|
123
|
+
/** Promote a previously-declared run to `running` (or declare it). */
|
|
124
|
+
start(runId: string, descriptor: RunDescriptor): RunHandle;
|
|
125
|
+
/** Mark a run as terminal. */
|
|
126
|
+
complete(runId: string, status: Extract<RunStatus, 'completed' | 'failed' | 'aborted'>, err?: unknown): void;
|
|
127
|
+
/** Cancel a run via its `AbortController`. */
|
|
128
|
+
abort(runId: string, reason?: unknown): boolean;
|
|
129
|
+
/** Read-only snapshot, safe to JSON.stringify. */
|
|
130
|
+
snapshot(runId: string): RunStateSnapshot | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Number of runs currently in `pending` or `running`. Useful for
|
|
133
|
+
* snapshots / metrics. Note that `pending` runs hold a reservation
|
|
134
|
+
* but have not yet started any work — see {@link runningCount} for
|
|
135
|
+
* the drain-blocking subset.
|
|
136
|
+
*/
|
|
137
|
+
inflightCount(): number;
|
|
138
|
+
/**
|
|
139
|
+
* Number of runs with active work in progress (`running`). The
|
|
140
|
+
* lifecycle drain blocks on this counter only — pending runs are a
|
|
141
|
+
* pure reservation (e.g. an awaited WS subscription) and can be
|
|
142
|
+
* aborted immediately when SIGTERM arrives.
|
|
143
|
+
*/
|
|
144
|
+
runningCount(): number;
|
|
145
|
+
/**
|
|
146
|
+
* Drop every reserved-but-not-yet-started run. Called by the
|
|
147
|
+
* server lifecycle at the start of `stop()` so the drain only
|
|
148
|
+
* waits for actual work in flight.
|
|
149
|
+
*/
|
|
150
|
+
abortPending(reason?: unknown): number;
|
|
151
|
+
/** Drop terminal records older than `olderThan`. */
|
|
152
|
+
prune(olderThan: number): number;
|
|
153
|
+
/** Cancel every in-flight run. Used during graceful shutdown. */
|
|
154
|
+
abortAll(reason?: unknown): number;
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { RunDescriptor, RunHandle, RunStateSnapshot, RunStateTracker, RunStatus };
|
|
158
|
+
//# sourceMappingURL=run-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-state.d.ts","names":[],"sources":["../../src/runtime/run-state.ts"],"sourcesContent":[],"mappings":";;AAsBA;AASA;AAOA;AAWA;AAeA;AAqBA;AAsBA;AAwBA;;;;;;;;;;;;;KA7GY,SAAA;;;;;;;;KASA,OAAA;;;;;;KAOA,iBAAA,GAAoB,QAAQ;;;;;;;;;;UAWvB,eAAA;mBACE;iBACF;;;;;;;;UAaA,gBAAA;;iBAEA;mBACE;;;;;;;;;;;;;;;;;;;KAkBP,aAAA;;;;;;;;;;;;;;;;;;;UAsBK,SAAA;;mBAEE;;;;;;;;;;cAsBN,eAAA;;;;;;;;;iCAasB;;;qCAsBE;;mCAaF,gBAAgB;;kCA0BvC,QAAQ;;;;2BAgCO"}
|