@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 +20 -4
- package/README.md +1 -1
- package/dist/handler.js +112 -9
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/limits.d.ts +2 -0
- package/dist/limits.js +1 -0
- package/dist/replay.d.ts +8 -2
- package/dist/replay.js +29 -1
- package/dist/types.d.ts +11 -2
- package/package.json +3 -3
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
/**
|
|
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.
|
|
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.
|
|
29
|
-
"@arnilo/prism-workflows": "0.0.
|
|
28
|
+
"@arnilo/prism": "0.0.25",
|
|
29
|
+
"@arnilo/prism-workflows": "0.0.25"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@arnilo/prism": "file:../..",
|