@arnilo/prism-server 0.0.22 → 0.0.24

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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.24] - 2026-08-04
4
+
5
+ ### Added
6
+ - Durable `AgentEventSource` (memory + PostgreSQL LISTEN/NOTIFY), recoverable `ToolEffectStore`, and AG-UI MCP/MCP Apps/A2A fronting for Phase 7.
7
+
8
+ ### Changed
9
+ - Publishable graph remains **47** manifests at **0.0.24**; peers and lockfile move together.
10
+
11
+ See [migration guide](../../docs/migration.md) for the 0.0.23 → 0.0.24 notes.
12
+
13
+ ## [0.0.23] - 2026-08-03
14
+
15
+ ### Changed
16
+ - Released with exact 0.0.23 graph.
17
+
3
18
  ## [0.0.22] - 2026-07-31
4
19
 
5
20
  ### Changed
@@ -10,14 +25,11 @@
10
25
  ### Changed
11
26
  - Released with exact 0.0.21 graph.
12
27
 
13
-
14
-
15
28
  ## [0.0.20] - 2026-07-31
16
29
 
17
30
  ### Changed
18
31
  - Released with exact 0.0.20 graph.
19
32
 
20
-
21
33
  ## [0.0.19] - 2026-07-30
22
34
 
23
35
  ### Changed
package/README.md CHANGED
@@ -24,7 +24,7 @@ const response = await handler(new Request("https://api.example.test/prism/agent
24
24
  }));
25
25
  ```
26
26
 
27
- Routes: direct/SSE agent run, explicitly selected durable agent status/resume through `agentRuns`, direct/SSE workflow run, durable workflow enqueue/status/cancel/resume/replay, and optional ownership-scoped schedule create/list/pause/resume/trigger/delete. `agentRuns` uses core `createAgentRunLifecycle()`; no lifecycle route exists by default. All bodies, responses, events, queues, concurrency, and timeouts are bounded.
27
+ Routes: direct/SSE agent run, cross-replica durable event reconnect through object agent exposures with `events` + `resolveRun`, explicitly selected durable agent status/resume through `agentRuns`, direct/SSE workflow run, durable workflow enqueue/status/cancel/resume/replay, and optional ownership-scoped schedule create/list/pause/resume/trigger/delete. `agentRuns` uses core `createAgentRunLifecycle()`; no lifecycle route exists by default. All bodies, responses, events, queues, concurrency, and timeouts are bounded.
28
28
 
29
29
  Optional 0.0.14 co-work handlers (mount beside the API handler): `createConversationHandler` (durable user-scoped conversation threads with reconnectable redacted replay — `createConversationService`) and `createArtifactHandler` (artifact revisions, approve/reject review, expiring authorized delivery links — `createArtifactService` over the existing checkpoint store). Both are ownership-scoped and fail closed without authorization.
30
30
 
package/dist/handler.js CHANGED
@@ -28,7 +28,7 @@ export function createPrismHandler(options) {
28
28
  headers: {
29
29
  "access-control-allow-origin": origin,
30
30
  "access-control-allow-methods": "GET, POST, DELETE, OPTIONS",
31
- "access-control-allow-headers": "content-type, authorization",
31
+ "access-control-allow-headers": "content-type, authorization, last-event-id",
32
32
  vary: "origin",
33
33
  },
34
34
  }));
@@ -111,6 +111,35 @@ export function createPrismHandler(options) {
111
111
  owned.dispose();
112
112
  }
113
113
  }
114
+ if (route.kind === "agent-events") {
115
+ const exposure = options.agents?.[route.capabilityId];
116
+ if (!exposure || !("sessionFactory" in exposure) || !exposure.events || !exposure.resolveRun) {
117
+ throw new PrismServerError("Not found", 404, "ERR_PRISM_SERVER_NOT_FOUND");
118
+ }
119
+ if (!authorization.ownership.tenantId)
120
+ throw new PrismServerError("Forbidden", 403, "ERR_PRISM_SERVER_FORBIDDEN");
121
+ acquire();
122
+ const owned = ownedSignal(request, limits.requestTimeoutMs, options.disconnectAborts ?? true);
123
+ try {
124
+ const run = await awaitWithSignal(Promise.resolve(exposure.resolveRun({ runId: route.runId, authorization, signal: owned.signal })), owned.signal);
125
+ if (!run?.sessionId)
126
+ throw new PrismServerError("Not found", 404, "ERR_PRISM_SERVER_NOT_FOUND");
127
+ const after = replayCursor(request, limits.maxReplayCursorBytes);
128
+ const events = exposure.events.subscribe({
129
+ ownership: authorization.ownership,
130
+ sessionId: run.sessionId,
131
+ runId: run.runId,
132
+ after,
133
+ signal: owned.signal,
134
+ });
135
+ return respond(sseAgentEvents(events, owned, limits, options, release));
136
+ }
137
+ catch (error) {
138
+ owned.dispose();
139
+ release();
140
+ throw error;
141
+ }
142
+ }
114
143
  if (route.kind === "agent-status" || route.kind === "agent-resume") {
115
144
  const exposure = options.agentRuns?.[route.capabilityId];
116
145
  if (!exposure)
@@ -378,6 +407,8 @@ function parseRoute(request, base) {
378
407
  return { kind: "agent-status", operation: "agent.status", capabilityId: id, runId };
379
408
  if (parts.length === 5 && action === "resume" && request.method === "POST")
380
409
  return { kind: "agent-resume", operation: "agent.resume", capabilityId: id, runId };
410
+ if (parts.length === 5 && action === "events" && request.method === "GET")
411
+ return { kind: "agent-events", operation: "agent.events", capabilityId: id, runId };
381
412
  }
382
413
  if (group !== "workflows")
383
414
  return undefined;
@@ -592,6 +623,18 @@ function readOptionalId(value, name) {
592
623
  function validId(value) {
593
624
  return value.length <= 128 && /^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value);
594
625
  }
626
+ function replayCursor(request, maxBytes) {
627
+ const query = new URL(request.url).searchParams.get("cursor") ?? undefined;
628
+ const header = request.headers.get("last-event-id") ?? undefined;
629
+ if (query !== undefined && header !== undefined && query !== header) {
630
+ throw new PrismServerError("Conflicting event cursors", 400, "ERR_PRISM_SERVER_REPLAY_CURSOR");
631
+ }
632
+ const cursor = header ?? query;
633
+ if (cursor !== undefined && (Buffer.byteLength(cursor, "utf8") > maxBytes || /\r|\n|\0/.test(cursor))) {
634
+ throw new PrismServerError("Invalid event cursor", 400, "ERR_PRISM_SERVER_REPLAY_CURSOR");
635
+ }
636
+ return cursor;
637
+ }
595
638
  function normalizeBasePath(value) {
596
639
  if (!value.startsWith("/") || value.includes("?") || value.includes("#"))
597
640
  throw new RangeError("basePath must be an absolute URL path");
@@ -638,7 +681,19 @@ function ownedSignal(request, timeoutMs, disconnectAborts) {
638
681
  },
639
682
  };
640
683
  }
684
+ function sseAgentEvents(source, owned, limits, options, release) {
685
+ return sseStream(source, ({ record, cursor }) => {
686
+ if (/\r|\n|\0/.test(cursor) || Buffer.byteLength(cursor, "utf8") > limits.maxReplayCursorBytes) {
687
+ throw new PrismServerError("Invalid event cursor", 500, "ERR_PRISM_SERVER_REPLAY_CURSOR");
688
+ }
689
+ const safe = options.redactor?.redact(record.event) ?? record.event;
690
+ return `id: ${cursor}\ndata: ${JSON.stringify(safe)}\n\n`;
691
+ }, owned, limits, release);
692
+ }
641
693
  function sse(source, owned, limits, options, release) {
694
+ return sseStream(source, (value) => `data: ${JSON.stringify(options.redactor?.redact(value) ?? value)}\n\n`, owned, limits, release);
695
+ }
696
+ function sseStream(source, serialize, owned, limits, release) {
642
697
  const iterator = source[Symbol.asyncIterator]();
643
698
  const encoder = new TextEncoder();
644
699
  let events = 0;
@@ -667,8 +722,7 @@ function sse(source, owned, limits, options, release) {
667
722
  controller.close();
668
723
  return;
669
724
  }
670
- const safe = options.redactor?.redact(next.value) ?? next.value;
671
- const chunk = encoder.encode(`data: ${JSON.stringify(safe)}\n\n`);
725
+ const chunk = encoder.encode(serialize(next.value));
672
726
  events += 1;
673
727
  bytes += chunk.byteLength;
674
728
  if (chunk.byteLength > limits.maxEventBytes || events > limits.maxStreamEvents || bytes > limits.maxStreamBytes) {
package/dist/index.d.ts CHANGED
@@ -13,8 +13,8 @@ export type { PrismDeploymentLimits, PrismServerLimits, ResolvedPrismDeploymentL
13
13
  export { DEFAULT_DRAIN_DEADLINE_MS, DEFAULT_MAX_CONCURRENT_RUNS, DEFAULT_MAX_EVENT_BYTES, DEFAULT_MAX_HEALTH_BYTES, DEFAULT_MAX_QUEUED_EVENTS, DEFAULT_MAX_REPLAY_CURSOR_BYTES, DEFAULT_MAX_REPLAY_EVENTS, DEFAULT_MAX_REQUEST_BYTES, DEFAULT_MAX_RESPONSE_BYTES, DEFAULT_MAX_STREAM_BYTES, DEFAULT_MAX_STREAM_EVENTS, DEFAULT_REQUEST_TIMEOUT_MS, HARD_DRAIN_DEADLINE_MS, HARD_MAX_CONCURRENT_RUNS, HARD_MAX_EVENT_BYTES, HARD_MAX_HEALTH_BYTES, HARD_MAX_QUEUED_EVENTS, HARD_MAX_REPLAY_CURSOR_BYTES, HARD_MAX_REPLAY_EVENTS, HARD_MAX_REQUEST_BYTES, HARD_MAX_RESPONSE_BYTES, HARD_MAX_STREAM_BYTES, HARD_MAX_STREAM_EVENTS, HARD_REQUEST_TIMEOUT_MS, resolvePrismDeploymentLimits, resolvePrismServerLimits, } from "./limits.js";
14
14
  export type { MemoryRateLimiterOptions, PrismServerRateLimitDenial, PrismServerRateLimiter, PrismServerRateLimitInput, } from "./rate-limit.js";
15
15
  export { createMemoryRateLimiter } from "./rate-limit.js";
16
- export type { CreatePrismEventReplayOptions, CreatePrismReplayHandlerOptions, PrismEventReplay, PrismEventReplayRequest, } from "./replay.js";
17
- export { createPrismEventReplay, createPrismReplayHandler } from "./replay.js";
18
- export type { CreatePrismHandlerOptions, PrismAgentExposure, PrismAgentRunExposure, PrismRequestHandler, PrismScheduleExposure, PrismServerAuthorization, PrismServerAuthorizationInput, PrismServerAuthorizer, PrismServerOperation, PrismWorkflowExposure, } from "./types.js";
16
+ export type { CreatePrismEventReplayOptions, CreatePrismReplayHandlerOptions, PrismAgentEventReplay, PrismEventReplay, PrismEventReplayRequest, } from "./replay.js";
17
+ export { createPrismAgentEventReplay, createPrismEventReplay, createPrismReplayHandler } from "./replay.js";
18
+ export type { CreatePrismHandlerOptions, PrismAgentEventResolutionInput, PrismAgentExposure, PrismAgentRunExposure, PrismRequestHandler, PrismScheduleExposure, PrismServerAuthorization, PrismServerAuthorizationInput, PrismServerAuthorizer, PrismServerOperation, PrismWorkflowExposure, } from "./types.js";
19
19
  export { PrismServerError } from "./types.js";
20
20
  export declare const packageName = "@arnilo/prism-server";
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ export { createPrismHandler } from "./handler.js";
6
6
  export { createPrismHealthHandler } from "./health.js";
7
7
  export { DEFAULT_DRAIN_DEADLINE_MS, DEFAULT_MAX_CONCURRENT_RUNS, DEFAULT_MAX_EVENT_BYTES, DEFAULT_MAX_HEALTH_BYTES, DEFAULT_MAX_QUEUED_EVENTS, DEFAULT_MAX_REPLAY_CURSOR_BYTES, DEFAULT_MAX_REPLAY_EVENTS, DEFAULT_MAX_REQUEST_BYTES, DEFAULT_MAX_RESPONSE_BYTES, DEFAULT_MAX_STREAM_BYTES, DEFAULT_MAX_STREAM_EVENTS, DEFAULT_REQUEST_TIMEOUT_MS, HARD_DRAIN_DEADLINE_MS, HARD_MAX_CONCURRENT_RUNS, HARD_MAX_EVENT_BYTES, HARD_MAX_HEALTH_BYTES, HARD_MAX_QUEUED_EVENTS, HARD_MAX_REPLAY_CURSOR_BYTES, HARD_MAX_REPLAY_EVENTS, HARD_MAX_REQUEST_BYTES, HARD_MAX_RESPONSE_BYTES, HARD_MAX_STREAM_BYTES, HARD_MAX_STREAM_EVENTS, HARD_REQUEST_TIMEOUT_MS, resolvePrismDeploymentLimits, resolvePrismServerLimits, } from "./limits.js";
8
8
  export { createMemoryRateLimiter } from "./rate-limit.js";
9
- export { createPrismEventReplay, createPrismReplayHandler } from "./replay.js";
9
+ export { createPrismAgentEventReplay, createPrismEventReplay, createPrismReplayHandler } from "./replay.js";
10
10
  export { PrismServerError } from "./types.js";
11
11
  export const packageName = "@arnilo/prism-server";
12
12
  //# sourceMappingURL=index.js.map
package/dist/limits.d.ts CHANGED
@@ -31,6 +31,7 @@ export interface PrismServerLimits {
31
31
  readonly maxStreamEvents?: number;
32
32
  readonly maxConcurrentRuns?: number;
33
33
  readonly maxQueuedEvents?: number;
34
+ readonly maxReplayCursorBytes?: number;
34
35
  readonly requestTimeoutMs?: number;
35
36
  }
36
37
  export interface ResolvedPrismServerLimits {
@@ -41,6 +42,7 @@ export interface ResolvedPrismServerLimits {
41
42
  readonly maxStreamEvents: number;
42
43
  readonly maxConcurrentRuns: number;
43
44
  readonly maxQueuedEvents: number;
45
+ readonly maxReplayCursorBytes: number;
44
46
  readonly requestTimeoutMs: number;
45
47
  }
46
48
  export interface PrismDeploymentLimits {
package/dist/limits.js CHANGED
@@ -32,6 +32,7 @@ export function resolvePrismServerLimits(input = {}) {
32
32
  maxStreamEvents: bounded(input.maxStreamEvents, DEFAULT_MAX_STREAM_EVENTS, HARD_MAX_STREAM_EVENTS, "maxStreamEvents"),
33
33
  maxConcurrentRuns: bounded(input.maxConcurrentRuns, DEFAULT_MAX_CONCURRENT_RUNS, HARD_MAX_CONCURRENT_RUNS, "maxConcurrentRuns"),
34
34
  maxQueuedEvents: bounded(input.maxQueuedEvents, DEFAULT_MAX_QUEUED_EVENTS, HARD_MAX_QUEUED_EVENTS, "maxQueuedEvents"),
35
+ maxReplayCursorBytes: bounded(input.maxReplayCursorBytes, DEFAULT_MAX_REPLAY_CURSOR_BYTES, HARD_MAX_REPLAY_CURSOR_BYTES, "maxReplayCursorBytes"),
35
36
  requestTimeoutMs: bounded(input.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS, HARD_REQUEST_TIMEOUT_MS, "requestTimeoutMs"),
36
37
  };
37
38
  }
package/dist/replay.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AgentEventRecord, OwnershipScope, PersistencePage, ProductionPersistenceStore } from "@arnilo/prism";
1
+ import type { AgentEventEnvelope, AgentEventRecord, AgentEventSource, AgentEventSourcePage, OwnershipScope, PersistencePage, ProductionPersistenceStore } from "@arnilo/prism";
2
2
  import { type PrismDeploymentLimits } from "./limits.js";
3
3
  export interface PrismEventReplayRequest {
4
4
  readonly ownership: OwnershipScope;
@@ -13,7 +13,13 @@ export interface PrismEventReplay {
13
13
  export interface CreatePrismEventReplayOptions {
14
14
  readonly limits?: PrismDeploymentLimits;
15
15
  }
16
- /** Ownership-scoped, cursor-paginated durable event replay. Does not re-run work. */
16
+ export interface PrismAgentEventReplay {
17
+ page(input: PrismEventReplayRequest): Promise<AgentEventSourcePage>;
18
+ subscribe(input: PrismEventReplayRequest): AsyncIterable<AgentEventEnvelope>;
19
+ }
20
+ /** Shared source-backed page/follow adapter. Authorization supplies ownership before each call. */
21
+ export declare function createPrismAgentEventReplay(source: AgentEventSource, options?: CreatePrismEventReplayOptions): PrismAgentEventReplay;
22
+ /** Ownership-scoped, cursor-paginated legacy persistence replay. Does not re-run work. */
17
23
  export declare function createPrismEventReplay(store: Pick<ProductionPersistenceStore, "queryEvents">, options?: CreatePrismEventReplayOptions): PrismEventReplay;
18
24
  export interface CreatePrismReplayHandlerOptions {
19
25
  readonly replay: PrismEventReplay;
package/dist/replay.js CHANGED
@@ -1,6 +1,34 @@
1
1
  import { resolvePrismDeploymentLimits } from "./limits.js";
2
2
  import { PrismServerError } from "./types.js";
3
- /** Ownership-scoped, cursor-paginated durable event replay. Does not re-run work. */
3
+ /** Shared source-backed page/follow adapter. Authorization supplies ownership before each call. */
4
+ export function createPrismAgentEventReplay(source, options = {}) {
5
+ const limits = resolvePrismDeploymentLimits(options.limits);
6
+ const read = (input) => {
7
+ input.signal?.throwIfAborted();
8
+ assertOwnership(input.ownership);
9
+ if (!input.sessionId || !input.runId)
10
+ throw new PrismServerError("Run is unavailable", 404, "ERR_PRISM_SERVER_REPLAY");
11
+ if (input.cursor !== undefined)
12
+ assertCursor(input.cursor, limits);
13
+ return {
14
+ ownership: input.ownership,
15
+ sessionId: input.sessionId,
16
+ runId: input.runId,
17
+ after: input.cursor,
18
+ limit: limits.maxReplayEvents,
19
+ signal: input.signal,
20
+ };
21
+ };
22
+ return {
23
+ page: (input) => source.page(read(input)),
24
+ subscribe(input) {
25
+ return {
26
+ [Symbol.asyncIterator]: () => source.subscribe(read(input))[Symbol.asyncIterator](),
27
+ };
28
+ },
29
+ };
30
+ }
31
+ /** Ownership-scoped, cursor-paginated legacy persistence replay. Does not re-run work. */
4
32
  export function createPrismEventReplay(store, options = {}) {
5
33
  const limits = resolvePrismDeploymentLimits(options.limits);
6
34
  return {
package/dist/types.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { Agent, AgentIdentity, AgentRunLifecycle, AgentSession, OwnershipScope, RunOptions, SecretRedactor } from "@arnilo/prism";
1
+ import type { Agent, AgentEventSource, AgentIdentity, AgentRunLifecycle, AgentRunRef, AgentSession, OwnershipScope, RunOptions, SecretRedactor } from "@arnilo/prism";
2
2
  import type { RunWorkflowOptions, WorkflowCheckpointAdapter, WorkflowDefinition, WorkflowSchedules } from "@arnilo/prism-workflows";
3
3
  import type { PrismDrainController } from "./drain.js";
4
4
  import type { PrismServerLimits } from "./limits.js";
5
5
  import type { PrismServerRateLimiter } from "./rate-limit.js";
6
- export type PrismServerOperation = "agent.run" | "agent.stream" | "agent.status" | "agent.resume" | "workflow.run" | "workflow.stream" | "workflow.status" | "workflow.cancel" | "workflow.resume" | "workflow.enqueue" | "workflow.replay" | "schedule.create" | "schedule.list" | "schedule.pause" | "schedule.resume" | "schedule.trigger" | "schedule.delete";
6
+ export type PrismServerOperation = "agent.run" | "agent.stream" | "agent.status" | "agent.resume" | "agent.events" | "workflow.run" | "workflow.stream" | "workflow.status" | "workflow.cancel" | "workflow.resume" | "workflow.enqueue" | "workflow.replay" | "schedule.create" | "schedule.list" | "schedule.pause" | "schedule.resume" | "schedule.trigger" | "schedule.delete";
7
7
  export interface PrismServerAuthorization {
8
8
  readonly ownership: OwnershipScope;
9
9
  /** Host-verified identity; when set must project onto `ownership` without widening. */
@@ -17,9 +17,18 @@ export interface PrismServerAuthorizationInput {
17
17
  readonly signal: AbortSignal;
18
18
  }
19
19
  export type PrismServerAuthorizer = (input: PrismServerAuthorizationInput) => false | PrismServerAuthorization | Promise<false | PrismServerAuthorization>;
20
+ export interface PrismAgentEventResolutionInput {
21
+ readonly runId: string;
22
+ readonly authorization: PrismServerAuthorization;
23
+ readonly signal: AbortSignal;
24
+ }
20
25
  export interface PrismAgentExposure {
21
26
  readonly sessionFactory: (authorization: PrismServerAuthorization) => AgentSession | Promise<AgentSession>;
22
27
  readonly runOptions?: Omit<RunOptions, "ownership" | "signal" | "redactor">;
28
+ /** Optional durable cross-replica event source. Requires resolveRun. */
29
+ readonly events?: AgentEventSource;
30
+ /** Resolves an authorized public run selector to exact internal session/run IDs. */
31
+ readonly resolveRun?: (input: PrismAgentEventResolutionInput) => AgentRunRef | undefined | Promise<AgentRunRef | undefined>;
23
32
  }
24
33
  /** Explicit durable status/resume capability. Omit it to expose no agent lifecycle routes. */
25
34
  export interface PrismAgentRunExposure {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism-server",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "Optional framework-free Web Request-to-Response handler for explicitly selected Prism agents and workflows.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,8 +25,8 @@
25
25
  "pack:dry-run": "npm pack --dry-run"
26
26
  },
27
27
  "peerDependencies": {
28
- "@arnilo/prism": "0.0.22",
29
- "@arnilo/prism-workflows": "0.0.22"
28
+ "@arnilo/prism": "0.0.24",
29
+ "@arnilo/prism-workflows": "0.0.24"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@arnilo/prism": "file:../..",