@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.
Files changed (204) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +116 -0
  4. package/dist/app.d.ts +174 -0
  5. package/dist/app.d.ts.map +1 -0
  6. package/dist/app.js +684 -0
  7. package/dist/app.js.map +1 -0
  8. package/dist/commentary/audit-bridge.d.ts +57 -0
  9. package/dist/commentary/audit-bridge.d.ts.map +1 -0
  10. package/dist/commentary/audit-bridge.js +92 -0
  11. package/dist/commentary/audit-bridge.js.map +1 -0
  12. package/dist/commentary/built-in-patterns.d.ts +24 -0
  13. package/dist/commentary/built-in-patterns.d.ts.map +1 -0
  14. package/dist/commentary/built-in-patterns.js +62 -0
  15. package/dist/commentary/built-in-patterns.js.map +1 -0
  16. package/dist/commentary/index.d.ts +5 -0
  17. package/dist/commentary/index.js +5 -0
  18. package/dist/commentary/sanitizer.d.ts +37 -0
  19. package/dist/commentary/sanitizer.d.ts.map +1 -0
  20. package/dist/commentary/sanitizer.js +182 -0
  21. package/dist/commentary/sanitizer.js.map +1 -0
  22. package/dist/commentary/types.d.ts +120 -0
  23. package/dist/commentary/types.d.ts.map +1 -0
  24. package/dist/config.d.ts +760 -0
  25. package/dist/config.d.ts.map +1 -0
  26. package/dist/config.js +217 -0
  27. package/dist/config.js.map +1 -0
  28. package/dist/consolidator/daemon.d.ts +88 -0
  29. package/dist/consolidator/daemon.d.ts.map +1 -0
  30. package/dist/consolidator/daemon.js +54 -0
  31. package/dist/consolidator/daemon.js.map +1 -0
  32. package/dist/consolidator/index.d.ts +2 -0
  33. package/dist/consolidator/index.js +3 -0
  34. package/dist/errors/index.d.ts +104 -0
  35. package/dist/errors/index.d.ts.map +1 -0
  36. package/dist/errors/index.js +131 -0
  37. package/dist/errors/index.js.map +1 -0
  38. package/dist/health/checks.d.ts +120 -0
  39. package/dist/health/checks.d.ts.map +1 -0
  40. package/dist/health/checks.js +127 -0
  41. package/dist/health/checks.js.map +1 -0
  42. package/dist/health/index.d.ts +3 -0
  43. package/dist/health/index.js +4 -0
  44. package/dist/health/routes.d.ts +66 -0
  45. package/dist/health/routes.d.ts.map +1 -0
  46. package/dist/health/routes.js +96 -0
  47. package/dist/health/routes.js.map +1 -0
  48. package/dist/index.d.ts +88 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +86 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/internal/context.d.ts +66 -0
  53. package/dist/internal/context.d.ts.map +1 -0
  54. package/dist/internal/ids.js +21 -0
  55. package/dist/internal/ids.js.map +1 -0
  56. package/dist/internal/json.js +46 -0
  57. package/dist/internal/json.js.map +1 -0
  58. package/dist/lifecycle/hooks.d.ts +57 -0
  59. package/dist/lifecycle/hooks.d.ts.map +1 -0
  60. package/dist/lifecycle/index.d.ts +2 -0
  61. package/dist/lifecycle/index.js +3 -0
  62. package/dist/lifecycle/pre-bind.d.ts +38 -0
  63. package/dist/lifecycle/pre-bind.d.ts.map +1 -0
  64. package/dist/lifecycle/pre-bind.js +62 -0
  65. package/dist/lifecycle/pre-bind.js.map +1 -0
  66. package/dist/metrics/catalog.d.ts +34 -0
  67. package/dist/metrics/catalog.d.ts.map +1 -0
  68. package/dist/metrics/catalog.js +61 -0
  69. package/dist/metrics/catalog.js.map +1 -0
  70. package/dist/metrics/index.d.ts +3 -0
  71. package/dist/metrics/index.js +4 -0
  72. package/dist/metrics/registry.d.ts +63 -0
  73. package/dist/metrics/registry.d.ts.map +1 -0
  74. package/dist/metrics/registry.js +222 -0
  75. package/dist/metrics/registry.js.map +1 -0
  76. package/dist/middleware/audit.d.ts +47 -0
  77. package/dist/middleware/audit.d.ts.map +1 -0
  78. package/dist/middleware/audit.js +71 -0
  79. package/dist/middleware/audit.js.map +1 -0
  80. package/dist/middleware/auth.d.ts +50 -0
  81. package/dist/middleware/auth.d.ts.map +1 -0
  82. package/dist/middleware/auth.js +164 -0
  83. package/dist/middleware/auth.js.map +1 -0
  84. package/dist/middleware/cors.d.ts +15 -0
  85. package/dist/middleware/cors.d.ts.map +1 -0
  86. package/dist/middleware/cors.js +45 -0
  87. package/dist/middleware/cors.js.map +1 -0
  88. package/dist/middleware/csrf.d.ts +15 -0
  89. package/dist/middleware/csrf.d.ts.map +1 -0
  90. package/dist/middleware/csrf.js +77 -0
  91. package/dist/middleware/csrf.js.map +1 -0
  92. package/dist/middleware/idempotency.d.ts +41 -0
  93. package/dist/middleware/idempotency.d.ts.map +1 -0
  94. package/dist/middleware/idempotency.js +198 -0
  95. package/dist/middleware/idempotency.js.map +1 -0
  96. package/dist/middleware/index.d.ts +9 -0
  97. package/dist/middleware/index.js +10 -0
  98. package/dist/middleware/rate-limit.d.ts +17 -0
  99. package/dist/middleware/rate-limit.d.ts.map +1 -0
  100. package/dist/middleware/rate-limit.js +47 -0
  101. package/dist/middleware/rate-limit.js.map +1 -0
  102. package/dist/middleware/request-state.d.ts +27 -0
  103. package/dist/middleware/request-state.d.ts.map +1 -0
  104. package/dist/middleware/request-state.js +42 -0
  105. package/dist/middleware/request-state.js.map +1 -0
  106. package/dist/middleware/scope.d.ts +24 -0
  107. package/dist/middleware/scope.d.ts.map +1 -0
  108. package/dist/middleware/scope.js +50 -0
  109. package/dist/middleware/scope.js.map +1 -0
  110. package/dist/registry/index.d.ts +135 -0
  111. package/dist/registry/index.d.ts.map +1 -0
  112. package/dist/registry/index.js +96 -0
  113. package/dist/registry/index.js.map +1 -0
  114. package/dist/replay/index.d.ts +2 -0
  115. package/dist/replay/index.js +3 -0
  116. package/dist/replay/routes.d.ts +46 -0
  117. package/dist/replay/routes.d.ts.map +1 -0
  118. package/dist/replay/routes.js +189 -0
  119. package/dist/replay/routes.js.map +1 -0
  120. package/dist/routes/agents.d.ts +41 -0
  121. package/dist/routes/agents.d.ts.map +1 -0
  122. package/dist/routes/agents.js +213 -0
  123. package/dist/routes/agents.js.map +1 -0
  124. package/dist/routes/audit.d.ts +57 -0
  125. package/dist/routes/audit.d.ts.map +1 -0
  126. package/dist/routes/audit.js +116 -0
  127. package/dist/routes/audit.js.map +1 -0
  128. package/dist/routes/auth.d.ts +26 -0
  129. package/dist/routes/auth.d.ts.map +1 -0
  130. package/dist/routes/auth.js +54 -0
  131. package/dist/routes/auth.js.map +1 -0
  132. package/dist/routes/health.d.ts +22 -0
  133. package/dist/routes/health.d.ts.map +1 -0
  134. package/dist/routes/health.js +33 -0
  135. package/dist/routes/health.js.map +1 -0
  136. package/dist/routes/index.d.ts +10 -0
  137. package/dist/routes/index.js +12 -0
  138. package/dist/routes/mcp.d.ts +32 -0
  139. package/dist/routes/mcp.d.ts.map +1 -0
  140. package/dist/routes/mcp.js +61 -0
  141. package/dist/routes/mcp.js.map +1 -0
  142. package/dist/routes/memory.d.ts +141 -0
  143. package/dist/routes/memory.d.ts.map +1 -0
  144. package/dist/routes/memory.js +102 -0
  145. package/dist/routes/memory.js.map +1 -0
  146. package/dist/routes/sessions.d.ts +54 -0
  147. package/dist/routes/sessions.d.ts.map +1 -0
  148. package/dist/routes/sessions.js +135 -0
  149. package/dist/routes/sessions.js.map +1 -0
  150. package/dist/routes/skills.d.ts +34 -0
  151. package/dist/routes/skills.d.ts.map +1 -0
  152. package/dist/routes/skills.js +54 -0
  153. package/dist/routes/skills.js.map +1 -0
  154. package/dist/routes/tokens.d.ts +25 -0
  155. package/dist/routes/tokens.d.ts.map +1 -0
  156. package/dist/routes/tokens.js +87 -0
  157. package/dist/routes/tokens.js.map +1 -0
  158. package/dist/routes/workflows.d.ts +27 -0
  159. package/dist/routes/workflows.d.ts.map +1 -0
  160. package/dist/routes/workflows.js +220 -0
  161. package/dist/routes/workflows.js.map +1 -0
  162. package/dist/runtime/run-state.d.ts +158 -0
  163. package/dist/runtime/run-state.d.ts.map +1 -0
  164. package/dist/runtime/run-state.js +182 -0
  165. package/dist/runtime/run-state.js.map +1 -0
  166. package/dist/sse/events.d.ts +44 -0
  167. package/dist/sse/events.d.ts.map +1 -0
  168. package/dist/sse/events.js +200 -0
  169. package/dist/sse/events.js.map +1 -0
  170. package/dist/sse/index.d.ts +2 -0
  171. package/dist/sse/index.js +3 -0
  172. package/dist/triggers/daemon.d.ts +74 -0
  173. package/dist/triggers/daemon.d.ts.map +1 -0
  174. package/dist/triggers/daemon.js +114 -0
  175. package/dist/triggers/daemon.js.map +1 -0
  176. package/dist/triggers/index.d.ts +3 -0
  177. package/dist/triggers/index.js +4 -0
  178. package/dist/triggers/routes.d.ts +21 -0
  179. package/dist/triggers/routes.d.ts.map +1 -0
  180. package/dist/triggers/routes.js +117 -0
  181. package/dist/triggers/routes.js.map +1 -0
  182. package/dist/ws/dispatcher.d.ts +169 -0
  183. package/dist/ws/dispatcher.d.ts.map +1 -0
  184. package/dist/ws/dispatcher.js +303 -0
  185. package/dist/ws/dispatcher.js.map +1 -0
  186. package/dist/ws/index.d.ts +6 -0
  187. package/dist/ws/index.js +7 -0
  188. package/dist/ws/replay-buffer.d.ts +47 -0
  189. package/dist/ws/replay-buffer.d.ts.map +1 -0
  190. package/dist/ws/replay-buffer.js +88 -0
  191. package/dist/ws/replay-buffer.js.map +1 -0
  192. package/dist/ws/subjects.d.ts +71 -0
  193. package/dist/ws/subjects.d.ts.map +1 -0
  194. package/dist/ws/subjects.js +112 -0
  195. package/dist/ws/subjects.js.map +1 -0
  196. package/dist/ws/ticket.d.ts +74 -0
  197. package/dist/ws/ticket.d.ts.map +1 -0
  198. package/dist/ws/ticket.js +112 -0
  199. package/dist/ws/ticket.js.map +1 -0
  200. package/dist/ws/upgrade.d.ts +63 -0
  201. package/dist/ws/upgrade.d.ts.map +1 -0
  202. package/dist/ws/upgrade.js +324 -0
  203. package/dist/ws/upgrade.js.map +1 -0
  204. package/package.json +156 -0
@@ -0,0 +1,24 @@
1
+ import { ServerVariables } from "../internal/context.js";
2
+ import { MiddlewareHandler } from "hono";
3
+ import { ParsedScope } from "@graphorin/security/auth";
4
+
5
+ //#region src/middleware/scope.d.ts
6
+
7
+ /**
8
+ * Required-scope spec accepted by {@link createScopeMiddleware}. Either
9
+ * a single string (`'agents:invoke'`), a parsed scope, or a function
10
+ * that derives the required scope from the request (e.g. to insert the
11
+ * `:id` segment lazily).
12
+ *
13
+ * @stable
14
+ */
15
+ type ScopeRequirement = string | ParsedScope | ((path: string, params: Record<string, string>) => string | ParsedScope);
16
+ /**
17
+ * @stable
18
+ */
19
+ declare function createScopeMiddleware(requirement: ScopeRequirement): MiddlewareHandler<{
20
+ Variables: ServerVariables;
21
+ }>;
22
+ //#endregion
23
+ export { ScopeRequirement, createScopeMiddleware };
24
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","names":[],"sources":["../../src/middleware/scope.ts"],"sourcesContent":[],"mappings":";;;;;;AA6BA;;;;;;;;KARY,gBAAA,YAER,sCACwB,oCAAoC;;;;iBAKhD,qBAAA,cACD,mBACZ;aAA+B"}
@@ -0,0 +1,50 @@
1
+ import { parseScope, scopeMatches } from "@graphorin/security/auth";
2
+
3
+ //#region src/middleware/scope.ts
4
+ /**
5
+ * Per-route scope enforcement. Reads the verified token populated by
6
+ * the auth middleware and short-circuits with `403` when the granted
7
+ * set does not match the required scope.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ /**
12
+ * @stable
13
+ */
14
+ function createScopeMiddleware(requirement) {
15
+ return async (c, next) => {
16
+ const auth = c.get("state").auth;
17
+ if (auth.kind === "anonymous") {
18
+ await next();
19
+ return;
20
+ }
21
+ if (auth.kind === "unauthenticated") return c.json({
22
+ error: "auth-required",
23
+ message: "Authentication required for this endpoint."
24
+ }, 401);
25
+ const required = resolveRequirement(requirement, c.req.path, c.req.param());
26
+ const granted = auth.grantedScopes;
27
+ let allowed = false;
28
+ for (const scope of granted) if (scopeMatches(scope, required)) {
29
+ allowed = true;
30
+ break;
31
+ }
32
+ if (!allowed) return c.json({
33
+ error: "scope-denied",
34
+ message: `Token lacks required scope '${required.raw}'.`,
35
+ hint: `Mint a token with the '${required.raw}' (or admin:*) scope.`
36
+ }, 403);
37
+ await next();
38
+ };
39
+ }
40
+ function resolveRequirement(requirement, path, params) {
41
+ if (typeof requirement === "function") {
42
+ const next = requirement(path, params);
43
+ return typeof next === "string" ? parseScope(next) : next;
44
+ }
45
+ return typeof requirement === "string" ? parseScope(requirement) : requirement;
46
+ }
47
+
48
+ //#endregion
49
+ export { createScopeMiddleware };
50
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.js","names":[],"sources":["../../src/middleware/scope.ts"],"sourcesContent":["/**\n * Per-route scope enforcement. Reads the verified token populated by\n * the auth middleware and short-circuits with `403` when the granted\n * set does not match the required scope.\n *\n * @packageDocumentation\n */\n\nimport { type ParsedScope, parseScope, scopeMatches } from '@graphorin/security/auth';\nimport type { MiddlewareHandler } from 'hono';\n\nimport type { ServerVariables } from '../internal/context.js';\n\n/**\n * Required-scope spec accepted by {@link createScopeMiddleware}. Either\n * a single string (`'agents:invoke'`), a parsed scope, or a function\n * that derives the required scope from the request (e.g. to insert the\n * `:id` segment lazily).\n *\n * @stable\n */\nexport type ScopeRequirement =\n | string\n | ParsedScope\n | ((path: string, params: Record<string, string>) => string | ParsedScope);\n\n/**\n * @stable\n */\nexport function createScopeMiddleware(\n requirement: ScopeRequirement,\n): MiddlewareHandler<{ Variables: ServerVariables }> {\n return async (c, next) => {\n const auth = c.get('state').auth;\n if (auth.kind === 'anonymous') {\n // IP-13: auth is disabled server-wide (auth.kind='none'). There is no\n // token to scope-check — the trusted-loopback operator is allowed\n // through every gate.\n await next();\n return;\n }\n if (auth.kind === 'unauthenticated') {\n return c.json(\n {\n error: 'auth-required',\n message: 'Authentication required for this endpoint.',\n },\n 401,\n );\n }\n const required = resolveRequirement(\n requirement,\n c.req.path,\n c.req.param() as Record<string, string>,\n );\n const granted = auth.grantedScopes;\n let allowed = false;\n for (const scope of granted) {\n if (scopeMatches(scope, required)) {\n allowed = true;\n break;\n }\n }\n if (!allowed) {\n return c.json(\n {\n error: 'scope-denied',\n message: `Token lacks required scope '${required.raw}'.`,\n hint: `Mint a token with the '${required.raw}' (or admin:*) scope.`,\n },\n 403,\n );\n }\n await next();\n };\n}\n\nfunction resolveRequirement(\n requirement: ScopeRequirement,\n path: string,\n params: Record<string, string>,\n): ParsedScope {\n if (typeof requirement === 'function') {\n const next = requirement(path, params);\n return typeof next === 'string' ? parseScope(next) : next;\n }\n return typeof requirement === 'string' ? parseScope(requirement) : requirement;\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAgB,sBACd,aACmD;AACnD,QAAO,OAAO,GAAG,SAAS;EACxB,MAAM,OAAO,EAAE,IAAI,QAAQ,CAAC;AAC5B,MAAI,KAAK,SAAS,aAAa;AAI7B,SAAM,MAAM;AACZ;;AAEF,MAAI,KAAK,SAAS,kBAChB,QAAO,EAAE,KACP;GACE,OAAO;GACP,SAAS;GACV,EACD,IACD;EAEH,MAAM,WAAW,mBACf,aACA,EAAE,IAAI,MACN,EAAE,IAAI,OAAO,CACd;EACD,MAAM,UAAU,KAAK;EACrB,IAAI,UAAU;AACd,OAAK,MAAM,SAAS,QAClB,KAAI,aAAa,OAAO,SAAS,EAAE;AACjC,aAAU;AACV;;AAGJ,MAAI,CAAC,QACH,QAAO,EAAE,KACP;GACE,OAAO;GACP,SAAS,+BAA+B,SAAS,IAAI;GACrD,MAAM,0BAA0B,SAAS,IAAI;GAC9C,EACD,IACD;AAEH,QAAM,MAAM;;;AAIhB,SAAS,mBACP,aACA,MACA,QACa;AACb,KAAI,OAAO,gBAAgB,YAAY;EACrC,MAAM,OAAO,YAAY,MAAM,OAAO;AACtC,SAAO,OAAO,SAAS,WAAW,WAAW,KAAK,GAAG;;AAEvD,QAAO,OAAO,gBAAgB,WAAW,WAAW,YAAY,GAAG"}
@@ -0,0 +1,135 @@
1
+ //#region src/registry/index.d.ts
2
+ /**
3
+ * Registry plumbing that lets the server route handlers locate user-
4
+ * defined agents, workflows, sessions, memory, skills, and MCP
5
+ * server bindings without taking a hard peer dependency on every
6
+ * sibling package.
7
+ *
8
+ * Every entry is keyed by string id; lookups never throw — callers
9
+ * receive `undefined` and the route handler decides how to surface
10
+ * the miss (typically a 404 with a typed error body).
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ /**
15
+ * Minimal shape the server needs from an `Agent`. Compatible with
16
+ * the `Agent` interface from `@graphorin/agent` but kept
17
+ * structurally so we avoid the peer dependency.
18
+ *
19
+ * @stable
20
+ */
21
+ interface ServerAgentLike {
22
+ readonly id: string;
23
+ run(input: unknown, options?: {
24
+ readonly signal?: AbortSignal;
25
+ readonly sessionId?: string;
26
+ readonly userId?: string;
27
+ }): Promise<unknown>;
28
+ /**
29
+ * Streaming surface (IP-2). `@graphorin/agent` agents satisfy this
30
+ * structurally; `POST /agents/:id/stream` consumes it and emits
31
+ * every event onto the run's WS subject. Optional so plain
32
+ * run-only fixtures keep working (they emit a single terminal frame).
33
+ */
34
+ stream?(input: unknown, options?: {
35
+ readonly signal?: AbortSignal;
36
+ readonly sessionId?: string;
37
+ readonly userId?: string;
38
+ }): AsyncIterable<unknown>;
39
+ }
40
+ /**
41
+ * Minimal shape the server needs from a `Workflow`. Mirrors the
42
+ * `Workflow` surface from `@graphorin/workflow`.
43
+ *
44
+ * @stable
45
+ */
46
+ interface ServerWorkflowLike {
47
+ readonly name: string;
48
+ execute(input: unknown, options?: {
49
+ readonly signal?: AbortSignal;
50
+ readonly threadId?: string;
51
+ }): AsyncIterable<unknown>;
52
+ resume?(threadId: string, directive?: {
53
+ readonly resume?: unknown;
54
+ }): AsyncIterable<unknown>;
55
+ getState?(threadId: string): Promise<unknown>;
56
+ listCheckpoints?(threadId: string): Promise<ReadonlyArray<unknown>>;
57
+ }
58
+ /**
59
+ * Snapshot record returned by {@link AgentRegistry.list}.
60
+ *
61
+ * @stable
62
+ */
63
+ interface AgentSummary {
64
+ readonly id: string;
65
+ readonly description?: string;
66
+ readonly tags?: ReadonlyArray<string>;
67
+ }
68
+ /**
69
+ * Snapshot record returned by {@link WorkflowRegistry.list}.
70
+ *
71
+ * @stable
72
+ */
73
+ interface WorkflowSummary {
74
+ readonly id: string;
75
+ readonly description?: string;
76
+ readonly tags?: ReadonlyArray<string>;
77
+ }
78
+ /**
79
+ * Registration descriptor accepted by {@link AgentRegistry.register}.
80
+ *
81
+ * @stable
82
+ */
83
+ interface AgentRegistration {
84
+ readonly id: string;
85
+ readonly agent: ServerAgentLike;
86
+ readonly description?: string;
87
+ readonly tags?: ReadonlyArray<string>;
88
+ }
89
+ /**
90
+ * Registration descriptor accepted by {@link WorkflowRegistry.register}.
91
+ *
92
+ * @stable
93
+ */
94
+ interface WorkflowRegistration {
95
+ readonly id: string;
96
+ readonly workflow: ServerWorkflowLike;
97
+ readonly description?: string;
98
+ readonly tags?: ReadonlyArray<string>;
99
+ }
100
+ /**
101
+ * Read/write registry for agents the server should expose. Every
102
+ * mutation is synchronous and the lookup is `O(1)`.
103
+ *
104
+ * The class is intentionally tiny — extension points (e.g. lazy
105
+ * factory loading, per-tenant scoping) live in higher-level packages
106
+ * and consume this surface as a primitive.
107
+ *
108
+ * @stable
109
+ */
110
+ declare class AgentRegistry {
111
+ #private;
112
+ register(entry: AgentRegistration): void;
113
+ unregister(id: string): boolean;
114
+ get(id: string): ServerAgentLike | undefined;
115
+ has(id: string): boolean;
116
+ list(): ReadonlyArray<AgentSummary>;
117
+ describe(id: string): AgentSummary | undefined;
118
+ size(): number;
119
+ }
120
+ /**
121
+ * @stable
122
+ */
123
+ declare class WorkflowRegistry {
124
+ #private;
125
+ register(entry: WorkflowRegistration): void;
126
+ unregister(id: string): boolean;
127
+ get(id: string): ServerWorkflowLike | undefined;
128
+ has(id: string): boolean;
129
+ list(): ReadonlyArray<WorkflowSummary>;
130
+ describe(id: string): WorkflowSummary | undefined;
131
+ size(): number;
132
+ }
133
+ //#endregion
134
+ export { AgentRegistration, AgentRegistry, AgentSummary, ServerAgentLike, ServerWorkflowLike, WorkflowRegistration, WorkflowRegistry, WorkflowSummary };
135
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/registry/index.ts"],"sourcesContent":[],"mappings":";;AAoBA;;;;;;AAgCA;;;;;;;;AAgBA;AAWA;AAkCA;AAYA;AAiBa,UA1HI,eAAA,CA0HS;EAGR,SAAA,EAAA,EAAA,MAAA;EAaC,GAAA,CAAA,KAAA,EAAA,OAAA,EAAA,OAoBiB,CApBjB,EAAA;IAQK,SAAA,MAAA,CAAA,EA7IA,WA6IA;IAAd,SAAA,SAAA,CAAA,EAAA,MAAA;IAYc,SAAA,MAAA,CAAA,EAAA,MAAA;EAAY,CAAA,CAAA,EArJ/B,OAqJ+B,CAAA,OAAA,CAAA;EAkBvB;;;;;;EAoC0B,MAAA,EAAA,KAAA,EAAA,OAAA,EAAA,QAAA,EAAA;sBAjMf;;;MAInB;;;;;;;;UASY,kBAAA;;;sBAIiB;;MAC7B;;;MACmE;+BACzC;sCACO,QAAQ;;;;;;;UAQ7B,YAAA;;;kBAGC;;;;;;;UAQD,eAAA;;;kBAGC;;;;;;;UA+BD,iBAAA;;kBAEC;;kBAEA;;;;;;;UAQD,oBAAA;;qBAEI;;kBAEH;;;;;;;;;;;;cAaL,aAAA;;kBAGK;;mBAaC;;UAQT,cAAc;wBAYA;;;;;;cAkBX,gBAAA;;kBAGK;;mBAaC;;UAQT,cAAc;wBAYA"}
@@ -0,0 +1,96 @@
1
+ //#region src/registry/index.ts
2
+ /**
3
+ * Read/write registry for agents the server should expose. Every
4
+ * mutation is synchronous and the lookup is `O(1)`.
5
+ *
6
+ * The class is intentionally tiny — extension points (e.g. lazy
7
+ * factory loading, per-tenant scoping) live in higher-level packages
8
+ * and consume this surface as a primitive.
9
+ *
10
+ * @stable
11
+ */
12
+ var AgentRegistry = class {
13
+ #entries = /* @__PURE__ */ new Map();
14
+ register(entry) {
15
+ this.#entries.set(entry.id, {
16
+ id: entry.id,
17
+ agent: entry.agent,
18
+ ...entry.description !== void 0 ? { description: entry.description } : {},
19
+ ...entry.tags !== void 0 ? { tags: Object.freeze(entry.tags.slice()) } : {}
20
+ });
21
+ }
22
+ unregister(id) {
23
+ return this.#entries.delete(id);
24
+ }
25
+ get(id) {
26
+ return this.#entries.get(id)?.agent;
27
+ }
28
+ has(id) {
29
+ return this.#entries.has(id);
30
+ }
31
+ list() {
32
+ return Object.freeze([...this.#entries.values()].map((entry) => Object.freeze({
33
+ id: entry.id,
34
+ ...entry.description !== void 0 ? { description: entry.description } : {},
35
+ ...entry.tags !== void 0 ? { tags: entry.tags } : {}
36
+ })));
37
+ }
38
+ describe(id) {
39
+ const entry = this.#entries.get(id);
40
+ if (entry === void 0) return void 0;
41
+ return Object.freeze({
42
+ id: entry.id,
43
+ ...entry.description !== void 0 ? { description: entry.description } : {},
44
+ ...entry.tags !== void 0 ? { tags: entry.tags } : {}
45
+ });
46
+ }
47
+ size() {
48
+ return this.#entries.size;
49
+ }
50
+ };
51
+ /**
52
+ * @stable
53
+ */
54
+ var WorkflowRegistry = class {
55
+ #entries = /* @__PURE__ */ new Map();
56
+ register(entry) {
57
+ this.#entries.set(entry.id, {
58
+ id: entry.id,
59
+ workflow: entry.workflow,
60
+ ...entry.description !== void 0 ? { description: entry.description } : {},
61
+ ...entry.tags !== void 0 ? { tags: Object.freeze(entry.tags.slice()) } : {}
62
+ });
63
+ }
64
+ unregister(id) {
65
+ return this.#entries.delete(id);
66
+ }
67
+ get(id) {
68
+ return this.#entries.get(id)?.workflow;
69
+ }
70
+ has(id) {
71
+ return this.#entries.has(id);
72
+ }
73
+ list() {
74
+ return Object.freeze([...this.#entries.values()].map((entry) => Object.freeze({
75
+ id: entry.id,
76
+ ...entry.description !== void 0 ? { description: entry.description } : {},
77
+ ...entry.tags !== void 0 ? { tags: entry.tags } : {}
78
+ })));
79
+ }
80
+ describe(id) {
81
+ const entry = this.#entries.get(id);
82
+ if (entry === void 0) return void 0;
83
+ return Object.freeze({
84
+ id: entry.id,
85
+ ...entry.description !== void 0 ? { description: entry.description } : {},
86
+ ...entry.tags !== void 0 ? { tags: entry.tags } : {}
87
+ });
88
+ }
89
+ size() {
90
+ return this.#entries.size;
91
+ }
92
+ };
93
+
94
+ //#endregion
95
+ export { AgentRegistry, WorkflowRegistry };
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["#entries"],"sources":["../../src/registry/index.ts"],"sourcesContent":["/**\n * Registry plumbing that lets the server route handlers locate user-\n * defined agents, workflows, sessions, memory, skills, and MCP\n * server bindings without taking a hard peer dependency on every\n * sibling package.\n *\n * Every entry is keyed by string id; lookups never throw — callers\n * receive `undefined` and the route handler decides how to surface\n * the miss (typically a 404 with a typed error body).\n *\n * @packageDocumentation\n */\n\n/**\n * Minimal shape the server needs from an `Agent`. Compatible with\n * the `Agent` interface from `@graphorin/agent` but kept\n * structurally so we avoid the peer dependency.\n *\n * @stable\n */\nexport interface ServerAgentLike {\n readonly id: string;\n run(\n input: unknown,\n options?: {\n readonly signal?: AbortSignal;\n readonly sessionId?: string;\n readonly userId?: string;\n },\n ): Promise<unknown>;\n /**\n * Streaming surface (IP-2). `@graphorin/agent` agents satisfy this\n * structurally; `POST /agents/:id/stream` consumes it and emits\n * every event onto the run's WS subject. Optional so plain\n * run-only fixtures keep working (they emit a single terminal frame).\n */\n stream?(\n input: unknown,\n options?: {\n readonly signal?: AbortSignal;\n readonly sessionId?: string;\n readonly userId?: string;\n },\n ): AsyncIterable<unknown>;\n}\n\n/**\n * Minimal shape the server needs from a `Workflow`. Mirrors the\n * `Workflow` surface from `@graphorin/workflow`.\n *\n * @stable\n */\nexport interface ServerWorkflowLike {\n readonly name: string;\n execute(\n input: unknown,\n options?: { readonly signal?: AbortSignal; readonly threadId?: string },\n ): AsyncIterable<unknown>;\n resume?(threadId: string, directive?: { readonly resume?: unknown }): AsyncIterable<unknown>;\n getState?(threadId: string): Promise<unknown>;\n listCheckpoints?(threadId: string): Promise<ReadonlyArray<unknown>>;\n}\n\n/**\n * Snapshot record returned by {@link AgentRegistry.list}.\n *\n * @stable\n */\nexport interface AgentSummary {\n readonly id: string;\n readonly description?: string;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * Snapshot record returned by {@link WorkflowRegistry.list}.\n *\n * @stable\n */\nexport interface WorkflowSummary {\n readonly id: string;\n readonly description?: string;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * Internal registry entry — pairs the user-supplied object with the\n * metadata routes serve to clients.\n *\n * @internal\n */\ninterface AgentEntry {\n readonly id: string;\n readonly agent: ServerAgentLike;\n readonly description?: string;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * @internal\n */\ninterface WorkflowEntry {\n readonly id: string;\n readonly workflow: ServerWorkflowLike;\n readonly description?: string;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * Registration descriptor accepted by {@link AgentRegistry.register}.\n *\n * @stable\n */\nexport interface AgentRegistration {\n readonly id: string;\n readonly agent: ServerAgentLike;\n readonly description?: string;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * Registration descriptor accepted by {@link WorkflowRegistry.register}.\n *\n * @stable\n */\nexport interface WorkflowRegistration {\n readonly id: string;\n readonly workflow: ServerWorkflowLike;\n readonly description?: string;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * Read/write registry for agents the server should expose. Every\n * mutation is synchronous and the lookup is `O(1)`.\n *\n * The class is intentionally tiny — extension points (e.g. lazy\n * factory loading, per-tenant scoping) live in higher-level packages\n * and consume this surface as a primitive.\n *\n * @stable\n */\nexport class AgentRegistry {\n readonly #entries: Map<string, AgentEntry> = new Map();\n\n register(entry: AgentRegistration): void {\n this.#entries.set(entry.id, {\n id: entry.id,\n agent: entry.agent,\n ...(entry.description !== undefined ? { description: entry.description } : {}),\n ...(entry.tags !== undefined ? { tags: Object.freeze(entry.tags.slice()) } : {}),\n });\n }\n\n unregister(id: string): boolean {\n return this.#entries.delete(id);\n }\n\n get(id: string): ServerAgentLike | undefined {\n return this.#entries.get(id)?.agent;\n }\n\n has(id: string): boolean {\n return this.#entries.has(id);\n }\n\n list(): ReadonlyArray<AgentSummary> {\n return Object.freeze(\n [...this.#entries.values()].map((entry) =>\n Object.freeze({\n id: entry.id,\n ...(entry.description !== undefined ? { description: entry.description } : {}),\n ...(entry.tags !== undefined ? { tags: entry.tags } : {}),\n }),\n ),\n );\n }\n\n describe(id: string): AgentSummary | undefined {\n const entry = this.#entries.get(id);\n if (entry === undefined) return undefined;\n return Object.freeze({\n id: entry.id,\n ...(entry.description !== undefined ? { description: entry.description } : {}),\n ...(entry.tags !== undefined ? { tags: entry.tags } : {}),\n });\n }\n\n size(): number {\n return this.#entries.size;\n }\n}\n\n/**\n * @stable\n */\nexport class WorkflowRegistry {\n readonly #entries: Map<string, WorkflowEntry> = new Map();\n\n register(entry: WorkflowRegistration): void {\n this.#entries.set(entry.id, {\n id: entry.id,\n workflow: entry.workflow,\n ...(entry.description !== undefined ? { description: entry.description } : {}),\n ...(entry.tags !== undefined ? { tags: Object.freeze(entry.tags.slice()) } : {}),\n });\n }\n\n unregister(id: string): boolean {\n return this.#entries.delete(id);\n }\n\n get(id: string): ServerWorkflowLike | undefined {\n return this.#entries.get(id)?.workflow;\n }\n\n has(id: string): boolean {\n return this.#entries.has(id);\n }\n\n list(): ReadonlyArray<WorkflowSummary> {\n return Object.freeze(\n [...this.#entries.values()].map((entry) =>\n Object.freeze({\n id: entry.id,\n ...(entry.description !== undefined ? { description: entry.description } : {}),\n ...(entry.tags !== undefined ? { tags: entry.tags } : {}),\n }),\n ),\n );\n }\n\n describe(id: string): WorkflowSummary | undefined {\n const entry = this.#entries.get(id);\n if (entry === undefined) return undefined;\n return Object.freeze({\n id: entry.id,\n ...(entry.description !== undefined ? { description: entry.description } : {}),\n ...(entry.tags !== undefined ? { tags: entry.tags } : {}),\n });\n }\n\n size(): number {\n return this.#entries.size;\n }\n}\n"],"mappings":";;;;;;;;;;;AA8IA,IAAa,gBAAb,MAA2B;CACzB,CAASA,0BAAoC,IAAI,KAAK;CAEtD,SAAS,OAAgC;AACvC,QAAKA,QAAS,IAAI,MAAM,IAAI;GAC1B,IAAI,MAAM;GACV,OAAO,MAAM;GACb,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,aAAa,GAAG,EAAE;GAC7E,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,OAAO,OAAO,MAAM,KAAK,OAAO,CAAC,EAAE,GAAG,EAAE;GAChF,CAAC;;CAGJ,WAAW,IAAqB;AAC9B,SAAO,MAAKA,QAAS,OAAO,GAAG;;CAGjC,IAAI,IAAyC;AAC3C,SAAO,MAAKA,QAAS,IAAI,GAAG,EAAE;;CAGhC,IAAI,IAAqB;AACvB,SAAO,MAAKA,QAAS,IAAI,GAAG;;CAG9B,OAAoC;AAClC,SAAO,OAAO,OACZ,CAAC,GAAG,MAAKA,QAAS,QAAQ,CAAC,CAAC,KAAK,UAC/B,OAAO,OAAO;GACZ,IAAI,MAAM;GACV,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,aAAa,GAAG,EAAE;GAC7E,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,MAAM,MAAM,GAAG,EAAE;GACzD,CAAC,CACH,CACF;;CAGH,SAAS,IAAsC;EAC7C,MAAM,QAAQ,MAAKA,QAAS,IAAI,GAAG;AACnC,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,OAAO,OAAO;GACnB,IAAI,MAAM;GACV,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,aAAa,GAAG,EAAE;GAC7E,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,MAAM,MAAM,GAAG,EAAE;GACzD,CAAC;;CAGJ,OAAe;AACb,SAAO,MAAKA,QAAS;;;;;;AAOzB,IAAa,mBAAb,MAA8B;CAC5B,CAASA,0BAAuC,IAAI,KAAK;CAEzD,SAAS,OAAmC;AAC1C,QAAKA,QAAS,IAAI,MAAM,IAAI;GAC1B,IAAI,MAAM;GACV,UAAU,MAAM;GAChB,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,aAAa,GAAG,EAAE;GAC7E,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,OAAO,OAAO,MAAM,KAAK,OAAO,CAAC,EAAE,GAAG,EAAE;GAChF,CAAC;;CAGJ,WAAW,IAAqB;AAC9B,SAAO,MAAKA,QAAS,OAAO,GAAG;;CAGjC,IAAI,IAA4C;AAC9C,SAAO,MAAKA,QAAS,IAAI,GAAG,EAAE;;CAGhC,IAAI,IAAqB;AACvB,SAAO,MAAKA,QAAS,IAAI,GAAG;;CAG9B,OAAuC;AACrC,SAAO,OAAO,OACZ,CAAC,GAAG,MAAKA,QAAS,QAAQ,CAAC,CAAC,KAAK,UAC/B,OAAO,OAAO;GACZ,IAAI,MAAM;GACV,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,aAAa,GAAG,EAAE;GAC7E,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,MAAM,MAAM,GAAG,EAAE;GACzD,CAAC,CACH,CACF;;CAGH,SAAS,IAAyC;EAChD,MAAM,QAAQ,MAAKA,QAAS,IAAI,GAAG;AACnC,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,OAAO,OAAO;GACnB,IAAI,MAAM;GACV,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,aAAa,GAAG,EAAE;GAC7E,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,MAAM,MAAM,GAAG,EAAE;GACzD,CAAC;;CAGJ,OAAe;AACb,SAAO,MAAKA,QAAS"}
@@ -0,0 +1,2 @@
1
+ import { ReplayApi, ReplayMode, ReplayResponse, ReplayRoutesDeps, createReplayRoutes } from "./routes.js";
2
+ export { type ReplayApi, type ReplayMode, type ReplayResponse, type ReplayRoutesDeps, createReplayRoutes };
@@ -0,0 +1,3 @@
1
+ import { createReplayRoutes } from "./routes.js";
2
+
3
+ export { createReplayRoutes };
@@ -0,0 +1,46 @@
1
+ import { ServerVariables } from "../internal/context.js";
2
+ import { AuditDb } from "@graphorin/security/audit";
3
+ import { Hono } from "hono";
4
+
5
+ //#region src/replay/routes.d.ts
6
+
7
+ /** @stable */
8
+ type ReplayMode = 'sanitized' | 'raw';
9
+ /** @stable */
10
+ interface ReplayApi {
11
+ loadRunReplay(input: {
12
+ readonly runId: string;
13
+ readonly mode: ReplayMode;
14
+ readonly fromMessageId?: string;
15
+ readonly provider?: string;
16
+ }): Promise<ReplayResponse>;
17
+ loadSessionReplay(input: {
18
+ readonly sessionId: string;
19
+ readonly mode: ReplayMode;
20
+ readonly fromMessageId?: string;
21
+ readonly provider?: string;
22
+ }): Promise<ReplayResponse>;
23
+ }
24
+ /** @stable */
25
+ interface ReplayResponse {
26
+ readonly events: ReadonlyArray<unknown>;
27
+ readonly truncated?: boolean;
28
+ readonly nextCursor?: string;
29
+ }
30
+ /**
31
+ * @stable
32
+ */
33
+ interface ReplayRoutesDeps {
34
+ readonly replay: ReplayApi;
35
+ readonly auditDb?: AuditDb;
36
+ readonly now?: () => number;
37
+ }
38
+ /**
39
+ * @stable
40
+ */
41
+ declare function createReplayRoutes(deps: ReplayRoutesDeps): Hono<{
42
+ Variables: ServerVariables;
43
+ }>;
44
+ //#endregion
45
+ export { ReplayApi, ReplayMode, ReplayResponse, ReplayRoutesDeps, createReplayRoutes };
46
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","names":[],"sources":["../../src/replay/routes.ts"],"sourcesContent":[],"mappings":";;;;;;;KA+BY,UAAA;;UAGK,SAAA;;;mBAGE;;;MAGb,QAAQ;;;mBAGK;;;MAGb,QAAQ;;;UAIG,cAAA;mBACE;;;;;;;UAoBF,gBAAA;mBACE;qBACE;;;;;;iBAOL,kBAAA,OAAyB,mBAAmB;aAAkB"}
@@ -0,0 +1,189 @@
1
+ import { appendAudit } from "@graphorin/security/audit";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { parseScope, scopeMatches } from "@graphorin/security/auth";
5
+
6
+ //#region src/replay/routes.ts
7
+ /**
8
+ * Replay endpoints with scope enforcement.
9
+ *
10
+ * POST /v1/runs/:runId/replay
11
+ * POST /v1/sessions/:id/replay
12
+ *
13
+ * Scope rules:
14
+ * - `raw: false` (default) → require `traces:read:sanitized`.
15
+ * - `raw: true` → require `traces:read:raw` (admin).
16
+ *
17
+ * Every successful invocation appends a `trace.replay.accessed` audit
18
+ * entry. The audit write is best-effort: a missing `AuditDb` does not
19
+ * block the replay.
20
+ *
21
+ * The actual replay stream is consumer-supplied — the framework
22
+ * accepts a `ReplayApi` from the operator that knows how to load
23
+ * trace events for a `runId` / `sessionId`. Phase 14c only owns the
24
+ * scope check + audit + transport.
25
+ *
26
+ * @packageDocumentation
27
+ */
28
+ const SCOPE_SANITIZED = parseScope("traces:read:sanitized");
29
+ const SCOPE_RAW = parseScope("traces:read:raw");
30
+ const ReplayRequestSchema = z.object({
31
+ raw: z.boolean().optional(),
32
+ fromMessageId: z.string().min(1).optional(),
33
+ provider: z.string().min(1).optional()
34
+ }).strict().default({});
35
+ /**
36
+ * @stable
37
+ */
38
+ function createReplayRoutes(deps) {
39
+ const app = new Hono();
40
+ const now = deps.now ?? Date.now;
41
+ app.post("/runs/:runId/replay", async (c) => {
42
+ const runId = c.req.param("runId");
43
+ const body = ReplayRequestSchema.safeParse(await safelyParseJson(c));
44
+ if (!body.success) return c.json(buildValidationError("Invalid replay body.", body.error.issues), 400);
45
+ const mode = body.data.raw === true ? "raw" : "sanitized";
46
+ const auth = c.get("state").auth;
47
+ const scopeError = enforceReplayScope(auth, mode);
48
+ if (scopeError !== void 0) {
49
+ await maybeAppendAudit(deps.auditDb, {
50
+ actor: auditActor(auth),
51
+ action: "replay:skipped",
52
+ target: `run:${runId}`,
53
+ decision: "denied",
54
+ ts: now(),
55
+ metadata: {
56
+ mode,
57
+ reason: scopeError.code
58
+ }
59
+ });
60
+ return c.json(scopeError.body, scopeError.status);
61
+ }
62
+ const replay = await deps.replay.loadRunReplay({
63
+ runId,
64
+ mode,
65
+ ...body.data.fromMessageId !== void 0 ? { fromMessageId: body.data.fromMessageId } : {},
66
+ ...body.data.provider !== void 0 ? { provider: body.data.provider } : {}
67
+ });
68
+ await maybeAppendAudit(deps.auditDb, {
69
+ actor: auditActor(auth),
70
+ action: "replay:accessed",
71
+ target: `run:${runId}`,
72
+ decision: "success",
73
+ ts: now(),
74
+ metadata: {
75
+ mode,
76
+ eventCount: replay.events.length
77
+ }
78
+ });
79
+ return c.json(serializeReplay(replay, mode));
80
+ });
81
+ app.post("/sessions/:id/replay", async (c) => {
82
+ const sessionId = c.req.param("id");
83
+ const body = ReplayRequestSchema.safeParse(await safelyParseJson(c));
84
+ if (!body.success) return c.json(buildValidationError("Invalid replay body.", body.error.issues), 400);
85
+ const mode = body.data.raw === true ? "raw" : "sanitized";
86
+ const auth = c.get("state").auth;
87
+ const scopeError = enforceReplayScope(auth, mode);
88
+ if (scopeError !== void 0) {
89
+ await maybeAppendAudit(deps.auditDb, {
90
+ actor: auditActor(auth),
91
+ action: "replay:skipped",
92
+ target: `session:${sessionId}`,
93
+ decision: "denied",
94
+ ts: now(),
95
+ metadata: {
96
+ mode,
97
+ reason: scopeError.code
98
+ }
99
+ });
100
+ return c.json(scopeError.body, scopeError.status);
101
+ }
102
+ const replay = await deps.replay.loadSessionReplay({
103
+ sessionId,
104
+ mode,
105
+ ...body.data.fromMessageId !== void 0 ? { fromMessageId: body.data.fromMessageId } : {},
106
+ ...body.data.provider !== void 0 ? { provider: body.data.provider } : {}
107
+ });
108
+ await maybeAppendAudit(deps.auditDb, {
109
+ actor: auditActor(auth),
110
+ action: "replay:accessed",
111
+ target: `session:${sessionId}`,
112
+ decision: "success",
113
+ ts: now(),
114
+ metadata: {
115
+ mode,
116
+ eventCount: replay.events.length
117
+ }
118
+ });
119
+ return c.json(serializeReplay(replay, mode));
120
+ });
121
+ return app;
122
+ }
123
+ function enforceReplayScope(auth, mode) {
124
+ if (auth.kind === "unauthenticated") return {
125
+ code: "auth-required",
126
+ status: 401,
127
+ body: {
128
+ error: "auth-required",
129
+ message: "Authentication required for replay endpoints."
130
+ }
131
+ };
132
+ const required = mode === "raw" ? SCOPE_RAW : SCOPE_SANITIZED;
133
+ for (const scope of auth.grantedScopes) if (scopeMatches(scope, required)) return void 0;
134
+ return {
135
+ code: "scope-denied",
136
+ status: 403,
137
+ body: {
138
+ error: "scope-denied",
139
+ message: `Token lacks required scope '${required.raw}'.`,
140
+ hint: `Mint a token with the '${required.raw}' (or admin:*) scope.`
141
+ }
142
+ };
143
+ }
144
+ function auditActor(auth) {
145
+ if (auth.kind === "token") return {
146
+ kind: "token",
147
+ id: auth.token.tokenId,
148
+ ...auth.token.label !== void 0 ? { label: auth.token.label } : {}
149
+ };
150
+ return {
151
+ kind: "system",
152
+ id: auth.kind === "anonymous" ? "anonymous" : "unauthenticated"
153
+ };
154
+ }
155
+ async function maybeAppendAudit(auditDb, entry) {
156
+ if (auditDb === void 0) return;
157
+ try {
158
+ await appendAudit(auditDb, entry);
159
+ } catch {}
160
+ }
161
+ function serializeReplay(replay, mode) {
162
+ return {
163
+ mode,
164
+ events: [...replay.events],
165
+ ...replay.truncated === true ? { truncated: true } : {},
166
+ ...replay.nextCursor !== void 0 ? { nextCursor: replay.nextCursor } : {}
167
+ };
168
+ }
169
+ function buildValidationError(message, issues) {
170
+ return {
171
+ error: "config-invalid",
172
+ message,
173
+ issues: issues.map((i) => ({
174
+ path: i.path,
175
+ message: i.message
176
+ }))
177
+ };
178
+ }
179
+ async function safelyParseJson(c) {
180
+ try {
181
+ return await c.req.json();
182
+ } catch {
183
+ return {};
184
+ }
185
+ }
186
+
187
+ //#endregion
188
+ export { createReplayRoutes };
189
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","names":["SCOPE_SANITIZED: ParsedScope","SCOPE_RAW: ParsedScope","mode: ReplayMode"],"sources":["../../src/replay/routes.ts"],"sourcesContent":["/**\n * Replay endpoints with scope enforcement.\n *\n * POST /v1/runs/:runId/replay\n * POST /v1/sessions/:id/replay\n *\n * Scope rules:\n * - `raw: false` (default) → require `traces:read:sanitized`.\n * - `raw: true` → require `traces:read:raw` (admin).\n *\n * Every successful invocation appends a `trace.replay.accessed` audit\n * entry. The audit write is best-effort: a missing `AuditDb` does not\n * block the replay.\n *\n * The actual replay stream is consumer-supplied — the framework\n * accepts a `ReplayApi` from the operator that knows how to load\n * trace events for a `runId` / `sessionId`. Phase 14c only owns the\n * scope check + audit + transport.\n *\n * @packageDocumentation\n */\n\nimport { type AuditDb, appendAudit } from '@graphorin/security/audit';\nimport { type ParsedScope, parseScope, scopeMatches } from '@graphorin/security/auth';\nimport { Hono } from 'hono';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { z } from 'zod';\n\nimport type { AuthState, ServerVariables } from '../internal/context.js';\n\n/** @stable */\nexport type ReplayMode = 'sanitized' | 'raw';\n\n/** @stable */\nexport interface ReplayApi {\n loadRunReplay(input: {\n readonly runId: string;\n readonly mode: ReplayMode;\n readonly fromMessageId?: string;\n readonly provider?: string;\n }): Promise<ReplayResponse>;\n loadSessionReplay(input: {\n readonly sessionId: string;\n readonly mode: ReplayMode;\n readonly fromMessageId?: string;\n readonly provider?: string;\n }): Promise<ReplayResponse>;\n}\n\n/** @stable */\nexport interface ReplayResponse {\n readonly events: ReadonlyArray<unknown>;\n readonly truncated?: boolean;\n readonly nextCursor?: string;\n}\n\nconst SCOPE_SANITIZED: ParsedScope = parseScope('traces:read:sanitized');\nconst SCOPE_RAW: ParsedScope = parseScope('traces:read:raw');\n\nconst ReplayRequestSchema = z\n .object({\n raw: z.boolean().optional(),\n fromMessageId: z.string().min(1).optional(),\n provider: z.string().min(1).optional(),\n })\n .strict()\n .default({});\n\n/**\n * @stable\n */\nexport interface ReplayRoutesDeps {\n readonly replay: ReplayApi;\n readonly auditDb?: AuditDb;\n readonly now?: () => number;\n}\n\n/**\n * @stable\n */\nexport function createReplayRoutes(deps: ReplayRoutesDeps): Hono<{ Variables: ServerVariables }> {\n const app = new Hono<{ Variables: ServerVariables }>();\n const now = deps.now ?? Date.now;\n\n app.post('/runs/:runId/replay', async (c) => {\n const runId = c.req.param('runId');\n const body = ReplayRequestSchema.safeParse(await safelyParseJson(c));\n if (!body.success) {\n return c.json(buildValidationError('Invalid replay body.', body.error.issues), 400);\n }\n const mode: ReplayMode = body.data.raw === true ? 'raw' : 'sanitized';\n const auth = c.get('state').auth;\n const scopeError = enforceReplayScope(auth, mode);\n if (scopeError !== undefined) {\n await maybeAppendAudit(deps.auditDb, {\n actor: auditActor(auth),\n action: 'replay:skipped',\n target: `run:${runId}`,\n decision: 'denied',\n ts: now(),\n metadata: { mode, reason: scopeError.code },\n });\n return c.json(scopeError.body, scopeError.status);\n }\n const replay = await deps.replay.loadRunReplay({\n runId,\n mode,\n ...(body.data.fromMessageId !== undefined ? { fromMessageId: body.data.fromMessageId } : {}),\n ...(body.data.provider !== undefined ? { provider: body.data.provider } : {}),\n });\n await maybeAppendAudit(deps.auditDb, {\n actor: auditActor(auth),\n action: 'replay:accessed',\n target: `run:${runId}`,\n decision: 'success',\n ts: now(),\n metadata: { mode, eventCount: replay.events.length },\n });\n return c.json(serializeReplay(replay, mode));\n });\n\n app.post('/sessions/:id/replay', async (c) => {\n const sessionId = c.req.param('id');\n const body = ReplayRequestSchema.safeParse(await safelyParseJson(c));\n if (!body.success) {\n return c.json(buildValidationError('Invalid replay body.', body.error.issues), 400);\n }\n const mode: ReplayMode = body.data.raw === true ? 'raw' : 'sanitized';\n const auth = c.get('state').auth;\n const scopeError = enforceReplayScope(auth, mode);\n if (scopeError !== undefined) {\n await maybeAppendAudit(deps.auditDb, {\n actor: auditActor(auth),\n action: 'replay:skipped',\n target: `session:${sessionId}`,\n decision: 'denied',\n ts: now(),\n metadata: { mode, reason: scopeError.code },\n });\n return c.json(scopeError.body, scopeError.status);\n }\n const replay = await deps.replay.loadSessionReplay({\n sessionId,\n mode,\n ...(body.data.fromMessageId !== undefined ? { fromMessageId: body.data.fromMessageId } : {}),\n ...(body.data.provider !== undefined ? { provider: body.data.provider } : {}),\n });\n await maybeAppendAudit(deps.auditDb, {\n actor: auditActor(auth),\n action: 'replay:accessed',\n target: `session:${sessionId}`,\n decision: 'success',\n ts: now(),\n metadata: { mode, eventCount: replay.events.length },\n });\n return c.json(serializeReplay(replay, mode));\n });\n\n return app;\n}\n\ninterface ScopeRefusal {\n readonly code: 'auth-required' | 'scope-denied';\n readonly status: ContentfulStatusCode;\n readonly body: { readonly error: string; readonly message: string; readonly hint?: string };\n}\n\nfunction enforceReplayScope(auth: AuthState, mode: ReplayMode): ScopeRefusal | undefined {\n // IP-13: both a verified token and the no-auth anonymous principal carry a\n // `grantedScopes` set; only a genuinely unauthenticated request is refused.\n if (auth.kind === 'unauthenticated') {\n return {\n code: 'auth-required',\n status: 401,\n body: {\n error: 'auth-required',\n message: 'Authentication required for replay endpoints.',\n },\n };\n }\n const required = mode === 'raw' ? SCOPE_RAW : SCOPE_SANITIZED;\n for (const scope of auth.grantedScopes) {\n if (scopeMatches(scope, required)) return undefined;\n }\n return {\n code: 'scope-denied',\n status: 403,\n body: {\n error: 'scope-denied',\n message: `Token lacks required scope '${required.raw}'.`,\n hint: `Mint a token with the '${required.raw}' (or admin:*) scope.`,\n },\n };\n}\n\nfunction auditActor(auth: AuthState): {\n readonly kind: 'token' | 'system';\n readonly id: string;\n readonly label?: string;\n} {\n if (auth.kind === 'token') {\n return {\n kind: 'token',\n id: auth.token.tokenId,\n ...(auth.token.label !== undefined ? { label: auth.token.label } : {}),\n };\n }\n // IP-13: attribute no-auth replays to the synthetic 'anonymous' principal.\n return { kind: 'system', id: auth.kind === 'anonymous' ? 'anonymous' : 'unauthenticated' };\n}\n\nasync function maybeAppendAudit(\n auditDb: AuditDb | undefined,\n entry: Parameters<typeof appendAudit>[1],\n): Promise<void> {\n if (auditDb === undefined) return;\n try {\n await appendAudit(auditDb, entry);\n } catch {\n // Best-effort — replay must not be blocked by audit failures.\n }\n}\n\nfunction serializeReplay(replay: ReplayResponse, mode: ReplayMode): Record<string, unknown> {\n return {\n mode,\n events: [...replay.events],\n ...(replay.truncated === true ? { truncated: true } : {}),\n ...(replay.nextCursor !== undefined ? { nextCursor: replay.nextCursor } : {}),\n };\n}\n\nfunction buildValidationError(\n message: string,\n issues: ReadonlyArray<{\n readonly path: ReadonlyArray<string | number>;\n readonly message: string;\n }>,\n): Record<string, unknown> {\n return {\n error: 'config-invalid',\n message,\n issues: issues.map((i) => ({ path: i.path, message: i.message })),\n };\n}\n\nasync function safelyParseJson(c: {\n readonly req: { readonly json: () => Promise<unknown> };\n}): 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,41 @@
1
+ import { ServerVariables } from "../internal/context.js";
2
+ import { AgentRegistry } 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/agents.d.ts
8
+
9
+ /**
10
+ * @stable
11
+ */
12
+ interface AgentRoutesDeps {
13
+ readonly agents: AgentRegistry;
14
+ readonly runs: RunStateTracker;
15
+ readonly newRunId?: () => string;
16
+ /**
17
+ * Streaming dispatcher (IP-2). When present, `POST /:id/stream`
18
+ * actually runs the agent and emits every event onto the
19
+ * `agent:<id>/runs/<runId>/events` subject.
20
+ */
21
+ readonly dispatcher?: WsDispatcher;
22
+ }
23
+ /**
24
+ * @stable
25
+ */
26
+ declare function createAgentRoutes(deps: AgentRoutesDeps): Hono<{
27
+ Variables: ServerVariables;
28
+ }>;
29
+ /**
30
+ * Companion router for the `/runs/...` surface. Kept separate so the
31
+ * `createServer` factory can mount it under the top-level base path
32
+ * rather than under `/agents`.
33
+ *
34
+ * @stable
35
+ */
36
+ declare function createRunRoutes(deps: AgentRoutesDeps): Hono<{
37
+ Variables: ServerVariables;
38
+ }>;
39
+ //#endregion
40
+ export { AgentRoutesDeps, createAgentRoutes, createRunRoutes };
41
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","names":[],"sources":["../../src/routes/agents.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;UA8BiB,eAAA;mBACE;iBACF;;;;;;;wBAAe;;;;;iBAsBhB,iBAAA,OAAwB,kBAAkB;aAAkB;;;;;;;;;iBA+L5D,eAAA,OAAsB,kBAAkB;aAAkB"}