@arnilo/prism-server 0.0.23 → 0.0.25

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,11 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.25] - 2026-08-06
4
+
5
+ ### Added
6
+ - Agent resume accepts batch `decisions: RunDecision[]` (exclusive with legacy binary `decision`) with boundary validation.
7
+
8
+ ### Changed
9
+ - Released with exact 0.0.25 graph.
10
+
11
+ See [migration guide](../../docs/migration.md) for the 0.0.24 → 0.0.25 notes.
12
+
13
+ ## [0.0.24] - 2026-08-04
14
+
15
+ ### Added
16
+ - Durable `AgentEventSource` (memory + PostgreSQL LISTEN/NOTIFY), recoverable `ToolEffectStore`, and AG-UI MCP/MCP Apps/A2A fronting for Phase 7.
17
+
18
+ ### Changed
19
+ - Publishable graph remains **47** manifests at **0.0.24**; peers and lockfile move together.
20
+
21
+ See [migration guide](../../docs/migration.md) for the 0.0.23 → 0.0.24 notes.
22
+
3
23
  ## [0.0.23] - 2026-08-03
4
24
 
5
25
  ### Changed
6
26
  - Released with exact 0.0.23 graph.
7
27
 
8
-
9
28
  ## [0.0.22] - 2026-07-31
10
29
 
11
30
  ### Changed
@@ -16,14 +35,11 @@
16
35
  ### Changed
17
36
  - Released with exact 0.0.21 graph.
18
37
 
19
-
20
-
21
38
  ## [0.0.20] - 2026-07-31
22
39
 
23
40
  ### Changed
24
41
  - Released with exact 0.0.20 graph.
25
42
 
26
-
27
43
  ## [0.0.19] - 2026-07-30
28
44
 
29
45
  ### 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. Agent resume accepts legacy binary `decision` or batch `decisions: RunDecision[]` (exactly one). 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
@@ -1,4 +1,4 @@
1
- import { AgentRunStateError, assertIdentityActive, assertIdentityMatchesOwnership, } from "@arnilo/prism";
1
+ import { AgentRunStateError, assertIdentityActive, assertIdentityMatchesOwnership, HARD_MAX_DECISION_REASON_BYTES, HARD_MAX_ELICITATION_BYTES, HARD_MAX_PENDING_DECISIONS, } from "@arnilo/prism";
2
2
  import { cancelWorkflowRun, createWorkflowEventBus, enqueueWorkflow, getWorkflowRun, replayWorkflow, resumeWorkflow, runWorkflow, } from "@arnilo/prism-workflows";
3
3
  import { isAdmitOperation } from "./drain.js";
4
4
  import { resolvePrismServerLimits } from "./limits.js";
@@ -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;
@@ -527,17 +558,66 @@ function isMessage(value) {
527
558
  const item = value;
528
559
  return ["system", "user", "assistant", "tool"].includes(String(item.role)) && Array.isArray(item.content);
529
560
  }
561
+ const RUN_DECISION_OUTCOMES = new Set(["allow_once", "allow_for_run", "reject_once", "reject_for_run"]);
562
+ const RUN_DECISION_KEYS = new Set(["approvalId", "outcome", "reason", "modifiedArguments", "elicitation"]);
563
+ /** Boundary validation for a client-supplied decision batch; core re-validates under CAS. */
564
+ function readAgentDecisions(value) {
565
+ if (!Array.isArray(value) || value.length === 0 || value.length > HARD_MAX_PENDING_DECISIONS) {
566
+ throw new PrismServerError("decisions must be a non-empty bounded array", 400, "ERR_PRISM_SERVER_RESUME");
567
+ }
568
+ return value.map((entry) => {
569
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
570
+ throw new PrismServerError("decision entry must be an object", 400, "ERR_PRISM_SERVER_RESUME");
571
+ }
572
+ const row = entry;
573
+ if (Object.keys(row).some((key) => !RUN_DECISION_KEYS.has(key))) {
574
+ throw new PrismServerError("decision entry has unknown keys", 400, "ERR_PRISM_SERVER_RESUME");
575
+ }
576
+ if (typeof row.approvalId !== "string" || row.approvalId.length === 0 || row.approvalId.length > 128) {
577
+ throw new PrismServerError("decision approvalId is invalid", 400, "ERR_PRISM_SERVER_RESUME");
578
+ }
579
+ if (typeof row.outcome !== "string" || !RUN_DECISION_OUTCOMES.has(row.outcome)) {
580
+ throw new PrismServerError("decision outcome is invalid", 400, "ERR_PRISM_SERVER_RESUME");
581
+ }
582
+ if (row.reason !== undefined &&
583
+ (typeof row.reason !== "string" || Buffer.byteLength(row.reason, "utf8") > HARD_MAX_DECISION_REASON_BYTES)) {
584
+ throw new PrismServerError("decision reason exceeds limits", 400, "ERR_PRISM_SERVER_RESUME");
585
+ }
586
+ for (const key of ["modifiedArguments", "elicitation"]) {
587
+ const field = row[key];
588
+ if (field === undefined)
589
+ continue;
590
+ if (!field || typeof field !== "object" || Array.isArray(field)) {
591
+ throw new PrismServerError(`decision ${key} must be an object`, 400, "ERR_PRISM_SERVER_RESUME");
592
+ }
593
+ const text = JSON.stringify(field);
594
+ if (text === undefined || Buffer.byteLength(text, "utf8") > HARD_MAX_ELICITATION_BYTES) {
595
+ throw new PrismServerError(`decision ${key} exceeds limits`, 400, "ERR_PRISM_SERVER_RESUME");
596
+ }
597
+ }
598
+ return entry;
599
+ });
600
+ }
530
601
  function readAgentResume(body) {
531
- if (Object.keys(body).some((key) => key !== "decision" && key !== "expectedVersion")) {
602
+ if (Object.keys(body).some((key) => key !== "decision" && key !== "decisions" && key !== "expectedVersion")) {
532
603
  throw new PrismServerError("Invalid agent resume body", 400, "ERR_PRISM_SERVER_RESUME");
533
604
  }
534
- if (body.decision !== "approve" && body.decision !== "deny") {
535
- throw new PrismServerError("decision must be approve or deny", 400, "ERR_PRISM_SERVER_RESUME");
536
- }
537
605
  if (!Number.isSafeInteger(body.expectedVersion) || Number(body.expectedVersion) < 1) {
538
606
  throw new PrismServerError("expectedVersion must be a positive safe integer", 400, "ERR_PRISM_SERVER_RESUME");
539
607
  }
540
- return { decision: body.decision, expectedVersion: Number(body.expectedVersion) };
608
+ if (body.decision !== undefined && body.decisions !== undefined) {
609
+ throw new PrismServerError("provide exactly one of decision or decisions", 400, "ERR_PRISM_SERVER_RESUME");
610
+ }
611
+ if (body.decision !== undefined) {
612
+ if (body.decision !== "approve" && body.decision !== "deny") {
613
+ throw new PrismServerError("decision must be approve or deny", 400, "ERR_PRISM_SERVER_RESUME");
614
+ }
615
+ return { decision: body.decision, expectedVersion: Number(body.expectedVersion) };
616
+ }
617
+ if (body.decisions === undefined) {
618
+ throw new PrismServerError("provide decision or decisions", 400, "ERR_PRISM_SERVER_RESUME");
619
+ }
620
+ return { decisions: readAgentDecisions(body.decisions), expectedVersion: Number(body.expectedVersion) };
541
621
  }
542
622
  function readResume(body) {
543
623
  if (body.decision !== "approve" && body.decision !== "deny") {
@@ -592,6 +672,18 @@ function readOptionalId(value, name) {
592
672
  function validId(value) {
593
673
  return value.length <= 128 && /^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value);
594
674
  }
675
+ function replayCursor(request, maxBytes) {
676
+ const query = new URL(request.url).searchParams.get("cursor") ?? undefined;
677
+ const header = request.headers.get("last-event-id") ?? undefined;
678
+ if (query !== undefined && header !== undefined && query !== header) {
679
+ throw new PrismServerError("Conflicting event cursors", 400, "ERR_PRISM_SERVER_REPLAY_CURSOR");
680
+ }
681
+ const cursor = header ?? query;
682
+ if (cursor !== undefined && (Buffer.byteLength(cursor, "utf8") > maxBytes || /\r|\n|\0/.test(cursor))) {
683
+ throw new PrismServerError("Invalid event cursor", 400, "ERR_PRISM_SERVER_REPLAY_CURSOR");
684
+ }
685
+ return cursor;
686
+ }
595
687
  function normalizeBasePath(value) {
596
688
  if (!value.startsWith("/") || value.includes("?") || value.includes("#"))
597
689
  throw new RangeError("basePath must be an absolute URL path");
@@ -638,7 +730,19 @@ function ownedSignal(request, timeoutMs, disconnectAborts) {
638
730
  },
639
731
  };
640
732
  }
733
+ function sseAgentEvents(source, owned, limits, options, release) {
734
+ return sseStream(source, ({ record, cursor }) => {
735
+ if (/\r|\n|\0/.test(cursor) || Buffer.byteLength(cursor, "utf8") > limits.maxReplayCursorBytes) {
736
+ throw new PrismServerError("Invalid event cursor", 500, "ERR_PRISM_SERVER_REPLAY_CURSOR");
737
+ }
738
+ const safe = options.redactor?.redact(record.event) ?? record.event;
739
+ return `id: ${cursor}\ndata: ${JSON.stringify(safe)}\n\n`;
740
+ }, owned, limits, release);
741
+ }
641
742
  function sse(source, owned, limits, options, release) {
743
+ return sseStream(source, (value) => `data: ${JSON.stringify(options.redactor?.redact(value) ?? value)}\n\n`, owned, limits, release);
744
+ }
745
+ function sseStream(source, serialize, owned, limits, release) {
642
746
  const iterator = source[Symbol.asyncIterator]();
643
747
  const encoder = new TextEncoder();
644
748
  let events = 0;
@@ -667,8 +771,7 @@ function sse(source, owned, limits, options, release) {
667
771
  controller.close();
668
772
  return;
669
773
  }
670
- const safe = options.redactor?.redact(next.value) ?? next.value;
671
- const chunk = encoder.encode(`data: ${JSON.stringify(safe)}\n\n`);
774
+ const chunk = encoder.encode(serialize(next.value));
672
775
  events += 1;
673
776
  bytes += chunk.byteLength;
674
777
  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.23",
3
+ "version": "0.0.25",
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.23",
29
- "@arnilo/prism-workflows": "0.0.23"
28
+ "@arnilo/prism": "0.0.25",
29
+ "@arnilo/prism-workflows": "0.0.25"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@arnilo/prism": "file:../..",