@spine-event-engine/server 2.0.0-snapshot.22 → 2.0.0-snapshot.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/README.md +104 -7
- package/REFERENCE.md +272 -31
- package/dist/agent/agent-admission.d.ts +80 -0
- package/dist/agent/agent-admission.d.ts.map +1 -0
- package/dist/agent/agent-admission.js +127 -0
- package/dist/agent/agent-admission.js.map +1 -0
- package/dist/agent/agent-ai-binding.d.ts +24 -0
- package/dist/agent/agent-ai-binding.d.ts.map +1 -0
- package/dist/agent/agent-ai-binding.js +48 -0
- package/dist/agent/agent-ai-binding.js.map +1 -0
- package/dist/agent/agent-ai-runtime.d.ts +76 -0
- package/dist/agent/agent-ai-runtime.d.ts.map +1 -0
- package/dist/agent/agent-ai-runtime.js +1269 -0
- package/dist/agent/agent-ai-runtime.js.map +1 -0
- package/dist/agent/agent-execution-capacity.d.ts +43 -0
- package/dist/agent/agent-execution-capacity.d.ts.map +1 -0
- package/dist/agent/agent-execution-capacity.js +147 -0
- package/dist/agent/agent-execution-capacity.js.map +1 -0
- package/dist/agent/agent-execution-fault.d.ts +18 -0
- package/dist/agent/agent-execution-fault.d.ts.map +1 -0
- package/dist/agent/agent-execution-fault.js +31 -0
- package/dist/agent/agent-execution-fault.js.map +1 -0
- package/dist/agent/agent-execution-session.d.ts +103 -0
- package/dist/agent/agent-execution-session.d.ts.map +1 -0
- package/dist/agent/agent-execution-session.js +238 -0
- package/dist/agent/agent-execution-session.js.map +1 -0
- package/dist/agent/agent-history.d.ts +148 -0
- package/dist/agent/agent-history.d.ts.map +1 -0
- package/dist/agent/agent-history.js +340 -0
- package/dist/agent/agent-history.js.map +1 -0
- package/dist/agent/agent-interaction-audit.d.ts +58 -0
- package/dist/agent/agent-interaction-audit.d.ts.map +1 -0
- package/dist/agent/agent-interaction-audit.js +114 -0
- package/dist/agent/agent-interaction-audit.js.map +1 -0
- package/dist/agent/agent-interaction-events.d.ts +40 -0
- package/dist/agent/agent-interaction-events.d.ts.map +1 -0
- package/dist/agent/agent-interaction-events.js +129 -0
- package/dist/agent/agent-interaction-events.js.map +1 -0
- package/dist/agent/agent-mcp-budget.d.ts +125 -0
- package/dist/agent/agent-mcp-budget.d.ts.map +1 -0
- package/dist/agent/agent-mcp-budget.js +566 -0
- package/dist/agent/agent-mcp-budget.js.map +1 -0
- package/dist/agent/agent-mcp-host.d.ts +125 -0
- package/dist/agent/agent-mcp-host.d.ts.map +1 -0
- package/dist/agent/agent-mcp-host.js +15 -0
- package/dist/agent/agent-mcp-host.js.map +1 -0
- package/dist/agent/agent-mcp-runtime.d.ts +199 -0
- package/dist/agent/agent-mcp-runtime.d.ts.map +1 -0
- package/dist/agent/agent-mcp-runtime.js +621 -0
- package/dist/agent/agent-mcp-runtime.js.map +1 -0
- package/dist/agent/agent-model-selection.d.ts +129 -0
- package/dist/agent/agent-model-selection.d.ts.map +1 -0
- package/dist/agent/agent-model-selection.js +342 -0
- package/dist/agent/agent-model-selection.js.map +1 -0
- package/dist/agent/agent-read-runtime.d.ts +70 -0
- package/dist/agent/agent-read-runtime.d.ts.map +1 -0
- package/dist/agent/agent-read-runtime.js +320 -0
- package/dist/agent/agent-read-runtime.js.map +1 -0
- package/dist/agent/agent-revisions.d.ts +35 -0
- package/dist/agent/agent-revisions.d.ts.map +1 -0
- package/dist/agent/agent-revisions.js +133 -0
- package/dist/agent/agent-revisions.js.map +1 -0
- package/dist/agent/agent-scheduler.d.ts +99 -0
- package/dist/agent/agent-scheduler.d.ts.map +1 -0
- package/dist/agent/agent-scheduler.js +398 -0
- package/dist/agent/agent-scheduler.js.map +1 -0
- package/dist/bus/command-bus.d.ts +62 -0
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +51 -2
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/event-bus.d.ts +101 -2
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +74 -13
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/bus/saved-dispatcher-binding.d.ts +53 -0
- package/dist/bus/saved-dispatcher-binding.d.ts.map +1 -0
- package/dist/bus/saved-dispatcher-binding.js +27 -0
- package/dist/bus/saved-dispatcher-binding.js.map +1 -0
- package/dist/context/bounded-context.d.ts +45 -12
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +306 -86
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +21 -7
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +1 -0
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +31 -1
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +2 -0
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts +25 -0
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +45 -1
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/delivery/delivery-monitor.d.ts +12 -9
- package/dist/delivery/delivery-monitor.d.ts.map +1 -1
- package/dist/delivery/delivery-monitor.js +15 -9
- package/dist/delivery/delivery-monitor.js.map +1 -1
- package/dist/delivery/delivery-ports.d.ts +6 -13
- package/dist/delivery/delivery-ports.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.d.ts +15 -0
- package/dist/delivery/delivery-run-control.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.js.map +1 -1
- package/dist/delivery/delivery-supervisor.d.ts +33 -5
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
- package/dist/delivery/delivery-supervisor.js +150 -22
- package/dist/delivery/delivery-supervisor.js.map +1 -1
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +128 -54
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +8 -11
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +33 -76
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts +9 -12
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +10 -13
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +56 -0
- package/dist/delivery/reception-observation.d.ts.map +1 -0
- package/dist/delivery/reception-observation.js +67 -0
- package/dist/delivery/reception-observation.js.map +1 -0
- package/dist/entity/entity.d.ts +82 -3
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +121 -22
- package/dist/entity/entity.js.map +1 -1
- package/dist/handler/generated-handler-registry.js +8 -3
- package/dist/handler/generated-handler-registry.js.map +1 -1
- package/dist/handler/handler-metadata.d.ts +17 -1
- package/dist/handler/handler-metadata.d.ts.map +1 -1
- package/dist/handler/handler-metadata.js +10 -4
- package/dist/handler/handler-metadata.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/repository/commit-fence.d.ts +11 -0
- package/dist/repository/commit-fence.d.ts.map +1 -1
- package/dist/repository/commit-fence.js +13 -0
- package/dist/repository/commit-fence.js.map +1 -1
- package/dist/repository/repository.d.ts +221 -13
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +2323 -134
- package/dist/repository/repository.js.map +1 -1
- package/dist/runtime/runtime.d.ts +11 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +13 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/signal-metadata.d.ts.map +1 -1
- package/dist/runtime/signal-metadata.js +3 -10
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +14 -30
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +75 -47
- package/dist/runtime/signal-publisher.js.map +1 -1
- package/dist/runtime/standalone-handler-runtime.d.ts +7 -0
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -1
- package/dist/runtime/standalone-handler-runtime.js +118 -11
- package/dist/runtime/standalone-handler-runtime.js.map +1 -1
- package/dist/server/environment-delivery-worker.d.ts.map +1 -1
- package/dist/server/environment-delivery-worker.js +305 -5
- package/dist/server/environment-delivery-worker.js.map +1 -1
- package/dist/server/managed-server-application.d.ts +2 -2
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +467 -177
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.d.ts +14 -0
- package/dist/server/node-coordinator.d.ts.map +1 -1
- package/dist/server/node-coordinator.js +266 -45
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/server-log.d.ts.map +1 -1
- package/dist/server/server-log.js +4 -0
- package/dist/server/server-log.js.map +1 -1
- package/dist/server/server.d.ts +9 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +33 -8
- package/dist/server/server.js.map +1 -1
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +200 -72
- package/dist/services/spine-services.js.map +1 -1
- package/dist/stand/event-origin-filter.d.ts +16 -0
- package/dist/stand/event-origin-filter.d.ts.map +1 -0
- package/dist/stand/event-origin-filter.js +124 -0
- package/dist/stand/event-origin-filter.js.map +1 -0
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +7 -0
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-runtime.d.ts +18 -1
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +32 -4
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/testing/index.d.ts +57 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +88 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -13
|
@@ -11,16 +11,22 @@
|
|
|
11
11
|
* or implied. See the License for the specific language governing permissions and limitations under
|
|
12
12
|
* the License.
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
15
|
+
import { clone, create, equals, ScalarType, toBinary, } from "@bufbuild/protobuf";
|
|
16
|
+
import { AnySchema, FileDescriptorProtoSchema, Int32ValueSchema, Int64ValueSchema, StringValueSchema, } from "@bufbuild/protobuf/wkt";
|
|
16
17
|
import { ValidationException, RejectionThrowable, Validate, TypeUrls, AnyMessages, Identifiers, StringifierRegistry, Time, } from "@spine-event-engine/core";
|
|
17
18
|
import { CommandContextSchema, ActorContextSchema, CommandIdSchema, CommandSchema, EventContextSchema, EventIdSchema, EventSchema, MessageIdSchema, TenantIdSchema, RejectionEventContextSchema, VersionSchema, EntityOption_Kind, } from "@spine-event-engine/proto";
|
|
18
19
|
import * as EntityLog from "@spine-event-engine/proto/generated/spine/system/server/entity_log_events_pb.js";
|
|
19
20
|
import { EntityTypeNameSchema } from "@spine-event-engine/proto/generated/spine/system/server/entity_type_pb.js";
|
|
20
21
|
import { EntityRecordSchema, } from "@spine-event-engine/proto/generated/spine/server/entity/entity_pb.js";
|
|
21
|
-
import { StorageQueryPolicy, } from "@spine-event-engine/storage";
|
|
22
|
+
import { EventStore, StorageQueryPolicy, } from "@spine-event-engine/storage";
|
|
22
23
|
import { TenantBoundary } from "@spine-event-engine/storage/provider";
|
|
23
24
|
import { EntityCommitStorageFactories } from "@spine-event-engine/storage/provider";
|
|
25
|
+
import { AgentHistoryStorageFactories, } from "@spine-event-engine/storage/provider";
|
|
26
|
+
import { AgentExecutionStorageFactories, } from "@spine-event-engine/storage/provider";
|
|
27
|
+
import { AgentHistoryEntrySchema, AgentInvocationTerminatedSchema, AgentModelSelectionChangedSchema as SelectionChangedSchema, AiModelKind, AiDiagnosticIdSchema, ModelRefSchema, ToolEffect, ConversationIdSchema, } from "@spine-event-engine/proto/agent";
|
|
28
|
+
import { AgentInvocationKeySchema, AgentExecutionStartSchema, AgentExecutionCompletionSchema, AgentInvocationBoundsSchema, AgentInvocationStatus, AgentOutgoingCommandSchema, AgentSignalKeySchema, AgentSavedDispatchTargetSchema as SavedTargetSchema, AgentSavedRepositoryFamily, AgentSavedTargetKind, } from "@spine-event-engine/proto/generated/spine/server/agent/execution_record_pb.js";
|
|
29
|
+
import { registryOptions } from "@spine-event-engine/ai/spi/runtime";
|
|
24
30
|
import { CommandValidationError } from "../bus/command-errors.js";
|
|
25
31
|
import { SignalPublisher } from "../runtime/signal-publisher.js";
|
|
26
32
|
import { ServerEnvironment, serverEnvironmentAccess } from "../server/server-environment.js";
|
|
@@ -30,11 +36,24 @@ import { EventRoutingInternals } from "./event-routing.js";
|
|
|
30
36
|
import { StateUpdateRoutingInternals, } from "./state-update-routing.js";
|
|
31
37
|
import { RoutingDeclarations } from "./routing-declarations.js";
|
|
32
38
|
import { EventDispatcherOriginSchemas } from "../bus/event-dispatcher-origin-schemas.js";
|
|
39
|
+
import { SavedDispatcherBindings, } from "../bus/saved-dispatcher-binding.js";
|
|
33
40
|
import { Delivery } from "../delivery/delivery.js";
|
|
41
|
+
import { receptionObservation } from "../delivery/reception-observation.js";
|
|
34
42
|
import { commitFenced } from "./commit-fence.js";
|
|
43
|
+
import { AgentHistoryReads } from "../agent/agent-history.js";
|
|
44
|
+
import { AgentReadRuntime } from "../agent/agent-read-runtime.js";
|
|
45
|
+
import { AgentAdmission } from "../agent/agent-admission.js";
|
|
46
|
+
import { AgentRevisions } from "../agent/agent-revisions.js";
|
|
47
|
+
import { AgentExecutionSession } from "../agent/agent-execution-session.js";
|
|
48
|
+
import { AgentExecutionFault } from "../agent/agent-execution-fault.js";
|
|
49
|
+
import { AgentModelSelection } from "../agent/agent-model-selection.js";
|
|
50
|
+
import { AgentAiRuntime, AgentUncertainAttemptError } from "../agent/agent-ai-runtime.js";
|
|
51
|
+
import { AgentInteractionEvents } from "../agent/agent-interaction-events.js";
|
|
52
|
+
import { AgentInteractionAudit } from "../agent/agent-interaction-audit.js";
|
|
53
|
+
import { AgentAiBindings } from "../agent/agent-ai-binding.js";
|
|
35
54
|
import { InboxTargets } from "../delivery/inbox.js";
|
|
36
55
|
import { ShardIndex } from "../delivery/shard-index.js";
|
|
37
|
-
import { Aggregate, ProcessManager, Projection, entityHistoryAccess, processManagerQueryAccess, transactionalEntityAccess, } from "../entity/entity.js";
|
|
56
|
+
import { Aggregate, Agent, ProcessManager, Projection, entityHistoryAccess, processManagerQueryAccess, transactionalEntityAccess, } from "../entity/entity.js";
|
|
38
57
|
import { describeEntityMetadata, attachEntitySchema, } from "../entity/entity-metadata.js";
|
|
39
58
|
import { EntityIds, EntityRecords, entityStorageDescriptor, } from "../entity/entity-storage-descriptor.js";
|
|
40
59
|
import { repositorySpecScanner } from "../entity/spec-scanner.js";
|
|
@@ -185,6 +204,31 @@ export class Repository {
|
|
|
185
204
|
get snapshot() {
|
|
186
205
|
return RepositoryIdentity.cloneRepositorySnapshot(RepositoryIdentity.createRepositorySnapshot(this.#entityType, this.#entityFamily, this.#metadata));
|
|
187
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Opens retained history for one Agent without restoring an Entity.
|
|
209
|
+
* The trusted application must authorize the caller before using this read.
|
|
210
|
+
*
|
|
211
|
+
* @param this Registered Agent repository.
|
|
212
|
+
* @param id Typed Agent identifier.
|
|
213
|
+
* @param scope Explicit tenant boundary; pass `{}` in a single-tenant Bounded Context.
|
|
214
|
+
* @returns Reader for indexed history categories.
|
|
215
|
+
*/
|
|
216
|
+
agentHistory(id, scope) {
|
|
217
|
+
return RepositoryAgentReads.history(this, id, scope);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Reads the exact recorded phase for one accepted Command or Event.
|
|
221
|
+
* The trusted application must authorize the caller before using this read.
|
|
222
|
+
*
|
|
223
|
+
* @param this Registered Agent repository.
|
|
224
|
+
* @param id Typed Agent identifier.
|
|
225
|
+
* @param source Original Command or Event ID.
|
|
226
|
+
* @param scope Explicit tenant boundary; pass `{}` in a single-tenant Bounded Context.
|
|
227
|
+
* @returns Recorded phase, or `undefined` when no invocation was recorded.
|
|
228
|
+
*/
|
|
229
|
+
agentExecution(id, source, scope) {
|
|
230
|
+
return RepositoryAgentReads.execution(this, id, source, scope);
|
|
231
|
+
}
|
|
188
232
|
/**
|
|
189
233
|
* Routes a command to one entity ID without invoking a handler.
|
|
190
234
|
*
|
|
@@ -213,6 +257,199 @@ export class Repository {
|
|
|
213
257
|
});
|
|
214
258
|
}
|
|
215
259
|
}
|
|
260
|
+
const RepositoryAgentReads = {
|
|
261
|
+
/**
|
|
262
|
+
* Captures a validated Agent and tenant boundary without opening Entity storage.
|
|
263
|
+
*
|
|
264
|
+
* @param repository Registered Agent repository.
|
|
265
|
+
* @param id Typed Agent identifier.
|
|
266
|
+
* @param scope Explicit tenant selection.
|
|
267
|
+
* @returns Detached read identity and active runtime.
|
|
268
|
+
*/
|
|
269
|
+
capture(repository, id, scope) {
|
|
270
|
+
if (repository.entityFamily !== "agent")
|
|
271
|
+
throw new TypeError("Agent read requires an Agent repository.");
|
|
272
|
+
const scopeValue = scope;
|
|
273
|
+
if (scopeValue === null || typeof scopeValue !== "object")
|
|
274
|
+
throw new TypeError("Agent read requires an explicit scope.");
|
|
275
|
+
const runtime = repositoryRuntimes.get(repository);
|
|
276
|
+
if (runtime === undefined)
|
|
277
|
+
throw new Error("Agent read requires an active repository.");
|
|
278
|
+
const validated = RepositoryRoutes.readRouteId(id, repository.idField, "command");
|
|
279
|
+
const field = repository.idField.descriptor;
|
|
280
|
+
const copiedId = field.fieldKind === "message"
|
|
281
|
+
? clone(field.message, validated)
|
|
282
|
+
: validated;
|
|
283
|
+
const tenant = scope.tenantId === undefined ? undefined : RepositoryTenants.require(scope.tenantId);
|
|
284
|
+
const context = RepositoryTenants.storageContextForTenant(runtime.context, tenant);
|
|
285
|
+
return { runtime, context, id: copiedId };
|
|
286
|
+
},
|
|
287
|
+
/**
|
|
288
|
+
* Returns a detached public history request before provider access.
|
|
289
|
+
*
|
|
290
|
+
* @param request Page request and opaque cursor.
|
|
291
|
+
* @returns Independent request values.
|
|
292
|
+
*/
|
|
293
|
+
request(request) {
|
|
294
|
+
return {
|
|
295
|
+
pageSize: request.pageSize,
|
|
296
|
+
...(request.cursor === undefined ? {} : { cursor: request.cursor }),
|
|
297
|
+
};
|
|
298
|
+
},
|
|
299
|
+
/**
|
|
300
|
+
* Creates the four scoped history views for one captured Agent identity.
|
|
301
|
+
*
|
|
302
|
+
* @param repository Registered Agent repository.
|
|
303
|
+
* @param id Typed Agent identifier.
|
|
304
|
+
* @param scope Explicit tenant selection.
|
|
305
|
+
* @returns Reader using indexed provider history.
|
|
306
|
+
*/
|
|
307
|
+
history(repository, id, scope) {
|
|
308
|
+
const captured = this.capture(repository, id, scope);
|
|
309
|
+
const read = (view, request) => this.historyPage(repository, captured, view, this.request(request));
|
|
310
|
+
return Object.freeze({
|
|
311
|
+
fullHistory: (request) => read({ kind: "full" }, request),
|
|
312
|
+
conversationHistory: async (request) => {
|
|
313
|
+
const conversationValue = request.conversation;
|
|
314
|
+
if (conversationValue === null ||
|
|
315
|
+
typeof conversationValue !== "object" ||
|
|
316
|
+
!("$typeName" in conversationValue) ||
|
|
317
|
+
conversationValue.$typeName !== ConversationIdSchema.typeName ||
|
|
318
|
+
!("value" in conversationValue) ||
|
|
319
|
+
typeof conversationValue.value !== "string" ||
|
|
320
|
+
conversationValue.value.length === 0)
|
|
321
|
+
throw new TypeError("Agent conversation history requires a ConversationId.");
|
|
322
|
+
const conversation = clone(ConversationIdSchema, request.conversation);
|
|
323
|
+
const page = await read({ kind: "conversation", conversation }, request);
|
|
324
|
+
return this.project(page, "conversationRecord");
|
|
325
|
+
},
|
|
326
|
+
systemEventHistory: async (request) => this.project(await read({ kind: "system" }, request), "systemEvent"),
|
|
327
|
+
domainEventHistory: async (request) => this.project(await read({ kind: "domain" }, request), "domainEvent"),
|
|
328
|
+
});
|
|
329
|
+
},
|
|
330
|
+
/**
|
|
331
|
+
* Reads one category from the existing indexed history provider.
|
|
332
|
+
*
|
|
333
|
+
* @param repository Registered Agent repository.
|
|
334
|
+
* @param captured Detached Agent and tenant identity.
|
|
335
|
+
* @param view Indexed category and optional conversation.
|
|
336
|
+
* @param request Detached page request.
|
|
337
|
+
* @returns Detached history entries and opaque continuation.
|
|
338
|
+
*/
|
|
339
|
+
async historyPage(repository, captured, view, request) {
|
|
340
|
+
if (repositoryRuntimes.get(repository) !== captured.runtime)
|
|
341
|
+
throw new Error("Agent read requires an active repository.");
|
|
342
|
+
const input = RepositoryStorage.entityStorageInput(repository, captured.context);
|
|
343
|
+
const history = AgentHistoryStorageFactories.create(captured.runtime.storageFactory, {
|
|
344
|
+
context: captured.context,
|
|
345
|
+
stateType: repository.stateSchema.typeName,
|
|
346
|
+
id: { key: input.id.key },
|
|
347
|
+
});
|
|
348
|
+
try {
|
|
349
|
+
const page = await AgentHistoryReads.readBound({
|
|
350
|
+
storage: history,
|
|
351
|
+
entityId: captured.id,
|
|
352
|
+
scope: repositoryAccess.agentHistoryScope(captured.context, input, captured.id),
|
|
353
|
+
}, view, request);
|
|
354
|
+
return {
|
|
355
|
+
items: page.items,
|
|
356
|
+
...(page.nextCursor === undefined ? {} : { nextCursor: page.nextCursor }),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
finally {
|
|
360
|
+
history.close();
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
/**
|
|
364
|
+
* Returns one typed category from the full Proto history oneof.
|
|
365
|
+
*
|
|
366
|
+
* @typeParam K Selected history oneof category.
|
|
367
|
+
* @param page Complete indexed history page.
|
|
368
|
+
* @param kind Expected oneof category.
|
|
369
|
+
* @returns Category values and the original continuation.
|
|
370
|
+
*/
|
|
371
|
+
project(page, kind) {
|
|
372
|
+
const items = page.items.map((entry) => {
|
|
373
|
+
if (entry.item.case !== kind)
|
|
374
|
+
throw new Error("Agent history provider returned the wrong category.");
|
|
375
|
+
return entry.item.value;
|
|
376
|
+
});
|
|
377
|
+
return {
|
|
378
|
+
items,
|
|
379
|
+
...(page.nextCursor === undefined ? {} : { nextCursor: page.nextCursor }),
|
|
380
|
+
};
|
|
381
|
+
},
|
|
382
|
+
/**
|
|
383
|
+
* Reads the exact saved invocation and exposes only its phase.
|
|
384
|
+
*
|
|
385
|
+
* @param repository Registered Agent repository.
|
|
386
|
+
* @param id Typed Agent identifier.
|
|
387
|
+
* @param source Original Command or Event ID.
|
|
388
|
+
* @param scope Explicit tenant selection.
|
|
389
|
+
* @returns Current phase, or `undefined` for no saved invocation.
|
|
390
|
+
*/
|
|
391
|
+
async execution(repository, id, source, scope) {
|
|
392
|
+
const captured = this.capture(repository, id, scope);
|
|
393
|
+
const sourceSignal = this.source(source);
|
|
394
|
+
const input = RepositoryStorage.entityStorageInput(repository, captured.context);
|
|
395
|
+
const key = create(AgentInvocationKeySchema, {
|
|
396
|
+
scope: { stateType: repository.stateSchema.typeName, agentKey: input.id.key(captured.id) },
|
|
397
|
+
sourceSignal,
|
|
398
|
+
});
|
|
399
|
+
if (repositoryRuntimes.get(repository) !== captured.runtime)
|
|
400
|
+
throw new Error("Agent read requires an active repository.");
|
|
401
|
+
const storage = AgentExecutionStorageFactories.create(captured.runtime.storageFactory, {
|
|
402
|
+
entity: input,
|
|
403
|
+
stateType: repository.stateSchema.typeName,
|
|
404
|
+
});
|
|
405
|
+
try {
|
|
406
|
+
const record = await storage.read(key);
|
|
407
|
+
return record === undefined ? undefined : this.status(record.status);
|
|
408
|
+
}
|
|
409
|
+
finally {
|
|
410
|
+
storage.close();
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
/**
|
|
414
|
+
* Copies an original signal identifier into the private execution key.
|
|
415
|
+
*
|
|
416
|
+
* @param source Original Command or Event ID.
|
|
417
|
+
* @returns Typed private source key.
|
|
418
|
+
*/
|
|
419
|
+
source(source) {
|
|
420
|
+
if (source.$typeName === CommandIdSchema.typeName && source.uuid.length > 0)
|
|
421
|
+
return create(AgentSignalKeySchema, {
|
|
422
|
+
id: { case: "command", value: clone(CommandIdSchema, source) },
|
|
423
|
+
});
|
|
424
|
+
if (source.$typeName === EventIdSchema.typeName && source.value.length > 0)
|
|
425
|
+
return create(AgentSignalKeySchema, {
|
|
426
|
+
id: { case: "event", value: clone(EventIdSchema, source) },
|
|
427
|
+
});
|
|
428
|
+
throw new TypeError("Agent execution requires an original CommandId or EventId.");
|
|
429
|
+
},
|
|
430
|
+
/**
|
|
431
|
+
* Returns the public status for a private execution phase.
|
|
432
|
+
*
|
|
433
|
+
* @param status Saved private phase.
|
|
434
|
+
* @returns Public phase string.
|
|
435
|
+
*/
|
|
436
|
+
status(status) {
|
|
437
|
+
switch (status) {
|
|
438
|
+
case AgentInvocationStatus.AGENT_INVOCATION_ACCEPTED:
|
|
439
|
+
return "accepted";
|
|
440
|
+
case AgentInvocationStatus.AGENT_INVOCATION_ACTIVE:
|
|
441
|
+
return "active";
|
|
442
|
+
case AgentInvocationStatus.AGENT_INVOCATION_COMPLETED_PENDING_COMMANDS:
|
|
443
|
+
return "completed-pending-commands";
|
|
444
|
+
case AgentInvocationStatus.AGENT_INVOCATION_COMPLETED:
|
|
445
|
+
return "completed";
|
|
446
|
+
case AgentInvocationStatus.AGENT_INVOCATION_TERMINATED:
|
|
447
|
+
return "terminated";
|
|
448
|
+
default:
|
|
449
|
+
throw new Error("Agent execution provider returned an unsupported phase.");
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
};
|
|
216
453
|
const repositorySnapshots = new WeakMap();
|
|
217
454
|
const repositoryCreators = new WeakMap();
|
|
218
455
|
const repositoryRoutings = new WeakMap();
|
|
@@ -225,6 +462,8 @@ const repositoryRuntimes = new WeakMap();
|
|
|
225
462
|
const repositoryEntityHandles = new WeakMap();
|
|
226
463
|
const entityStateHistoryCaches = new WeakMap();
|
|
227
464
|
const repositoryHistoryConfigurations = new WeakMap();
|
|
465
|
+
const repositoryAiConfigurations = new WeakMap();
|
|
466
|
+
const repositoryAgentCodeRevisions = new WeakMap();
|
|
228
467
|
const repositoryDispatchGuards = new WeakMap();
|
|
229
468
|
/**
|
|
230
469
|
* Installs repository routing, history, and callback state after identity validation.
|
|
@@ -248,10 +487,27 @@ const RepositoryRegistration = {
|
|
|
248
487
|
repositoryRoutings.set(repository, routing);
|
|
249
488
|
repositoryProducedEventSchemas.set(repository, Object.freeze([...routing.producedEventSchemas]));
|
|
250
489
|
repositoryHistoryConfigurations.set(repository, RepositoryStorage.readHistoryConfiguration(options, entityFamily));
|
|
490
|
+
this.installAgentOptions(repository, options, entityFamily);
|
|
251
491
|
repositorySnapshots.set(repository, RepositoryIdentity.createRepositorySnapshot(entityType, entityFamily, metadata));
|
|
252
492
|
repositoryDispatchers.set(repository, RepositoryDispatch.createRepositoryDispatchers(repository, routing));
|
|
253
493
|
RepositoryRegistration.installTargets(repository, routing);
|
|
254
494
|
},
|
|
495
|
+
/**
|
|
496
|
+
* Captures Agent capability and code revisions during repository registration.
|
|
497
|
+
*
|
|
498
|
+
* @typeParam EntityType Concrete generated Entity constructor.
|
|
499
|
+
* @param repository Registered Entity repository for this operation.
|
|
500
|
+
* @param options Repository or execution options for this operation.
|
|
501
|
+
* @param family Registered Entity family.
|
|
502
|
+
*/
|
|
503
|
+
installAgentOptions(repository, options, family) {
|
|
504
|
+
if (family !== "agent")
|
|
505
|
+
return;
|
|
506
|
+
if (options.ai !== undefined)
|
|
507
|
+
repositoryAiConfigurations.set(repository, options.ai);
|
|
508
|
+
if (options.agentCodeRevision !== undefined)
|
|
509
|
+
repositoryAgentCodeRevisions.set(repository, options.agentCodeRevision);
|
|
510
|
+
},
|
|
255
511
|
/**
|
|
256
512
|
* Registers durable Entity and direct Projection delivery targets.
|
|
257
513
|
*
|
|
@@ -312,6 +568,98 @@ export const repositoryAccess = Object.freeze({
|
|
|
312
568
|
}
|
|
313
569
|
return schemas;
|
|
314
570
|
},
|
|
571
|
+
producedCommandSchemas(repository) {
|
|
572
|
+
const routing = repositoryRoutings.get(repository);
|
|
573
|
+
if (routing === undefined)
|
|
574
|
+
throw new TypeError("Produced Command schemas require a Repository instance.");
|
|
575
|
+
return routing.producedCommandSchemas;
|
|
576
|
+
},
|
|
577
|
+
agentConfiguration(repository) {
|
|
578
|
+
const ai = repositoryAiConfigurations.get(repository);
|
|
579
|
+
const codeRevision = repositoryAgentCodeRevisions.get(repository);
|
|
580
|
+
return {
|
|
581
|
+
...(ai === undefined ? {} : { ai }),
|
|
582
|
+
...(codeRevision === undefined ? {} : { codeRevision }),
|
|
583
|
+
};
|
|
584
|
+
},
|
|
585
|
+
runAcceptedAgent(repository, tenantId, key, signal) {
|
|
586
|
+
return AgentExecutionRunner.run(repository, tenantId, key, signal);
|
|
587
|
+
},
|
|
588
|
+
pendingAcceptedAgents(repository, tenantId, after, count) {
|
|
589
|
+
const runtime = repositoryRuntimes.get(repository);
|
|
590
|
+
if (runtime === undefined || repository.entityFamily !== "agent")
|
|
591
|
+
throw new Error("Agent pending discovery requires a registered Agent repository.");
|
|
592
|
+
const input = RepositoryStorage.entityStorageInput(repository, RepositoryTenants.storageContextForTenant(runtime.context, tenantId));
|
|
593
|
+
const storage = RepositoryStorage.openRepositoryEntityStorage(repository, runtime.storageFactory, input);
|
|
594
|
+
if (storage.agentExecution === undefined)
|
|
595
|
+
throw new Error("Agent pending discovery requires execution storage.");
|
|
596
|
+
return storage.agentExecution.pending({ count, ...(after === undefined ? {} : { after }) });
|
|
597
|
+
},
|
|
598
|
+
/**
|
|
599
|
+
* Opens a scoped provider read without restoring an application Entity.
|
|
600
|
+
*
|
|
601
|
+
* @param repository Registered Agent repository.
|
|
602
|
+
* @param entityId Typed Agent identifier.
|
|
603
|
+
* @param view Indexed category.
|
|
604
|
+
* @param read Requested count, byte budget, and complete continuation key.
|
|
605
|
+
* @param tenantId Complete tenant identity when required.
|
|
606
|
+
* @returns Retained entries and continuation status.
|
|
607
|
+
*/
|
|
608
|
+
async agentHistory(repository, entityId, view, read, tenantId) {
|
|
609
|
+
if (repository.entityFamily !== "agent")
|
|
610
|
+
throw new TypeError("Agent history requires an Agent repository.");
|
|
611
|
+
const runtime = repositoryRuntimes.get(repository);
|
|
612
|
+
if (runtime === undefined)
|
|
613
|
+
throw new Error("Agent history requires an active repository.");
|
|
614
|
+
const context = RepositoryTenants.storageContextForTenant(runtime.context, tenantId);
|
|
615
|
+
const input = RepositoryStorage.entityStorageInput(repository, context);
|
|
616
|
+
const history = AgentHistoryStorageFactories.create(runtime.storageFactory, {
|
|
617
|
+
context,
|
|
618
|
+
stateType: repository.stateSchema.typeName,
|
|
619
|
+
id: { key: input.id.key },
|
|
620
|
+
});
|
|
621
|
+
try {
|
|
622
|
+
return await history.read({ entityId, view, ...read });
|
|
623
|
+
}
|
|
624
|
+
finally {
|
|
625
|
+
history.close();
|
|
626
|
+
}
|
|
627
|
+
},
|
|
628
|
+
async agentHistoryPage(repository, entityId, request, tenantId) {
|
|
629
|
+
if (repository.entityFamily !== "agent")
|
|
630
|
+
throw new TypeError("Agent history requires an Agent repository.");
|
|
631
|
+
const runtime = repositoryRuntimes.get(repository);
|
|
632
|
+
if (runtime === undefined)
|
|
633
|
+
throw new Error("Agent history requires an active repository.");
|
|
634
|
+
const context = RepositoryTenants.storageContextForTenant(runtime.context, tenantId);
|
|
635
|
+
const input = RepositoryStorage.entityStorageInput(repository, context);
|
|
636
|
+
const history = AgentHistoryStorageFactories.create(runtime.storageFactory, {
|
|
637
|
+
context,
|
|
638
|
+
stateType: repository.stateSchema.typeName,
|
|
639
|
+
id: { key: input.id.key },
|
|
640
|
+
});
|
|
641
|
+
try {
|
|
642
|
+
return await AgentHistoryReads.readBound({
|
|
643
|
+
storage: history,
|
|
644
|
+
entityId,
|
|
645
|
+
scope: this.agentHistoryScope(context, input, entityId),
|
|
646
|
+
}, { kind: "full" }, request);
|
|
647
|
+
}
|
|
648
|
+
finally {
|
|
649
|
+
history.close();
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
/**
|
|
653
|
+
* Computes the opaque cursor scope for a registered Agent history read.
|
|
654
|
+
*/
|
|
655
|
+
agentHistoryScope(context, input, entityId) {
|
|
656
|
+
return {
|
|
657
|
+
context: context.name,
|
|
658
|
+
tenant: JSON.stringify(context.tenantId ?? null),
|
|
659
|
+
repository: input.sourceType.typeName,
|
|
660
|
+
entity: input.id.key(entityId),
|
|
661
|
+
};
|
|
662
|
+
},
|
|
315
663
|
/**
|
|
316
664
|
* Looks up command dispatch for the repository.
|
|
317
665
|
*
|
|
@@ -1558,13 +1906,10 @@ class ProjectionEventExecution {
|
|
|
1558
1906
|
async #loadProjection(entityId, options, mode) {
|
|
1559
1907
|
const stored = await standAccess.readCurrent(this.#runtime.stand, this.#repository.stateSchema, entityId, options);
|
|
1560
1908
|
const entity = RepositoryEntities.instantiate(this.#repository, entityId, stored, mode === "rebuild" && stored?.deleted === true);
|
|
1561
|
-
const { commits, events, storageInput } = RepositoryEntities.bindStorage(this.#repository, this.#runtime, options.tenantId, entityId, entity);
|
|
1562
|
-
return RepositoryEntities.loaded(entity, stored, commits, events, storageInput, this.#repository.stateSchema, entityId);
|
|
1909
|
+
const { commits, events, agentHistory, storageInput } = RepositoryEntities.bindStorage(this.#repository, this.#runtime, options.tenantId, entityId, entity);
|
|
1910
|
+
return RepositoryEntities.loaded(entity, stored, commits, events, agentHistory, storageInput, this.#repository.stateSchema, entityId);
|
|
1563
1911
|
}
|
|
1564
1912
|
}
|
|
1565
|
-
/**
|
|
1566
|
-
* Binds Process Manager query access to Stand for one handler invocation.
|
|
1567
|
-
*/
|
|
1568
1913
|
const ProcessManagerQueries = Object.freeze({
|
|
1569
1914
|
/**
|
|
1570
1915
|
* Binds tenant-aware query reads and returns a release callback.
|
|
@@ -1573,15 +1918,16 @@ const ProcessManagerQueries = Object.freeze({
|
|
|
1573
1918
|
* @param runtime Context Stand and query services.
|
|
1574
1919
|
* @param actorContext Actor metadata from the source signal.
|
|
1575
1920
|
* @param tenantId Tenant for query reads, when present.
|
|
1921
|
+
* @param intercept Durable Agent snapshot interception, when active.
|
|
1576
1922
|
* @returns Callback that removes the query binding.
|
|
1577
1923
|
*/
|
|
1578
|
-
bind(entity, runtime, actorContext, tenantId) {
|
|
1924
|
+
bind(entity, runtime, actorContext, tenantId, intercept) {
|
|
1579
1925
|
const context = actorContext === undefined
|
|
1580
1926
|
? create(ActorContextSchema)
|
|
1581
1927
|
: clone(ActorContextSchema, actorContext);
|
|
1582
1928
|
const effectiveTenant = EffectiveTenants.current(runtime.context.multitenant, tenantId);
|
|
1583
1929
|
context.tenantId = effectiveTenant;
|
|
1584
|
-
return processManagerQueryAccess.bind(entity, (plan, schema, query) => ProcessManagerQueries.read(runtime, effectiveTenant, plan, schema, query), context);
|
|
1930
|
+
return processManagerQueryAccess.bind(entity, (plan, schema, query) => ProcessManagerQueries.read(runtime, effectiveTenant, plan, schema, query, intercept), context);
|
|
1585
1931
|
},
|
|
1586
1932
|
/**
|
|
1587
1933
|
* Routes a handler read to the registered state target before reaching storage.
|
|
@@ -1592,9 +1938,10 @@ const ProcessManagerQueries = Object.freeze({
|
|
|
1592
1938
|
* @param plan Compiled read criteria.
|
|
1593
1939
|
* @param schema State schema supplied by the handler.
|
|
1594
1940
|
* @param query Wire query carrying the actor and requested fields.
|
|
1941
|
+
* @param intercept Durable Agent snapshot interception, when active.
|
|
1595
1942
|
* @returns Detached state snapshots decoded with the caller's schema.
|
|
1596
1943
|
*/
|
|
1597
|
-
async read(runtime, tenantId, plan, schema, query) {
|
|
1944
|
+
async read(runtime, tenantId, plan, schema, query, intercept) {
|
|
1598
1945
|
const routes = RegisteredTargets.forStand(runtime.stand);
|
|
1599
1946
|
const typeUrl = TypeUrls.derive(schema);
|
|
1600
1947
|
const target = routes?.find(typeUrl);
|
|
@@ -1609,7 +1956,23 @@ const ProcessManagerQueries = Object.freeze({
|
|
|
1609
1956
|
if (target !== undefined)
|
|
1610
1957
|
routes?.validate(target, plan);
|
|
1611
1958
|
const destination = EffectiveTenants.destination(tenantId, target?.context.isMultitenant ?? runtime.context.multitenant);
|
|
1612
|
-
const
|
|
1959
|
+
const live = () => this.load(target?.context.stand() ?? runtime.stand, registeredSchema, plan, destination, query, schema);
|
|
1960
|
+
return intercept === undefined ? live() : intercept(schema, query, live);
|
|
1961
|
+
},
|
|
1962
|
+
/**
|
|
1963
|
+
* Reads the authorized target after optional Agent snapshot reuse is checked.
|
|
1964
|
+
*
|
|
1965
|
+
* @typeParam Schema Generated Projection state descriptor.
|
|
1966
|
+
* @param stand Stand selected by the authorized query route.
|
|
1967
|
+
* @param registeredSchema Descriptor registered for the target.
|
|
1968
|
+
* @param plan Compiled query criteria.
|
|
1969
|
+
* @param destination Effective tenant selected for the target.
|
|
1970
|
+
* @param query Original typed Query envelope.
|
|
1971
|
+
* @param schema Descriptor expected by the caller.
|
|
1972
|
+
* @returns Detached Projection state snapshots.
|
|
1973
|
+
*/
|
|
1974
|
+
async load(stand, registeredSchema, plan, destination, query, schema) {
|
|
1975
|
+
const results = await QueryReader.read(stand, registeredSchema, plan, destination, 10_000, query);
|
|
1613
1976
|
return Object.freeze(results.map((result) => clone(schema, result.state)));
|
|
1614
1977
|
},
|
|
1615
1978
|
});
|
|
@@ -1654,32 +2017,65 @@ class ProcessManagerExecutionSupport {
|
|
|
1654
2017
|
async load(entityId, options) {
|
|
1655
2018
|
const stored = await standAccess.readCurrent(this.#runtime.stand, this.#repository.stateSchema, entityId, options);
|
|
1656
2019
|
const entity = RepositoryEntities.instantiate(this.#repository, entityId, stored, false);
|
|
1657
|
-
const { commits, events, storageInput } = RepositoryEntities.bindStorage(this.#repository, this.#runtime, options.tenantId, entityId, entity);
|
|
1658
|
-
return RepositoryEntities.loaded(entity, stored, commits, events, storageInput, this.#repository.stateSchema, entityId);
|
|
2020
|
+
const { commits, events, agentHistory, storageInput } = RepositoryEntities.bindStorage(this.#repository, this.#runtime, options.tenantId, entityId, entity);
|
|
2021
|
+
return RepositoryEntities.loaded(entity, stored, commits, events, agentHistory, storageInput, this.#repository.stateSchema, entityId);
|
|
1659
2022
|
}
|
|
1660
2023
|
/**
|
|
1661
2024
|
* Stores changed Process Manager state or produced Events under one Version.
|
|
1662
2025
|
*
|
|
1663
2026
|
* @param loaded Process Manager and commit storage.
|
|
1664
2027
|
* @param options Stand tenant selection.
|
|
1665
|
-
* @param
|
|
2028
|
+
* @param diagnostics Input and produced Events to retain when configured.
|
|
2029
|
+
* @param producedEvents Delivery Events to save before current state.
|
|
1666
2030
|
* @returns `true` after a successful commit or unchanged no-op.
|
|
1667
2031
|
*/
|
|
1668
|
-
async commit(loaded, options,
|
|
2032
|
+
async commit(loaded, options, diagnostics, producedEvents) {
|
|
1669
2033
|
const version = RepositoryEntities.repositoryVersion(loaded.entity);
|
|
1670
2034
|
const priorNumber = loaded.current?.version?.number ?? 0;
|
|
1671
2035
|
if (version.number === priorNumber) {
|
|
1672
|
-
await this.#appendUnchangedDiagnostics(loaded,
|
|
2036
|
+
await this.#appendUnchangedDiagnostics(loaded, diagnostics);
|
|
2037
|
+
const store = new EventStore(this.#runtime.context, this.#runtime.storageFactory);
|
|
2038
|
+
try {
|
|
2039
|
+
await store.appendAll(producedEvents);
|
|
2040
|
+
}
|
|
2041
|
+
finally {
|
|
2042
|
+
store.close();
|
|
2043
|
+
}
|
|
2044
|
+
await this.#appendAgentEvents(loaded, producedEvents);
|
|
1673
2045
|
return true;
|
|
1674
2046
|
}
|
|
1675
2047
|
const entityId = loaded.entity.id;
|
|
1676
2048
|
const state = RepositoryEntities.repositoryState(loaded.entity);
|
|
1677
2049
|
const lifecycle = RepositoryEntities.repositoryLifecycle(loaded.entity);
|
|
1678
2050
|
const deferred = await standAccess.deferUpdate(this.#runtime.stand, this.#repository.stateSchema, state, RepositoryStand.standUpdateOptions(options.tenantId, version, lifecycle), this.#repository.metadata);
|
|
1679
|
-
await this.#storeProcessManagerRecord(loaded, entityId,
|
|
1680
|
-
this.#notifyProcessManager(deferred,
|
|
2051
|
+
await this.#storeProcessManagerRecord(loaded, entityId, diagnostics, producedEvents, deferred);
|
|
2052
|
+
this.#notifyProcessManager(deferred, diagnostics);
|
|
2053
|
+
await this.#appendAgentEvents(loaded, producedEvents);
|
|
1681
2054
|
return true;
|
|
1682
2055
|
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Stores original envelopes emitted by an Agent under its repository.
|
|
2058
|
+
*
|
|
2059
|
+
* @param loaded Restored Agent with history storage.
|
|
2060
|
+
* @param events Emitted domain Event envelopes.
|
|
2061
|
+
* @returns Completion after every append.
|
|
2062
|
+
*/
|
|
2063
|
+
async #appendAgentEvents(loaded, events) {
|
|
2064
|
+
if (this.#repository.entityFamily !== "agent")
|
|
2065
|
+
return;
|
|
2066
|
+
const history = loaded.agentHistory;
|
|
2067
|
+
if (history === undefined)
|
|
2068
|
+
throw new Error("Agent history storage is required.");
|
|
2069
|
+
const id = loaded.entity.id;
|
|
2070
|
+
for (const event of events) {
|
|
2071
|
+
if (event.context?.timestamp === undefined)
|
|
2072
|
+
throw new Error("Agent emitted Event requires its original timestamp.");
|
|
2073
|
+
await history.append(id, create(AgentHistoryEntrySchema, {
|
|
2074
|
+
occurredAt: event.context.timestamp,
|
|
2075
|
+
item: { case: "domainEvent", value: event },
|
|
2076
|
+
}));
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
1683
2079
|
/**
|
|
1684
2080
|
* Writes configured Process Manager diagnostics without storing unchanged state.
|
|
1685
2081
|
*
|
|
@@ -1698,13 +2094,14 @@ class ProcessManagerExecutionSupport {
|
|
|
1698
2094
|
*
|
|
1699
2095
|
* @param loaded Process Manager and commit storage.
|
|
1700
2096
|
* @param entityId Process Manager identifier.
|
|
1701
|
-
* @param
|
|
2097
|
+
* @param diagnostics Diagnostic Events to retain when configured.
|
|
2098
|
+
* @param producedEvents Delivery Events to save before current state.
|
|
1702
2099
|
* @param deferred Stand update to notify or cancel.
|
|
1703
2100
|
* @returns Completion after durable persistence.
|
|
1704
2101
|
*/
|
|
1705
|
-
async #storeProcessManagerRecord(loaded, entityId,
|
|
2102
|
+
async #storeProcessManagerRecord(loaded, entityId, diagnostics, producedEvents, deferred) {
|
|
1706
2103
|
try {
|
|
1707
|
-
await this.#commitProcessManagerRecord(loaded, entityId,
|
|
2104
|
+
await this.#commitProcessManagerRecord(loaded, entityId, diagnostics, producedEvents, deferred);
|
|
1708
2105
|
entityStateHistoryCaches.get(loaded.entity)?.clear();
|
|
1709
2106
|
}
|
|
1710
2107
|
catch (error) {
|
|
@@ -1717,11 +2114,12 @@ class ProcessManagerExecutionSupport {
|
|
|
1717
2114
|
*
|
|
1718
2115
|
* @param loaded Loaded Process Manager and commit storage.
|
|
1719
2116
|
* @param entityId Process Manager identifier.
|
|
1720
|
-
* @param
|
|
2117
|
+
* @param diagnostics Diagnostic Events to retain when configured.
|
|
2118
|
+
* @param producedEvents Delivery Events to save before current state.
|
|
1721
2119
|
* @param deferred Stand update containing the prepared record.
|
|
1722
2120
|
* @returns Completion after the storage commit.
|
|
1723
2121
|
*/
|
|
1724
|
-
#commitProcessManagerRecord(loaded, entityId,
|
|
2122
|
+
#commitProcessManagerRecord(loaded, entityId, diagnostics, producedEvents, deferred) {
|
|
1725
2123
|
const history = RepositoryStorage.historyConfiguration(this.#repository);
|
|
1726
2124
|
const record = RepositoryStand.preparedRecord(this.#repository, deferred, entityId);
|
|
1727
2125
|
return loaded.commits.commit({
|
|
@@ -1731,8 +2129,9 @@ class ProcessManagerExecutionSupport {
|
|
|
1731
2129
|
next: record,
|
|
1732
2130
|
...(history.stateHistory ? { states: [record] } : {}),
|
|
1733
2131
|
...(history.processManagerEventHistory
|
|
1734
|
-
? { diagnostics:
|
|
2132
|
+
? { diagnostics: diagnostics.map((event) => clone(EventSchema, event)) }
|
|
1735
2133
|
: {}),
|
|
2134
|
+
events: producedEvents.map((event) => clone(EventSchema, event)),
|
|
1736
2135
|
});
|
|
1737
2136
|
}
|
|
1738
2137
|
/**
|
|
@@ -1751,7 +2150,1271 @@ class ProcessManagerExecutionSupport {
|
|
|
1751
2150
|
}
|
|
1752
2151
|
}
|
|
1753
2152
|
/**
|
|
1754
|
-
*
|
|
2153
|
+
* Advances admitted Agent work only under a provider claim and exact record fence.
|
|
2154
|
+
*/
|
|
2155
|
+
const AgentExecutionRunner = {
|
|
2156
|
+
/**
|
|
2157
|
+
* Processes one accepted Agent transition under its provider claim.
|
|
2158
|
+
*
|
|
2159
|
+
* @param repository Registered Agent repository.
|
|
2160
|
+
* @param tenantId Tenant selected for this invocation.
|
|
2161
|
+
* @param key Exact accepted invocation key.
|
|
2162
|
+
* @param signal Cancellation signal for the current accepted execution.
|
|
2163
|
+
* @returns When the fenced repository operation completes.
|
|
2164
|
+
*/
|
|
2165
|
+
async run(repository, tenantId, key, signal) {
|
|
2166
|
+
if (signal?.aborted)
|
|
2167
|
+
return;
|
|
2168
|
+
const runtime = repositoryRuntimes.get(repository);
|
|
2169
|
+
if (runtime?.ai === undefined || repository.entityFamily !== "agent")
|
|
2170
|
+
throw new Error("Agent execution requires a bound Agent repository and AI registry.");
|
|
2171
|
+
const input = RepositoryStorage.entityStorageInput(repository, RepositoryTenants.storageContextForTenant(runtime.context, tenantId));
|
|
2172
|
+
const storage = RepositoryStorage.openRepositoryEntityStorage(repository, runtime.storageFactory, input);
|
|
2173
|
+
const executions = storage.agentExecution;
|
|
2174
|
+
if (executions === undefined)
|
|
2175
|
+
throw new Error("Agent execution storage is required.");
|
|
2176
|
+
const token = randomUUID();
|
|
2177
|
+
const claimed = await executions.claim(key, token, addMillis(Time.currentTime(), 30_000));
|
|
2178
|
+
if (claimed === undefined)
|
|
2179
|
+
return;
|
|
2180
|
+
const session = new AgentExecutionSession(executions, claimed.record, token, claimed.preferences);
|
|
2181
|
+
await this.runClaimed(repository, runtime, tenantId, session, signal);
|
|
2182
|
+
},
|
|
2183
|
+
/**
|
|
2184
|
+
* Updates and releases one exact claim while its saved work advances.
|
|
2185
|
+
*
|
|
2186
|
+
* @param repository Registered Entity repository for this operation.
|
|
2187
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2188
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2189
|
+
* @param session Fenced Agent execution session.
|
|
2190
|
+
* @param signal Cancellation signal for the current accepted execution.
|
|
2191
|
+
* @returns When the fenced repository operation completes.
|
|
2192
|
+
*/
|
|
2193
|
+
async runClaimed(repository, runtime, tenantId, session, signal) {
|
|
2194
|
+
const abort = () => {
|
|
2195
|
+
session.stop();
|
|
2196
|
+
};
|
|
2197
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
2198
|
+
if (signal?.aborted)
|
|
2199
|
+
abort();
|
|
2200
|
+
const renewal = setInterval(() => {
|
|
2201
|
+
// spine-log-boundary: server.agent_lease_renewal
|
|
2202
|
+
void session.renew(addMillis(Time.currentTime(), 30_000)).catch(() => undefined);
|
|
2203
|
+
}, 10_000);
|
|
2204
|
+
try {
|
|
2205
|
+
if (session.signal.aborted)
|
|
2206
|
+
return;
|
|
2207
|
+
await this.advanceClaimed(repository, runtime, tenantId, session);
|
|
2208
|
+
}
|
|
2209
|
+
catch (error) {
|
|
2210
|
+
if (signal?.aborted)
|
|
2211
|
+
return;
|
|
2212
|
+
await this.handleRunFailure(repository, runtime, tenantId, session, error);
|
|
2213
|
+
}
|
|
2214
|
+
finally {
|
|
2215
|
+
clearInterval(renewal);
|
|
2216
|
+
signal?.removeEventListener("abort", abort);
|
|
2217
|
+
session.stop();
|
|
2218
|
+
}
|
|
2219
|
+
},
|
|
2220
|
+
/**
|
|
2221
|
+
* Handles transient failures while giving expired work a final audit.
|
|
2222
|
+
*
|
|
2223
|
+
* @param repository Registered Entity repository for this operation.
|
|
2224
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2225
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2226
|
+
* @param session Fenced Agent execution session.
|
|
2227
|
+
* @param error Failure raised while processing this accepted signal.
|
|
2228
|
+
* @returns When the fenced repository operation completes.
|
|
2229
|
+
*/
|
|
2230
|
+
async handleRunFailure(repository, runtime, tenantId, session, error) {
|
|
2231
|
+
if (session.record().status ===
|
|
2232
|
+
AgentInvocationStatus.AGENT_INVOCATION_COMPLETED_PENDING_COMMANDS ||
|
|
2233
|
+
session.record().status === AgentInvocationStatus.AGENT_INVOCATION_COMPLETED)
|
|
2234
|
+
throw error;
|
|
2235
|
+
if (error instanceof AgentUncertainAttemptError) {
|
|
2236
|
+
const reason = this.hasUnresolvedToolWrite(session.record())
|
|
2237
|
+
? "TOOL_OUTCOME_UNKNOWN"
|
|
2238
|
+
: "MODEL_OUTCOME_UNKNOWN";
|
|
2239
|
+
return this.terminate(repository, runtime, tenantId, session, reason);
|
|
2240
|
+
}
|
|
2241
|
+
if (error instanceof AgentExecutionFault) {
|
|
2242
|
+
if (session.signal.aborted)
|
|
2243
|
+
throw error;
|
|
2244
|
+
return this.terminate(repository, runtime, tenantId, session, error.reason);
|
|
2245
|
+
}
|
|
2246
|
+
if (this.deadlineExpired(session.record()))
|
|
2247
|
+
return this.terminate(repository, runtime, tenantId, session, "DEADLINE_EXCEEDED");
|
|
2248
|
+
throw error;
|
|
2249
|
+
},
|
|
2250
|
+
/**
|
|
2251
|
+
* Processes one claimed record according to its saved completion or request state.
|
|
2252
|
+
*
|
|
2253
|
+
* @param repository Registered Entity repository for this operation.
|
|
2254
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2255
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2256
|
+
* @param session Fenced Agent execution session.
|
|
2257
|
+
* @returns When the fenced repository operation completes.
|
|
2258
|
+
*/
|
|
2259
|
+
async advanceClaimed(repository, runtime, tenantId, session) {
|
|
2260
|
+
if (this.hasUnresolvedPhysicalRequest(session.record()))
|
|
2261
|
+
return this.terminate(repository, runtime, tenantId, session, this.hasUnresolvedToolWrite(session.record())
|
|
2262
|
+
? "TOOL_OUTCOME_UNKNOWN"
|
|
2263
|
+
: "MODEL_OUTCOME_UNKNOWN");
|
|
2264
|
+
if (session.record().status === AgentInvocationStatus.AGENT_INVOCATION_COMPLETED_PENDING_COMMANDS)
|
|
2265
|
+
return this.deliverCommands(runtime, session);
|
|
2266
|
+
if (this.deadlineExpired(session.record()))
|
|
2267
|
+
return this.terminate(repository, runtime, tenantId, session, "DEADLINE_EXCEEDED");
|
|
2268
|
+
return this.execute(repository, runtime, tenantId, session);
|
|
2269
|
+
},
|
|
2270
|
+
/**
|
|
2271
|
+
* Tests the original saved deadline without granting recovery fresh time.
|
|
2272
|
+
*
|
|
2273
|
+
* @param record Durable Agent execution record.
|
|
2274
|
+
* @returns Whether the checked condition holds.
|
|
2275
|
+
*/
|
|
2276
|
+
deadlineExpired(record) {
|
|
2277
|
+
const deadline = record.started?.deadline;
|
|
2278
|
+
return (deadline !== undefined &&
|
|
2279
|
+
Time.currentTimeMillis() >=
|
|
2280
|
+
Number(deadline.seconds) * 1_000 + Math.floor(deadline.nanos / 1_000_000));
|
|
2281
|
+
},
|
|
2282
|
+
/**
|
|
2283
|
+
* Checks for a dispatched model reservation whose response was never saved.
|
|
2284
|
+
*
|
|
2285
|
+
* @param record Durable Agent execution record.
|
|
2286
|
+
* @returns Whether the checked condition holds.
|
|
2287
|
+
*/
|
|
2288
|
+
hasUnresolvedPhysicalRequest(record) {
|
|
2289
|
+
const model = record.journal.some((entry) => {
|
|
2290
|
+
if (entry.evidence.case !== "attempt")
|
|
2291
|
+
return false;
|
|
2292
|
+
const attempt = entry.evidence.value;
|
|
2293
|
+
return (attempt.transportReserved &&
|
|
2294
|
+
attempt.response.case === undefined &&
|
|
2295
|
+
!this.hasSavedOperationResult(record, attempt.operation?.value));
|
|
2296
|
+
});
|
|
2297
|
+
return model || this.hasUnresolvedToolWrite(record);
|
|
2298
|
+
},
|
|
2299
|
+
/**
|
|
2300
|
+
* Checks for a dispatched write with no recorded tool result.
|
|
2301
|
+
*
|
|
2302
|
+
* @param record Durable Agent execution record.
|
|
2303
|
+
* @returns Whether the checked condition holds.
|
|
2304
|
+
*/
|
|
2305
|
+
hasUnresolvedToolWrite(record) {
|
|
2306
|
+
return record.journal.some((entry) => {
|
|
2307
|
+
if (entry.evidence.case !== "tool")
|
|
2308
|
+
return false;
|
|
2309
|
+
const tool = entry.evidence.value;
|
|
2310
|
+
return (tool.request?.effect === ToolEffect.WRITE &&
|
|
2311
|
+
tool.response === undefined &&
|
|
2312
|
+
(tool.dispatched || tool.outcomeUnknown) &&
|
|
2313
|
+
!this.hasSavedOperationResult(record, tool.operation?.value));
|
|
2314
|
+
});
|
|
2315
|
+
},
|
|
2316
|
+
/**
|
|
2317
|
+
* Checks for a settled named result even when physical evidence is incomplete.
|
|
2318
|
+
*
|
|
2319
|
+
* @param record Durable Agent execution record.
|
|
2320
|
+
* @param id Saved signal, operation, or Entity identifier.
|
|
2321
|
+
* @returns Whether the checked condition holds.
|
|
2322
|
+
*/
|
|
2323
|
+
hasSavedOperationResult(record, id) {
|
|
2324
|
+
return record.journal.some((entry) => entry.evidence.case === "operation" &&
|
|
2325
|
+
entry.evidence.value.operation?.value === id &&
|
|
2326
|
+
entry.evidence.value.result.case !== undefined);
|
|
2327
|
+
},
|
|
2328
|
+
/**
|
|
2329
|
+
* Records termination of an unreplayable physical request under the provider fence.
|
|
2330
|
+
*
|
|
2331
|
+
* @param repository Registered Entity repository for this operation.
|
|
2332
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2333
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2334
|
+
* @param session Fenced Agent execution session.
|
|
2335
|
+
* @param reason Safe terminal reason retained in Agent audit.
|
|
2336
|
+
* @returns When the fenced repository operation completes.
|
|
2337
|
+
*/
|
|
2338
|
+
async terminate(repository, runtime, tenantId, session, reason) {
|
|
2339
|
+
const record = session.record();
|
|
2340
|
+
const event = this.terminationEvent(repository, runtime, tenantId, record, reason);
|
|
2341
|
+
const occurredAt = event.context?.timestamp;
|
|
2342
|
+
if (occurredAt === undefined)
|
|
2343
|
+
throw new Error("Agent termination Event requires Time.");
|
|
2344
|
+
runtime.registerSystemEventSchema(AgentInvocationTerminatedSchema);
|
|
2345
|
+
await session.update((next) => {
|
|
2346
|
+
next.status = AgentInvocationStatus.AGENT_INVOCATION_TERMINATED;
|
|
2347
|
+
next.terminalFailure = create(AiDiagnosticIdSchema, { value: randomUUID() });
|
|
2348
|
+
return next;
|
|
2349
|
+
}, [
|
|
2350
|
+
create(AgentHistoryEntrySchema, {
|
|
2351
|
+
occurredAt,
|
|
2352
|
+
item: { case: "systemEvent", value: event },
|
|
2353
|
+
}),
|
|
2354
|
+
]);
|
|
2355
|
+
await runtime.publishAgentSystemEvent(event);
|
|
2356
|
+
},
|
|
2357
|
+
/**
|
|
2358
|
+
* Builds one original termination envelope for a saved terminal reason.
|
|
2359
|
+
*
|
|
2360
|
+
* @param repository Registered Entity repository for this operation.
|
|
2361
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2362
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2363
|
+
* @param record Durable Agent execution record.
|
|
2364
|
+
* @param reason Safe terminal reason retained in Agent audit.
|
|
2365
|
+
* @returns Original AgentInvocationTerminated System Event envelope.
|
|
2366
|
+
*/
|
|
2367
|
+
terminationEvent(repository, runtime, tenantId, record, reason) {
|
|
2368
|
+
const accepted = record.accepted;
|
|
2369
|
+
if (accepted?.recipientId === undefined)
|
|
2370
|
+
throw new Error("Agent termination requires the original typed recipient.");
|
|
2371
|
+
const scope = AgentModelSelection.scope(accepted, repository.stateSchema, tenantId);
|
|
2372
|
+
const unresolvedToolCalls = this.unresolvedToolCalls(record);
|
|
2373
|
+
const payload = create(AgentInvocationTerminatedSchema, {
|
|
2374
|
+
agent: scope.agent,
|
|
2375
|
+
sourceSignal: scope.source,
|
|
2376
|
+
reason,
|
|
2377
|
+
unresolvedAttempts: this.unresolvedAttempts(record),
|
|
2378
|
+
unresolvedToolCalls,
|
|
2379
|
+
terminatedAt: Time.currentTime(),
|
|
2380
|
+
});
|
|
2381
|
+
return AgentInteractionEvents.envelope(accepted, runtime.signalMetadata, accepted.recipientId, AgentInvocationTerminatedSchema, payload);
|
|
2382
|
+
},
|
|
2383
|
+
/**
|
|
2384
|
+
* Returns IDs only for physically reserved model requests without saved outcomes.
|
|
2385
|
+
*
|
|
2386
|
+
* @param record Durable execution journal.
|
|
2387
|
+
* @returns Original attempt IDs for unresolved physical sends.
|
|
2388
|
+
*/
|
|
2389
|
+
unresolvedAttempts(record) {
|
|
2390
|
+
return record.journal.flatMap((entry) => {
|
|
2391
|
+
if (entry.evidence.case !== "attempt")
|
|
2392
|
+
return [];
|
|
2393
|
+
const attempt = entry.evidence.value;
|
|
2394
|
+
return attempt.transportReserved &&
|
|
2395
|
+
attempt.response.case === undefined &&
|
|
2396
|
+
!this.hasSavedOperationResult(record, attempt.operation?.value) &&
|
|
2397
|
+
attempt.attempt !== undefined
|
|
2398
|
+
? [attempt.attempt]
|
|
2399
|
+
: [];
|
|
2400
|
+
});
|
|
2401
|
+
},
|
|
2402
|
+
/**
|
|
2403
|
+
* Returns exact IDs for dispatched write effects without saved results.
|
|
2404
|
+
*
|
|
2405
|
+
* @param record Durable Agent execution record.
|
|
2406
|
+
* @returns Original ToolCall IDs for unresolved dispatched writes.
|
|
2407
|
+
*/
|
|
2408
|
+
unresolvedToolCalls(record) {
|
|
2409
|
+
return record.journal.flatMap((entry) => {
|
|
2410
|
+
if (entry.evidence.case !== "tool")
|
|
2411
|
+
return [];
|
|
2412
|
+
const tool = entry.evidence.value;
|
|
2413
|
+
return tool.request?.effect === ToolEffect.WRITE &&
|
|
2414
|
+
tool.response === undefined &&
|
|
2415
|
+
(tool.dispatched || tool.outcomeUnknown) &&
|
|
2416
|
+
tool.request.call !== undefined
|
|
2417
|
+
? [tool.request.call]
|
|
2418
|
+
: [];
|
|
2419
|
+
});
|
|
2420
|
+
},
|
|
2421
|
+
/**
|
|
2422
|
+
* Persists start facts, runs selected handlers, and conditionally completes a no-op.
|
|
2423
|
+
*
|
|
2424
|
+
* @param repository Agent registration.
|
|
2425
|
+
* @param runtime Bound services.
|
|
2426
|
+
* @param tenantId Complete tenant, when applicable.
|
|
2427
|
+
* @param session Fenced Agent execution session.
|
|
2428
|
+
* @returns When the fenced repository operation completes.
|
|
2429
|
+
*/
|
|
2430
|
+
async execute(repository, runtime, tenantId, session) {
|
|
2431
|
+
const accepted = session.record().accepted;
|
|
2432
|
+
if (accepted?.key === undefined || accepted.recipientId === undefined)
|
|
2433
|
+
throw new Error("Claimed Agent invocation lacks accepted source and recipient.");
|
|
2434
|
+
const entityId = InboxMessages.targetEntityId(accepted.recipientId, repository.idField);
|
|
2435
|
+
const support = new ProcessManagerExecutionSupport(repository, runtime);
|
|
2436
|
+
const loaded = await support.load(entityId, tenantId === undefined ? {} : { tenantId });
|
|
2437
|
+
const handlers = this.handlers(repository, accepted);
|
|
2438
|
+
const configuration = repositoryAccess.agentConfiguration(repository);
|
|
2439
|
+
if (runtime.ai === undefined)
|
|
2440
|
+
throw new Error("Agent execution requires a bound AI registry.");
|
|
2441
|
+
this.assertRevisions(repository, accepted, handlers, runtime.ai, configuration);
|
|
2442
|
+
const record = session.record().started === undefined
|
|
2443
|
+
? await this.start(repository, runtime, session, loaded, entityId, tenantId)
|
|
2444
|
+
: await this.selectMissingModels(repository, runtime, session, accepted, tenantId);
|
|
2445
|
+
if (session.signal.aborted)
|
|
2446
|
+
return;
|
|
2447
|
+
const outputs = await this.completeExecution(repository, runtime, tenantId, session, loaded, accepted, handlers, entityId, record.started?.initialVersion ?? create(VersionSchema));
|
|
2448
|
+
await this.deliverInitial(runtime, session, outputs);
|
|
2449
|
+
},
|
|
2450
|
+
/**
|
|
2451
|
+
* Creates or rejects the draft before one conditional Entity completion.
|
|
2452
|
+
*
|
|
2453
|
+
* @param repository Registered Entity repository for this operation.
|
|
2454
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2455
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2456
|
+
* @param session Fenced Agent execution session.
|
|
2457
|
+
* @param loaded Loaded Entity and its repository storage state.
|
|
2458
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
2459
|
+
* @param handlers Generated handlers in their saved order.
|
|
2460
|
+
* @param entityId Typed recipient Entity identifier.
|
|
2461
|
+
* @param initialVersion Entity version before selected handlers run.
|
|
2462
|
+
* @returns Record after conditional Entity and execution completion.
|
|
2463
|
+
*/
|
|
2464
|
+
async completeExecution(repository, runtime, tenantId, session, loaded, accepted, handlers, entityId, initialVersion) {
|
|
2465
|
+
const outputs = await this.outputsOrRejection(repository, runtime, tenantId, session, loaded.entity, accepted, handlers, entityId, initialVersion);
|
|
2466
|
+
await this.complete(repository, runtime, tenantId, session, loaded, entityId, outputs);
|
|
2467
|
+
return outputs;
|
|
2468
|
+
},
|
|
2469
|
+
/**
|
|
2470
|
+
* Rejects a Command draft and persists only its declared rejection.
|
|
2471
|
+
*
|
|
2472
|
+
* @param repository Registered Entity repository for this operation.
|
|
2473
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2474
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2475
|
+
* @param session Fenced Agent execution session.
|
|
2476
|
+
* @param entity Loaded recipient Entity instance.
|
|
2477
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
2478
|
+
* @param handlers Generated handlers in their saved order.
|
|
2479
|
+
* @param entityId Typed recipient Entity identifier.
|
|
2480
|
+
* @param initialVersion Entity version before selected handlers run.
|
|
2481
|
+
* @returns Prepared outgoing signals, or only the declared rejection.
|
|
2482
|
+
*/
|
|
2483
|
+
async outputsOrRejection(repository, runtime, tenantId, session, entity, accepted, handlers, entityId, initialVersion) {
|
|
2484
|
+
try {
|
|
2485
|
+
return await this.produceOutputs(repository, runtime, tenantId, session, entity, accepted, handlers, entityId, initialVersion);
|
|
2486
|
+
}
|
|
2487
|
+
catch (error) {
|
|
2488
|
+
if (!RejectionThrowable.is(error) || accepted.signal.case !== "command")
|
|
2489
|
+
throw error;
|
|
2490
|
+
const handler = handlers[0];
|
|
2491
|
+
if (handler === undefined)
|
|
2492
|
+
throw new Error("Agent rejection requires a Command assignee.");
|
|
2493
|
+
RepositorySignals.requireDeclaredRejection(handler.handler, error);
|
|
2494
|
+
session.discardPreferenceChanges();
|
|
2495
|
+
return [this.rejectionOutput(runtime, repository, accepted.signal.value, entityId, error)];
|
|
2496
|
+
}
|
|
2497
|
+
},
|
|
2498
|
+
/**
|
|
2499
|
+
* Wraps the original rejection Event for one normal EventBus handoff.
|
|
2500
|
+
*
|
|
2501
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2502
|
+
* @param repository Registered Entity repository for this operation.
|
|
2503
|
+
* @param command Original typed Command envelope.
|
|
2504
|
+
* @param entityId Typed recipient Entity identifier.
|
|
2505
|
+
* @param error Failure raised while processing this accepted signal.
|
|
2506
|
+
* @returns Prepared original rejection Event for one normal EventBus handoff.
|
|
2507
|
+
*/
|
|
2508
|
+
rejectionOutput(runtime, repository, command, entityId, error) {
|
|
2509
|
+
return {
|
|
2510
|
+
case: "event",
|
|
2511
|
+
value: RepositorySignals.rejectionEvent(runtime, repository, command, entityId, error),
|
|
2512
|
+
};
|
|
2513
|
+
},
|
|
2514
|
+
/**
|
|
2515
|
+
* Restores handler results and binds their declared original output envelopes.
|
|
2516
|
+
*
|
|
2517
|
+
* @param repository Registered Entity repository for this operation.
|
|
2518
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2519
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2520
|
+
* @param session Fenced Agent execution session.
|
|
2521
|
+
* @param entity Loaded recipient Entity instance.
|
|
2522
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
2523
|
+
* @param handlers Generated handlers in their saved order.
|
|
2524
|
+
* @param entityId Typed recipient Entity identifier.
|
|
2525
|
+
* @param initialVersion Entity version before selected handlers run.
|
|
2526
|
+
* @returns Prepared outgoing signals from accepted handler results.
|
|
2527
|
+
*/
|
|
2528
|
+
async produceOutputs(repository, runtime, tenantId, session, entity, accepted, handlers, entityId, initialVersion) {
|
|
2529
|
+
const produced = await this.invoke(repository, runtime, tenantId, session, entity, accepted, handlers);
|
|
2530
|
+
return this.bindOutputs(repository, runtime, accepted, entityId, initialVersion, produced);
|
|
2531
|
+
},
|
|
2532
|
+
/**
|
|
2533
|
+
* Commits changed state and the fenced execution record as one provider mutation.
|
|
2534
|
+
*
|
|
2535
|
+
* @param repository Agent registration.
|
|
2536
|
+
* @param runtime Bounded Context Stand and provider services.
|
|
2537
|
+
* @param tenantId Complete delivery tenant.
|
|
2538
|
+
* @param session Serialized provider claim and current record.
|
|
2539
|
+
* @param loaded Entity after handler transaction commit.
|
|
2540
|
+
* @param entityId Original typed recipient.
|
|
2541
|
+
* @param outputs Original emitted Event or Command envelopes.
|
|
2542
|
+
* @returns Complete record after the atomic provider mutation.
|
|
2543
|
+
*/
|
|
2544
|
+
async complete(repository, runtime, tenantId, session, loaded, entityId, outputs) {
|
|
2545
|
+
await this.authorizePreferenceChanges(repository, runtime, tenantId, session);
|
|
2546
|
+
const version = RepositoryEntities.repositoryVersion(loaded.entity);
|
|
2547
|
+
const preferences = session.preferences();
|
|
2548
|
+
const emitted = this.emittedHistory(outputs);
|
|
2549
|
+
const preferenceEvents = this.preferenceEvents(repository, runtime, tenantId, session);
|
|
2550
|
+
const historyEntries = [...emitted, ...this.preferenceHistory(preferenceEvents)];
|
|
2551
|
+
const makeNext = (record) => this.completionFacts(record, version, outputs, preferences);
|
|
2552
|
+
const completed = await this.commitCompletion(repository, runtime, tenantId, session, loaded, entityId, makeNext, historyEntries);
|
|
2553
|
+
for (const event of preferenceEvents)
|
|
2554
|
+
await runtime.publishAgentSystemEvent(event);
|
|
2555
|
+
return completed;
|
|
2556
|
+
},
|
|
2557
|
+
/**
|
|
2558
|
+
* Checks effective staged model changes before any completion mutation.
|
|
2559
|
+
*
|
|
2560
|
+
* @param repository Agent registration and model policy.
|
|
2561
|
+
* @param runtime Bounded Context and AI registry.
|
|
2562
|
+
* @param tenantId Accepted tenant when applicable.
|
|
2563
|
+
* @param session Fenced Agent execution and staged preferences.
|
|
2564
|
+
* @returns When every effective change is authorized.
|
|
2565
|
+
*/
|
|
2566
|
+
async authorizePreferenceChanges(repository, runtime, tenantId, session) {
|
|
2567
|
+
const policy = repositoryAccess.agentConfiguration(repository).ai;
|
|
2568
|
+
if (policy?.authorizeSelection === undefined)
|
|
2569
|
+
return;
|
|
2570
|
+
const accepted = session.record().accepted;
|
|
2571
|
+
if (runtime.ai === undefined || accepted === undefined)
|
|
2572
|
+
throw new Error("Agent model preference requires accepted start facts.");
|
|
2573
|
+
const deadlineMs = this.savedModelDeadline(session, "preference");
|
|
2574
|
+
const scope = AgentModelSelection.scope(accepted, repository.stateSchema, tenantId);
|
|
2575
|
+
for (const kind of [AiModelKind.GENERATION, AiModelKind.DECISION]) {
|
|
2576
|
+
const previous = this.preferenceModel(session.initialPreferences(), kind);
|
|
2577
|
+
const selected = this.preferenceModel(session.preferences(), kind);
|
|
2578
|
+
if (previous === undefined && selected === undefined)
|
|
2579
|
+
continue;
|
|
2580
|
+
if (previous !== undefined &&
|
|
2581
|
+
selected !== undefined &&
|
|
2582
|
+
equals(ModelRefSchema, previous, selected))
|
|
2583
|
+
continue;
|
|
2584
|
+
await AgentModelSelection.authorizeSelection(runtime.ai, policy, scope, selected, session.signal, deadlineMs);
|
|
2585
|
+
}
|
|
2586
|
+
},
|
|
2587
|
+
/**
|
|
2588
|
+
* Reads an optional saved model deadline and detects incomplete persisted bounds.
|
|
2589
|
+
*
|
|
2590
|
+
* @param session Fenced Agent execution session.
|
|
2591
|
+
* @param context Selection or preference authorization context.
|
|
2592
|
+
* @returns Saved absolute deadline, when configured.
|
|
2593
|
+
*/
|
|
2594
|
+
savedModelDeadline(session, context) {
|
|
2595
|
+
const record = session.record();
|
|
2596
|
+
const deadline = record.started?.deadline;
|
|
2597
|
+
if (deadline === undefined && record.started?.bounds?.deadlineMillis !== undefined)
|
|
2598
|
+
throw new Error(`Agent model ${context} deadline was not saved.`);
|
|
2599
|
+
return deadline === undefined
|
|
2600
|
+
? undefined
|
|
2601
|
+
: Number(deadline.seconds) * 1_000 + Math.floor(deadline.nanos / 1_000_000);
|
|
2602
|
+
},
|
|
2603
|
+
/**
|
|
2604
|
+
* Applies the same provider fence to preference events and Entity completion.
|
|
2605
|
+
*
|
|
2606
|
+
* @param repository Registered Entity repository for this operation.
|
|
2607
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2608
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2609
|
+
* @param session Fenced Agent execution session.
|
|
2610
|
+
* @param loaded Loaded Entity and its repository storage state.
|
|
2611
|
+
* @param entityId Typed recipient Entity identifier.
|
|
2612
|
+
* @param makeNext Callback constructing the next durable execution record.
|
|
2613
|
+
* @param historyEntries Agent history rows included in the fenced mutation.
|
|
2614
|
+
* @returns Saved execution record after conditional completion.
|
|
2615
|
+
*/
|
|
2616
|
+
commitCompletion(repository, runtime, tenantId, session, loaded, entityId, makeNext, historyEntries) {
|
|
2617
|
+
return RepositoryEntities.repositoryChanged(loaded.entity)
|
|
2618
|
+
? this.commitChanged(repository, runtime, tenantId, session, loaded, entityId, makeNext, historyEntries)
|
|
2619
|
+
: session.complete(makeNext, historyEntries);
|
|
2620
|
+
},
|
|
2621
|
+
/**
|
|
2622
|
+
* Wraps original preference Events as indexed Agent history entries.
|
|
2623
|
+
*
|
|
2624
|
+
* @param events Original Event envelopes retained for diagnostics.
|
|
2625
|
+
* @returns Typed Agent history rows for saved preference changes.
|
|
2626
|
+
*/
|
|
2627
|
+
preferenceHistory(events) {
|
|
2628
|
+
return events.map((event) => {
|
|
2629
|
+
const occurredAt = event.context?.timestamp;
|
|
2630
|
+
if (occurredAt === undefined)
|
|
2631
|
+
throw new Error("Agent preference Event requires Time.");
|
|
2632
|
+
return create(AgentHistoryEntrySchema, {
|
|
2633
|
+
occurredAt,
|
|
2634
|
+
item: { case: "systemEvent", value: event },
|
|
2635
|
+
});
|
|
2636
|
+
});
|
|
2637
|
+
},
|
|
2638
|
+
/**
|
|
2639
|
+
* Captures only effective preference changes committed by this signal.
|
|
2640
|
+
*
|
|
2641
|
+
* @param repository Registered Entity repository for this operation.
|
|
2642
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2643
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2644
|
+
* @param session Fenced Agent execution session.
|
|
2645
|
+
* @returns Original model-selection System Events.
|
|
2646
|
+
*/
|
|
2647
|
+
preferenceEvents(repository, runtime, tenantId, session) {
|
|
2648
|
+
const accepted = session.record().accepted;
|
|
2649
|
+
if (accepted?.recipientId === undefined)
|
|
2650
|
+
throw new Error("Agent preference requires accepted work.");
|
|
2651
|
+
const scope = AgentModelSelection.scope(accepted, repository.stateSchema, tenantId);
|
|
2652
|
+
return [AiModelKind.GENERATION, AiModelKind.DECISION].flatMap((kind) => {
|
|
2653
|
+
const previous = this.preferenceModel(session.initialPreferences(), kind);
|
|
2654
|
+
const selected = this.preferenceModel(session.preferences(), kind);
|
|
2655
|
+
if (previous === undefined && selected === undefined)
|
|
2656
|
+
return [];
|
|
2657
|
+
if (previous !== undefined &&
|
|
2658
|
+
selected !== undefined &&
|
|
2659
|
+
equals(ModelRefSchema, previous, selected))
|
|
2660
|
+
return [];
|
|
2661
|
+
return [this.preferenceEvent(accepted, scope, kind, previous, selected, runtime)];
|
|
2662
|
+
});
|
|
2663
|
+
},
|
|
2664
|
+
/**
|
|
2665
|
+
* Builds one effective selection change with its original causal context.
|
|
2666
|
+
*
|
|
2667
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
2668
|
+
* @param scope Accepted actor, source, Bounded Context, and tenant.
|
|
2669
|
+
* @param kind Generation or decision model kind.
|
|
2670
|
+
* @param previous Previously selected model deployment.
|
|
2671
|
+
* @param selected Newly selected model deployment.
|
|
2672
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2673
|
+
* @returns Original model-selection System Event.
|
|
2674
|
+
*/
|
|
2675
|
+
preferenceEvent(accepted, scope, kind, previous, selected, runtime) {
|
|
2676
|
+
const recipientId = accepted.recipientId;
|
|
2677
|
+
if (recipientId === undefined)
|
|
2678
|
+
throw new Error("Agent preference requires a recipient.");
|
|
2679
|
+
const payload = create(SelectionChangedSchema, {
|
|
2680
|
+
agent: scope.agent,
|
|
2681
|
+
sourceSignal: scope.source,
|
|
2682
|
+
kind,
|
|
2683
|
+
...(previous === undefined ? {} : { previousModel: previous }),
|
|
2684
|
+
...(selected === undefined ? {} : { selectedModel: selected }),
|
|
2685
|
+
changedAt: Time.currentTime(),
|
|
2686
|
+
});
|
|
2687
|
+
runtime.registerSystemEventSchema(SelectionChangedSchema);
|
|
2688
|
+
return AgentInteractionEvents.envelope(accepted, runtime.signalMetadata, recipientId, SelectionChangedSchema, payload);
|
|
2689
|
+
},
|
|
2690
|
+
/**
|
|
2691
|
+
* Reads an explicit model selection, including inherited defaults as absence.
|
|
2692
|
+
*
|
|
2693
|
+
* @param preferences Current Agent instance model preferences.
|
|
2694
|
+
* @param kind Generation or decision model kind.
|
|
2695
|
+
* @returns Selected deployment reference, or inherited default.
|
|
2696
|
+
*/
|
|
2697
|
+
preferenceModel(preferences, kind) {
|
|
2698
|
+
const selected = preferences.find((entry) => entry.kind === kind);
|
|
2699
|
+
return selected?.selection.case === "model" ? selected.selection.value : undefined;
|
|
2700
|
+
},
|
|
2701
|
+
/**
|
|
2702
|
+
* Builds the immutable completion image and emitted domain history.
|
|
2703
|
+
*
|
|
2704
|
+
* @param record Durable Agent execution record.
|
|
2705
|
+
* @param version Entity version associated with the signal.
|
|
2706
|
+
* @param outputs Prepared outgoing domain signals.
|
|
2707
|
+
* @param preferences Current Agent instance model preferences.
|
|
2708
|
+
* @returns Record completed with original outputs and resulting version.
|
|
2709
|
+
*/
|
|
2710
|
+
completionFacts(record, version, outputs, preferences) {
|
|
2711
|
+
const initialVersion = record.started?.initialVersion;
|
|
2712
|
+
if (initialVersion === undefined)
|
|
2713
|
+
throw new Error("Agent completion requires its started Version.");
|
|
2714
|
+
const commands = outputs
|
|
2715
|
+
.filter((output) => output.case === "command")
|
|
2716
|
+
.map((output) => create(AgentOutgoingCommandSchema, { command: output.value }));
|
|
2717
|
+
record.status =
|
|
2718
|
+
commands.length === 0
|
|
2719
|
+
? AgentInvocationStatus.AGENT_INVOCATION_COMPLETED
|
|
2720
|
+
: AgentInvocationStatus.AGENT_INVOCATION_COMPLETED_PENDING_COMMANDS;
|
|
2721
|
+
record.completion = create(AgentExecutionCompletionSchema, {
|
|
2722
|
+
initialVersion,
|
|
2723
|
+
resultingVersion: version,
|
|
2724
|
+
preferences: [...preferences],
|
|
2725
|
+
commands,
|
|
2726
|
+
});
|
|
2727
|
+
return record;
|
|
2728
|
+
},
|
|
2729
|
+
/**
|
|
2730
|
+
* Stores only committed domain Events in the Agent's emitted history.
|
|
2731
|
+
*
|
|
2732
|
+
* @param outputs Prepared outgoing domain signals.
|
|
2733
|
+
* @returns Committed domain Event rows for Agent history.
|
|
2734
|
+
*/
|
|
2735
|
+
emittedHistory(outputs) {
|
|
2736
|
+
return outputs.flatMap((output) => {
|
|
2737
|
+
if (output.case !== "event" || output.value.context?.rejection !== undefined)
|
|
2738
|
+
return [];
|
|
2739
|
+
const event = output.value;
|
|
2740
|
+
const occurredAt = event.context?.timestamp;
|
|
2741
|
+
if (occurredAt === undefined)
|
|
2742
|
+
throw new Error("Agent emitted Event requires its original timestamp.");
|
|
2743
|
+
return [
|
|
2744
|
+
create(AgentHistoryEntrySchema, {
|
|
2745
|
+
occurredAt,
|
|
2746
|
+
item: { case: "domainEvent", value: event },
|
|
2747
|
+
}),
|
|
2748
|
+
];
|
|
2749
|
+
});
|
|
2750
|
+
},
|
|
2751
|
+
/**
|
|
2752
|
+
* Commits changed Entity state with the already prepared execution image.
|
|
2753
|
+
*
|
|
2754
|
+
* @param repository Registered Entity repository for this operation.
|
|
2755
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2756
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
2757
|
+
* @param session Fenced Agent execution session.
|
|
2758
|
+
* @param loaded Loaded Entity and its repository storage state.
|
|
2759
|
+
* @param entityId Typed recipient Entity identifier.
|
|
2760
|
+
* @param makeNext Callback constructing the next durable execution record.
|
|
2761
|
+
* @param historyEntries Agent history rows included in the fenced mutation.
|
|
2762
|
+
* @returns Saved execution record after the changed Entity commit.
|
|
2763
|
+
*/
|
|
2764
|
+
async commitChanged(repository, runtime, tenantId, session, loaded, entityId, makeNext, historyEntries) {
|
|
2765
|
+
const version = RepositoryEntities.repositoryVersion(loaded.entity);
|
|
2766
|
+
const state = RepositoryEntities.repositoryState(loaded.entity);
|
|
2767
|
+
const lifecycle = RepositoryEntities.repositoryLifecycle(loaded.entity);
|
|
2768
|
+
const deferred = await standAccess.deferUpdate(runtime.stand, repository.stateSchema, state, RepositoryStand.standUpdateOptions(tenantId, version, lifecycle), repository.metadata);
|
|
2769
|
+
const prepared = RepositoryStand.preparedRecord(repository, deferred, entityId);
|
|
2770
|
+
return this.commitDeferred(repository, session, loaded, entityId, makeNext, historyEntries, deferred, prepared);
|
|
2771
|
+
},
|
|
2772
|
+
/**
|
|
2773
|
+
* Applies the deferred Stand image only after fenced provider completion.
|
|
2774
|
+
*
|
|
2775
|
+
* @param repository Registered Entity repository for this operation.
|
|
2776
|
+
* @param session Fenced Agent execution session.
|
|
2777
|
+
* @param loaded Loaded Entity and its repository storage state.
|
|
2778
|
+
* @param entityId Typed recipient Entity identifier.
|
|
2779
|
+
* @param makeNext Callback constructing the next durable execution record.
|
|
2780
|
+
* @param historyEntries Agent history rows included in the fenced mutation.
|
|
2781
|
+
* @param deferred Prepared Stand update awaiting conditional commit.
|
|
2782
|
+
* @param prepared Prepared conditional Entity commit.
|
|
2783
|
+
* @returns Saved execution record after the deferred Entity commit.
|
|
2784
|
+
*/
|
|
2785
|
+
async commitDeferred(repository, session, loaded, entityId, makeNext, historyEntries, deferred, prepared) {
|
|
2786
|
+
try {
|
|
2787
|
+
const completed = await session.complete(makeNext, historyEntries, {
|
|
2788
|
+
context: loaded.storageInput.context,
|
|
2789
|
+
entity: loaded.storageInput,
|
|
2790
|
+
entityId,
|
|
2791
|
+
next: prepared,
|
|
2792
|
+
...(RepositoryStorage.historyConfiguration(repository).stateHistory
|
|
2793
|
+
? { states: [prepared] }
|
|
2794
|
+
: {}),
|
|
2795
|
+
});
|
|
2796
|
+
deferred.notify();
|
|
2797
|
+
return completed;
|
|
2798
|
+
}
|
|
2799
|
+
catch (error) {
|
|
2800
|
+
deferred.cancel();
|
|
2801
|
+
throw error;
|
|
2802
|
+
}
|
|
2803
|
+
},
|
|
2804
|
+
/**
|
|
2805
|
+
* Binds declared generated Event and Command results in handler order.
|
|
2806
|
+
*
|
|
2807
|
+
* @param repository Agent declaration.
|
|
2808
|
+
* @param runtime Signal metadata factory.
|
|
2809
|
+
* @param accepted Original accepted envelope.
|
|
2810
|
+
* @param entityId Typed recipient.
|
|
2811
|
+
* @param version Entity Version before this transition.
|
|
2812
|
+
* @param produced Handler results in declaration order.
|
|
2813
|
+
* @returns Original outgoing envelopes ready for one conditional completion.
|
|
2814
|
+
*/
|
|
2815
|
+
bindOutputs(repository, runtime, accepted, entityId, version, produced) {
|
|
2816
|
+
const source = accepted.signal;
|
|
2817
|
+
if (source.case !== "command" && source.case !== "event")
|
|
2818
|
+
throw new Error("Agent output requires its accepted source signal.");
|
|
2819
|
+
const routing = repositoryRoutings.get(repository);
|
|
2820
|
+
if (routing === undefined)
|
|
2821
|
+
throw new Error("Agent output requires registered schemas.");
|
|
2822
|
+
return produced.map((signal) => this.bindOutput(repository, runtime, routing, source, entityId, version, signal));
|
|
2823
|
+
},
|
|
2824
|
+
/**
|
|
2825
|
+
* Binds one declared result using its generated Event or Command role.
|
|
2826
|
+
*
|
|
2827
|
+
* @param repository Registered Entity repository for this operation.
|
|
2828
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2829
|
+
* @param routing Registered generated handler routing metadata.
|
|
2830
|
+
* @param source Original accepted source signal.
|
|
2831
|
+
* @param entityId Typed recipient Entity identifier.
|
|
2832
|
+
* @param version Entity version associated with the signal.
|
|
2833
|
+
* @param signal Original domain signal or execution cancellation signal.
|
|
2834
|
+
* @returns Typed outgoing signal with its original envelope.
|
|
2835
|
+
*/
|
|
2836
|
+
bindOutput(repository, runtime, routing, source, entityId, version, signal) {
|
|
2837
|
+
const typeName = EntityInvocation.messageTypeName(signal);
|
|
2838
|
+
const eventSchema = routing.producedEventSchemas.find((entry) => entry.typeName === typeName);
|
|
2839
|
+
if (eventSchema !== undefined)
|
|
2840
|
+
return this.bindEventOutput(repository, runtime, source, entityId, version, signal, eventSchema);
|
|
2841
|
+
const schema = routing.producedCommandSchemas.find((entry) => entry.typeName === typeName);
|
|
2842
|
+
if (schema === undefined)
|
|
2843
|
+
throw new Error(`Agent cannot emit undeclared signal "${typeName}".`);
|
|
2844
|
+
return this.bindCommandOutput(runtime, source, signal, schema);
|
|
2845
|
+
},
|
|
2846
|
+
/**
|
|
2847
|
+
* Creates the original domain Event envelope and producer context.
|
|
2848
|
+
*
|
|
2849
|
+
* @param repository Registered Entity repository for this operation.
|
|
2850
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2851
|
+
* @param source Original accepted source signal.
|
|
2852
|
+
* @param entityId Typed recipient Entity identifier.
|
|
2853
|
+
* @param version Entity version associated with the signal.
|
|
2854
|
+
* @param signal Original domain signal or execution cancellation signal.
|
|
2855
|
+
* @param schema Generated message descriptor for the signal.
|
|
2856
|
+
* @returns Typed original Event envelope for one normal EventBus handoff.
|
|
2857
|
+
*/
|
|
2858
|
+
bindEventOutput(repository, runtime, source, entityId, version, signal, schema) {
|
|
2859
|
+
if (source.case !== "command" && source.case !== "event")
|
|
2860
|
+
throw new Error("Missing source.");
|
|
2861
|
+
const metadata = source.case === "command"
|
|
2862
|
+
? runtime.signalMetadata.eventFromCommand(source.value, { version: version.number })
|
|
2863
|
+
: runtime.signalMetadata.eventFromEvent(source.value, { version: version.number });
|
|
2864
|
+
const event = create(EventSchema, {
|
|
2865
|
+
id: metadata.id,
|
|
2866
|
+
message: AnyMessages.pack(schema, signal),
|
|
2867
|
+
context: RepositorySignals.eventContextWithProducer(metadata.context, repository, entityId, version),
|
|
2868
|
+
});
|
|
2869
|
+
return { case: "event", value: event };
|
|
2870
|
+
},
|
|
2871
|
+
/**
|
|
2872
|
+
* Creates the original Command envelope from its accepted origin.
|
|
2873
|
+
*
|
|
2874
|
+
* @param runtime Bounded Context storage and publication services.
|
|
2875
|
+
* @param source Original accepted source signal.
|
|
2876
|
+
* @param signal Original domain signal or execution cancellation signal.
|
|
2877
|
+
* @param schema Generated message descriptor for the signal.
|
|
2878
|
+
* @returns Typed original Command envelope for durable delivery.
|
|
2879
|
+
*/
|
|
2880
|
+
bindCommandOutput(runtime, source, signal, schema) {
|
|
2881
|
+
if (source.case !== "command" && source.case !== "event")
|
|
2882
|
+
throw new Error("Missing source.");
|
|
2883
|
+
const metadata = source.case === "command"
|
|
2884
|
+
? runtime.signalMetadata.commandFromCommand(source.value)
|
|
2885
|
+
: runtime.signalMetadata.commandFromEvent(source.value);
|
|
2886
|
+
const command = create(CommandSchema, {
|
|
2887
|
+
id: metadata.id,
|
|
2888
|
+
message: AnyMessages.pack(schema, signal),
|
|
2889
|
+
context: metadata.context,
|
|
2890
|
+
});
|
|
2891
|
+
return { case: "command", value: command };
|
|
2892
|
+
},
|
|
2893
|
+
/**
|
|
2894
|
+
* Dispatches every emitted Event to the normal EventBus and advances saved Commands.
|
|
2895
|
+
*
|
|
2896
|
+
* @param runtime Bound normal bus acceptance callbacks.
|
|
2897
|
+
* @param session Fenced record containing pending saved envelopes.
|
|
2898
|
+
* @param outputs Locally produced outputs in original handler order.
|
|
2899
|
+
* @returns When the fenced repository operation completes.
|
|
2900
|
+
*/
|
|
2901
|
+
async deliverInitial(runtime, session, outputs) {
|
|
2902
|
+
let commandIndex = 0;
|
|
2903
|
+
let failed = false;
|
|
2904
|
+
let firstFailure;
|
|
2905
|
+
for (const output of outputs) {
|
|
2906
|
+
try {
|
|
2907
|
+
if (output.case === "event") {
|
|
2908
|
+
await runtime.publishAgentEvent(output.value);
|
|
2909
|
+
}
|
|
2910
|
+
else {
|
|
2911
|
+
await this.deliverCommand(runtime, session, commandIndex);
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
catch (error) {
|
|
2915
|
+
if (!failed) {
|
|
2916
|
+
firstFailure = error;
|
|
2917
|
+
failed = true;
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
if (output.case === "command")
|
|
2921
|
+
commandIndex += 1;
|
|
2922
|
+
}
|
|
2923
|
+
if (failed)
|
|
2924
|
+
throw firstFailure;
|
|
2925
|
+
},
|
|
2926
|
+
/**
|
|
2927
|
+
* Retries pending saved Commands without rerunning the Agent handler.
|
|
2928
|
+
*
|
|
2929
|
+
* @param runtime Bound normal Command bus callbacks.
|
|
2930
|
+
* @param session Fenced record containing saved Commands.
|
|
2931
|
+
* @returns Completion after pending Command attempts.
|
|
2932
|
+
*/
|
|
2933
|
+
async deliverCommands(runtime, session) {
|
|
2934
|
+
for (const [index] of (session.record().completion?.commands ?? []).entries()) {
|
|
2935
|
+
if (session.signal.aborted)
|
|
2936
|
+
return;
|
|
2937
|
+
await this.deliverCommand(runtime, session, index);
|
|
2938
|
+
}
|
|
2939
|
+
},
|
|
2940
|
+
/**
|
|
2941
|
+
* Sends one saved Command under the current claim and records its acknowledgement.
|
|
2942
|
+
*
|
|
2943
|
+
* @param runtime Bound normal Command bus callbacks.
|
|
2944
|
+
* @param session Fenced record containing saved Commands.
|
|
2945
|
+
* @param index Command position in the saved completion.
|
|
2946
|
+
* @returns Completion after the Command is acknowledged or already delivered.
|
|
2947
|
+
*/
|
|
2948
|
+
async deliverCommand(runtime, session, index) {
|
|
2949
|
+
const output = session.record().completion?.commands[index];
|
|
2950
|
+
if (output === undefined || output.delivered)
|
|
2951
|
+
return;
|
|
2952
|
+
const command = output.command;
|
|
2953
|
+
if (command === undefined)
|
|
2954
|
+
throw new Error("Agent outgoing Command has no original envelope.");
|
|
2955
|
+
const key = this.outputKey(output);
|
|
2956
|
+
const plan = output.plan ?? (await runtime.prepareSavedCommand(command));
|
|
2957
|
+
if (output.plan === undefined) {
|
|
2958
|
+
await session.update((current) => {
|
|
2959
|
+
const pending = current.completion?.commands[index];
|
|
2960
|
+
if (pending === undefined ||
|
|
2961
|
+
pending.plan !== undefined ||
|
|
2962
|
+
pending.command?.id?.uuid !== command.id?.uuid)
|
|
2963
|
+
throw new Error("Saved Agent Command changed before plan installation.");
|
|
2964
|
+
pending.plan = plan;
|
|
2965
|
+
return current;
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
await runtime.publishSavedCommand(command, plan);
|
|
2969
|
+
await session.markDelivered([key]);
|
|
2970
|
+
runtime.recordAcceptedSaved(command);
|
|
2971
|
+
},
|
|
2972
|
+
/**
|
|
2973
|
+
* Returns the typed original ID of one completed outgoing Command.
|
|
2974
|
+
*
|
|
2975
|
+
* @param output Prepared outgoing Command.
|
|
2976
|
+
* @returns Original durable Command ID.
|
|
2977
|
+
*/
|
|
2978
|
+
outputKey(output) {
|
|
2979
|
+
if (output.command?.id !== undefined)
|
|
2980
|
+
return output.command.id;
|
|
2981
|
+
throw new Error("Agent outgoing Command requires its original typed ID.");
|
|
2982
|
+
},
|
|
2983
|
+
/**
|
|
2984
|
+
* Validates every stored binding against the current generated selectors.
|
|
2985
|
+
*
|
|
2986
|
+
* @param repository Agent registration.
|
|
2987
|
+
* @param accepted Immutable source and ordered bindings.
|
|
2988
|
+
* @returns Current matching handler metadata.
|
|
2989
|
+
*/
|
|
2990
|
+
handlers(repository, accepted) {
|
|
2991
|
+
const routing = repositoryRoutings.get(repository);
|
|
2992
|
+
if (routing === undefined)
|
|
2993
|
+
throw new Error("Agent execution requires registered handlers.");
|
|
2994
|
+
const current = this.selectedHandlers(routing, accepted);
|
|
2995
|
+
if (accepted.handlers.length !== current.length || current.length === 0)
|
|
2996
|
+
throw new AgentExecutionFault("REVISION_CHANGED", "Accepted Agent handler binding changed before replay.");
|
|
2997
|
+
for (const [ordinal, handler] of current.entries()) {
|
|
2998
|
+
const binding = accepted.handlers[ordinal];
|
|
2999
|
+
if (binding === undefined || !AgentAdmission.matchesHandler(binding, handler, ordinal))
|
|
3000
|
+
throw new AgentExecutionFault("REVISION_CHANGED", "Accepted Agent handler binding changed before replay.");
|
|
3001
|
+
}
|
|
3002
|
+
return current;
|
|
3003
|
+
},
|
|
3004
|
+
/**
|
|
3005
|
+
* Finds current Command or Event handlers from the immutable source envelope.
|
|
3006
|
+
*
|
|
3007
|
+
* @param routing Registered generated handler routing metadata.
|
|
3008
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
3009
|
+
* @returns Generated handlers selected for the accepted signal.
|
|
3010
|
+
*/
|
|
3011
|
+
selectedHandlers(routing, accepted) {
|
|
3012
|
+
if (accepted.signal.case === "command") {
|
|
3013
|
+
const binding = accepted.handlers[0];
|
|
3014
|
+
const current = routing.commandReadiness?.findCommandAssignee(binding?.signalType ?? "");
|
|
3015
|
+
return current === undefined ? [] : [current.registeredHandler];
|
|
3016
|
+
}
|
|
3017
|
+
if (accepted.signal.case !== "event")
|
|
3018
|
+
throw new Error("Agent source envelope is missing.");
|
|
3019
|
+
const event = accepted.signal.value;
|
|
3020
|
+
const packed = EntityInvocation.requireSignalMessage(event.message, "event");
|
|
3021
|
+
const schema = RepositoryRoutes.schemaForTypeUrl(routing.eventSchemas, packed.typeUrl, "event");
|
|
3022
|
+
const value = EntityInvocation.unpackRequired(packed, schema, "event");
|
|
3023
|
+
const external = event.context?.external === true;
|
|
3024
|
+
return [
|
|
3025
|
+
...routing.eventReactors(schema.typeName, value, external),
|
|
3026
|
+
...routing.commandReactions(schema.typeName, value, external),
|
|
3027
|
+
];
|
|
3028
|
+
},
|
|
3029
|
+
/**
|
|
3030
|
+
* Records start facts and the original dispatch System Event before handler execution.
|
|
3031
|
+
*
|
|
3032
|
+
* @param repository Agent registration.
|
|
3033
|
+
* @param runtime Bound services.
|
|
3034
|
+
* @param session Fenced provider claim and current record.
|
|
3035
|
+
* @param loaded Restored Entity and initial Version.
|
|
3036
|
+
* @param entityId Original typed recipient.
|
|
3037
|
+
* @param tenantId Accepted delivery tenant when applicable.
|
|
3038
|
+
* @returns Updated record image for exact subsequent CAS.
|
|
3039
|
+
*/
|
|
3040
|
+
async start(repository, runtime, session, loaded, entityId, tenantId) {
|
|
3041
|
+
const accepted = session.record().accepted;
|
|
3042
|
+
if (accepted === undefined)
|
|
3043
|
+
throw new Error("Agent start requires accepted work.");
|
|
3044
|
+
const started = this.startFacts(repository, runtime, accepted, loaded);
|
|
3045
|
+
const audit = this.startAudit(runtime, repository, accepted, entityId);
|
|
3046
|
+
await session.update((current) => {
|
|
3047
|
+
current.started = started;
|
|
3048
|
+
return current;
|
|
3049
|
+
}, [this.startHistory(audit)]);
|
|
3050
|
+
if (accepted.signal.case === "command")
|
|
3051
|
+
HandlerDispatchPublisher.publishCommand(runtime, audit);
|
|
3052
|
+
else
|
|
3053
|
+
HandlerDispatchPublisher.publishReactor(runtime, audit);
|
|
3054
|
+
return this.selectMissingModels(repository, runtime, session, accepted, tenantId);
|
|
3055
|
+
},
|
|
3056
|
+
/**
|
|
3057
|
+
* Stores the original dispatch audit envelope in mandatory Agent history.
|
|
3058
|
+
*
|
|
3059
|
+
* @param audit Original Agent System event envelope.
|
|
3060
|
+
* @returns Typed Agent history row for the original dispatch Event.
|
|
3061
|
+
*/
|
|
3062
|
+
startHistory(audit) {
|
|
3063
|
+
const occurredAt = audit.context?.timestamp;
|
|
3064
|
+
if (occurredAt === undefined)
|
|
3065
|
+
throw new Error("Agent dispatch audit requires its timestamp.");
|
|
3066
|
+
return create(AgentHistoryEntrySchema, {
|
|
3067
|
+
occurredAt,
|
|
3068
|
+
item: { case: "systemEvent", value: audit },
|
|
3069
|
+
});
|
|
3070
|
+
},
|
|
3071
|
+
/**
|
|
3072
|
+
* Verifies admission revisions and fixes whole-invocation limits before execution.
|
|
3073
|
+
*
|
|
3074
|
+
* @param repository Registered Entity repository for this operation.
|
|
3075
|
+
* @param runtime Bounded Context storage and publication services.
|
|
3076
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
3077
|
+
* @param loaded Loaded Entity and its repository storage state.
|
|
3078
|
+
* @returns Durable deadline, initial version, and whole-invocation bounds.
|
|
3079
|
+
*/
|
|
3080
|
+
startFacts(repository, runtime, accepted, loaded) {
|
|
3081
|
+
const configuration = repositoryAccess.agentConfiguration(repository);
|
|
3082
|
+
const ai = runtime.ai;
|
|
3083
|
+
if (accepted.key === undefined || ai === undefined || configuration.ai === undefined)
|
|
3084
|
+
throw new Error("Agent execution start requires accepted work and AI policy.");
|
|
3085
|
+
const limits = registryOptions(ai).invocationLimits ?? {};
|
|
3086
|
+
return create(AgentExecutionStartSchema, {
|
|
3087
|
+
...(limits.deadlineMs === undefined
|
|
3088
|
+
? {}
|
|
3089
|
+
: { deadline: addMillis(Time.currentTime(), limits.deadlineMs) }),
|
|
3090
|
+
initialVersion: RepositoryEntities.priorVersion(loaded.current),
|
|
3091
|
+
bounds: this.invocationBounds(limits),
|
|
3092
|
+
});
|
|
3093
|
+
},
|
|
3094
|
+
/**
|
|
3095
|
+
* Persists each model kind without a saved connection identity.
|
|
3096
|
+
*
|
|
3097
|
+
* @param repository Agent registration and permitted capabilities.
|
|
3098
|
+
* @param runtime Bound AI registry.
|
|
3099
|
+
* @param session Current fenced execution record.
|
|
3100
|
+
* @param accepted Original accepted signal and actor.
|
|
3101
|
+
* @param tenantId Accepted tenant when applicable.
|
|
3102
|
+
* @returns Record after each missing identity has been saved.
|
|
3103
|
+
*/
|
|
3104
|
+
async selectMissingModels(repository, runtime, session, accepted, tenantId) {
|
|
3105
|
+
const ai = runtime.ai;
|
|
3106
|
+
const configuration = repositoryAccess.agentConfiguration(repository).ai;
|
|
3107
|
+
if (ai === undefined || configuration === undefined)
|
|
3108
|
+
throw new Error("Agent model selection requires saved start facts and AI policy.");
|
|
3109
|
+
const deadlineMs = this.savedModelDeadline(session, "selection");
|
|
3110
|
+
const source = accepted.signal.value?.message;
|
|
3111
|
+
for (const kind of ["generation", "decision"]) {
|
|
3112
|
+
if (!configuration.models.some((model) => model.definition.kind === kind))
|
|
3113
|
+
continue;
|
|
3114
|
+
const modelKind = AgentModelSelection.kind(kind);
|
|
3115
|
+
if (session.record().started?.models.some((model) => model.kind === modelKind))
|
|
3116
|
+
continue;
|
|
3117
|
+
const selected = await AgentModelSelection.selectKind(ai, configuration, AgentModelSelection.scope(accepted, repository.stateSchema, tenantId), session.initialPreferences(), session.signal, deadlineMs, kind, source);
|
|
3118
|
+
await this.saveSelectedModel(session, selected);
|
|
3119
|
+
}
|
|
3120
|
+
return session.record();
|
|
3121
|
+
},
|
|
3122
|
+
/**
|
|
3123
|
+
* Writes one selected model under the current Agent claim.
|
|
3124
|
+
*
|
|
3125
|
+
* @param session Fenced Agent execution session.
|
|
3126
|
+
* @param selected Authorized deployment and connection identity.
|
|
3127
|
+
* @returns When the selection is durable.
|
|
3128
|
+
*/
|
|
3129
|
+
async saveSelectedModel(session, selected) {
|
|
3130
|
+
await session.update((record) => {
|
|
3131
|
+
if (record.started === undefined)
|
|
3132
|
+
throw new Error("Agent start facts disappeared.");
|
|
3133
|
+
record.started.models.push(selected);
|
|
3134
|
+
return record;
|
|
3135
|
+
});
|
|
3136
|
+
},
|
|
3137
|
+
/**
|
|
3138
|
+
* Rejects accepted work when its exact handler and AI policy changed.
|
|
3139
|
+
*
|
|
3140
|
+
* @param repository Registered Entity repository for this operation.
|
|
3141
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
3142
|
+
* @param handlers Generated handlers in their saved order.
|
|
3143
|
+
* @param ai Configured AI registry for this Bounded Context.
|
|
3144
|
+
* @param configuration Repository and AI policy used for revision checks.
|
|
3145
|
+
*/
|
|
3146
|
+
assertRevisions(repository, accepted, handlers, ai, configuration) {
|
|
3147
|
+
if (configuration.ai === undefined)
|
|
3148
|
+
throw new Error("Agent AI policy is required.");
|
|
3149
|
+
const revisions = AgentRevisions.atAdmission(repository.stateSchema, handlers, ai, configuration.ai);
|
|
3150
|
+
if (accepted.codeRevision?.value !== configuration.codeRevision ||
|
|
3151
|
+
accepted.schemaRevision?.value !== revisions.schema ||
|
|
3152
|
+
accepted.policyRevision?.value !== revisions.policy)
|
|
3153
|
+
throw new AgentExecutionFault("REVISION_CHANGED", "Accepted Agent code, schema, or policy changed before execution.");
|
|
3154
|
+
},
|
|
3155
|
+
/**
|
|
3156
|
+
* Copies the whole-invocation budget into its durable integer fields.
|
|
3157
|
+
*
|
|
3158
|
+
* @param limits Configured whole-invocation limits.
|
|
3159
|
+
* @returns Whole-transition limits persisted at invocation start.
|
|
3160
|
+
*/
|
|
3161
|
+
invocationBounds(limits) {
|
|
3162
|
+
return create(AgentInvocationBoundsSchema, {
|
|
3163
|
+
...(limits.operations === undefined ? {} : { operations: BigInt(limits.operations) }),
|
|
3164
|
+
...(limits.modelRequests === undefined
|
|
3165
|
+
? {}
|
|
3166
|
+
: { modelRequests: BigInt(limits.modelRequests) }),
|
|
3167
|
+
...(limits.toolCalls === undefined ? {} : { toolCalls: BigInt(limits.toolCalls) }),
|
|
3168
|
+
...(limits.recordedReads === undefined
|
|
3169
|
+
? {}
|
|
3170
|
+
: { recordedReads: BigInt(limits.recordedReads) }),
|
|
3171
|
+
...(limits.deadlineMs === undefined ? {} : { deadlineMillis: BigInt(limits.deadlineMs) }),
|
|
3172
|
+
...(limits.totalInputBytes === undefined
|
|
3173
|
+
? {}
|
|
3174
|
+
: { totalInputBytes: BigInt(limits.totalInputBytes) }),
|
|
3175
|
+
...(limits.totalOutputBytes === undefined
|
|
3176
|
+
? {}
|
|
3177
|
+
: { totalOutputBytes: BigInt(limits.totalOutputBytes) }),
|
|
3178
|
+
...(limits.maxRecoveryBytes === undefined
|
|
3179
|
+
? {}
|
|
3180
|
+
: { maxRecoveryBytes: BigInt(limits.maxRecoveryBytes) }),
|
|
3181
|
+
});
|
|
3182
|
+
},
|
|
3183
|
+
/**
|
|
3184
|
+
* Creates a genuine source-specific dispatch System Event before the handler.
|
|
3185
|
+
*
|
|
3186
|
+
* @param runtime Bounded Context storage and publication services.
|
|
3187
|
+
* @param repository Registered Entity repository for this operation.
|
|
3188
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
3189
|
+
* @param entityId Typed recipient Entity identifier.
|
|
3190
|
+
* @returns Original dispatch System Event envelope.
|
|
3191
|
+
*/
|
|
3192
|
+
startAudit(runtime, repository, accepted, entityId) {
|
|
3193
|
+
const source = accepted.signal;
|
|
3194
|
+
if (source.case === "command")
|
|
3195
|
+
return HandlerDispatchPublisher.commandEvent(runtime, repository, source.value, entityId);
|
|
3196
|
+
if (source.case === "event")
|
|
3197
|
+
return HandlerDispatchPublisher.reactorEvent(runtime, repository, source.value, entityId);
|
|
3198
|
+
throw new Error("Agent start requires its accepted envelope.");
|
|
3199
|
+
},
|
|
3200
|
+
/**
|
|
3201
|
+
* Invokes saved Command or Event bindings inside one Entity transaction.
|
|
3202
|
+
*
|
|
3203
|
+
* @param entity Restored Agent instance.
|
|
3204
|
+
* @param accepted Original accepted signal.
|
|
3205
|
+
* @param handlers Validated current handler bindings.
|
|
3206
|
+
* @param repository Registered Entity repository for this operation.
|
|
3207
|
+
* @param runtime Bounded Context storage and publication services.
|
|
3208
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
3209
|
+
* @param session Fenced Agent execution session.
|
|
3210
|
+
* @returns Outputs returned by the selected generated handlers.
|
|
3211
|
+
*/
|
|
3212
|
+
async invoke(repository, runtime, tenantId, session, entity, accepted, handlers) {
|
|
3213
|
+
transactionalEntityAccess.start(entity);
|
|
3214
|
+
try {
|
|
3215
|
+
const signals = [];
|
|
3216
|
+
for (const [ordinal, handler] of handlers.entries()) {
|
|
3217
|
+
signals.push(...(await this.invokeBoundHandler(repository, runtime, tenantId, session, entity, accepted, handler, ordinal)));
|
|
3218
|
+
}
|
|
3219
|
+
await this.commitInvokedEntity(entity);
|
|
3220
|
+
return signals;
|
|
3221
|
+
}
|
|
3222
|
+
catch (error) {
|
|
3223
|
+
transactionalEntityAccess.rollback(entity);
|
|
3224
|
+
throw error;
|
|
3225
|
+
}
|
|
3226
|
+
},
|
|
3227
|
+
/**
|
|
3228
|
+
* Validates the transaction result before Agent output completion.
|
|
3229
|
+
*
|
|
3230
|
+
* @param entity Loaded recipient Entity instance.
|
|
3231
|
+
* @returns When the fenced repository operation completes.
|
|
3232
|
+
*/
|
|
3233
|
+
async commitInvokedEntity(entity) {
|
|
3234
|
+
const commit = await commitFenced(entity, (current) => transactionalEntityAccess.commit(current));
|
|
3235
|
+
if (commit.status === "rejected")
|
|
3236
|
+
throw new TransitionValidationError(commit.validation.error);
|
|
3237
|
+
},
|
|
3238
|
+
/**
|
|
3239
|
+
* Binds named AI and history reads around one generated handler call.
|
|
3240
|
+
*
|
|
3241
|
+
* @param repository Registered Entity repository for this operation.
|
|
3242
|
+
* @param runtime Bounded Context storage and publication services.
|
|
3243
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
3244
|
+
* @param session Fenced Agent execution session.
|
|
3245
|
+
* @param entity Loaded recipient Entity instance.
|
|
3246
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
3247
|
+
* @param handler Generated handler selected for this signal.
|
|
3248
|
+
* @param ordinal Saved position of the selected handler.
|
|
3249
|
+
* @returns Outputs returned by one generated handler.
|
|
3250
|
+
*/
|
|
3251
|
+
async invokeBoundHandler(repository, runtime, tenantId, session, entity, accepted, handler, ordinal) {
|
|
3252
|
+
const facade = this.handlerFacade(repository, runtime, tenantId, session, accepted, ordinal);
|
|
3253
|
+
const reads = new AgentReadRuntime(session, ordinal);
|
|
3254
|
+
const release = this.bindHandlerCapabilities(entity, runtime, tenantId, accepted, reads, facade);
|
|
3255
|
+
try {
|
|
3256
|
+
const deadlineEpochMs = this.handlerDeadline(session);
|
|
3257
|
+
const signals = await this.awaitHandler(this.invokeHandler(entity, accepted, handler), session.signal, deadlineEpochMs);
|
|
3258
|
+
reads.finish();
|
|
3259
|
+
facade.finish();
|
|
3260
|
+
return signals;
|
|
3261
|
+
}
|
|
3262
|
+
finally {
|
|
3263
|
+
facade.close();
|
|
3264
|
+
release();
|
|
3265
|
+
}
|
|
3266
|
+
},
|
|
3267
|
+
/**
|
|
3268
|
+
* Binds Agent AI, indexed history and routed projection queries for one handler.
|
|
3269
|
+
*
|
|
3270
|
+
* @param entity Loaded recipient Entity instance.
|
|
3271
|
+
* @param runtime Bounded Context storage and publication services.
|
|
3272
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
3273
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
3274
|
+
* @param reads Handler-scoped durable read facade.
|
|
3275
|
+
* @param facade Handler-scoped AI facade.
|
|
3276
|
+
* @returns Release callback for handler-scoped AI and read facades.
|
|
3277
|
+
*/
|
|
3278
|
+
bindHandlerCapabilities(entity, runtime, tenantId, accepted, reads, facade) {
|
|
3279
|
+
const releaseHistory = AgentHistoryReads.withJournal(entity, (scope, view, request, live, maxBytes) => reads.history(scope, view, request, live, maxBytes));
|
|
3280
|
+
let releaseQuery;
|
|
3281
|
+
try {
|
|
3282
|
+
releaseQuery = ProcessManagerQueries.bind(entity, runtime, accepted.actor, tenantId, (schema, query, live) => reads.query(schema, query, live));
|
|
3283
|
+
const releaseAi = AgentAiBindings.bind(entity, facade);
|
|
3284
|
+
return () => {
|
|
3285
|
+
releaseQuery?.();
|
|
3286
|
+
releaseHistory();
|
|
3287
|
+
releaseAi();
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
3290
|
+
catch (error) {
|
|
3291
|
+
releaseQuery?.();
|
|
3292
|
+
releaseHistory();
|
|
3293
|
+
throw error;
|
|
3294
|
+
}
|
|
3295
|
+
},
|
|
3296
|
+
/**
|
|
3297
|
+
* Creates one handler-scoped facade from its accepted model policy.
|
|
3298
|
+
*
|
|
3299
|
+
* @param repository Registered Entity repository for this operation.
|
|
3300
|
+
* @param runtime Bounded Context storage and publication services.
|
|
3301
|
+
* @param tenantId Tenant selected for this accepted signal.
|
|
3302
|
+
* @param session Fenced Agent execution session.
|
|
3303
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
3304
|
+
* @param ordinal Saved position of the selected handler.
|
|
3305
|
+
* @returns AI facade bound to one selected handler.
|
|
3306
|
+
*/
|
|
3307
|
+
handlerFacade(repository, runtime, tenantId, session, accepted, ordinal) {
|
|
3308
|
+
const ai = runtime.ai;
|
|
3309
|
+
const configuration = repositoryAccess.agentConfiguration(repository).ai;
|
|
3310
|
+
if (ai === undefined || configuration === undefined)
|
|
3311
|
+
throw new Error("Agent handler requires its bound AI registry.");
|
|
3312
|
+
return new AgentAiRuntime(ai, configuration, AgentModelSelection.scope(accepted, repository.stateSchema, tenantId), session, ordinal, new AgentInteractionAudit(session, accepted, repository.stateSchema, runtime.signalMetadata, runtime.registerSystemEventSchema, runtime.publishAgentSystemEvent));
|
|
3313
|
+
},
|
|
3314
|
+
/**
|
|
3315
|
+
* Invokes one selected generated handler and validates its declared results.
|
|
3316
|
+
*
|
|
3317
|
+
* @param entity Loaded recipient Entity instance.
|
|
3318
|
+
* @param accepted Durable original signal and saved handler bindings.
|
|
3319
|
+
* @param handler Generated handler selected for this signal.
|
|
3320
|
+
* @returns Declared outputs returned by one generated handler.
|
|
3321
|
+
*/
|
|
3322
|
+
async invokeHandler(entity, accepted, handler) {
|
|
3323
|
+
const source = accepted.signal;
|
|
3324
|
+
if (source.case !== "command" && source.case !== "event")
|
|
3325
|
+
throw new Error("Agent source envelope is missing.");
|
|
3326
|
+
const kind = source.case;
|
|
3327
|
+
const message = EntityInvocation.unpackRequired(EntityInvocation.requireSignalMessage(source.value.message, kind), handler.handler.schema, kind);
|
|
3328
|
+
const context = source.case === "command"
|
|
3329
|
+
? EntityInvocation.commandHandlerContext(source.value)
|
|
3330
|
+
: EntityInvocation.eventHandlerContext(source.value);
|
|
3331
|
+
const produced = await EntityInvocation.invokeEntityMethod(entity, handler.handler.methodName, message, handler.handler.parameterCount, context);
|
|
3332
|
+
const signals = produced === undefined
|
|
3333
|
+
? []
|
|
3334
|
+
: Array.isArray(produced)
|
|
3335
|
+
? Array.from(produced)
|
|
3336
|
+
: [produced];
|
|
3337
|
+
RepositoryHandlers.requireDeclaredOutputs(handler.handler, signals);
|
|
3338
|
+
return signals;
|
|
3339
|
+
},
|
|
3340
|
+
/**
|
|
3341
|
+
* Reads the original invocation deadline before starting an application callback.
|
|
3342
|
+
*
|
|
3343
|
+
* @param session Current fenced execution session.
|
|
3344
|
+
* @returns Absolute saved deadline in milliseconds.
|
|
3345
|
+
*/
|
|
3346
|
+
handlerDeadline(session) {
|
|
3347
|
+
const record = session.record();
|
|
3348
|
+
const deadline = record.started?.deadline;
|
|
3349
|
+
if (deadline === undefined && record.started?.bounds?.deadlineMillis !== undefined)
|
|
3350
|
+
throw new Error("Agent handler requires its saved deadline.");
|
|
3351
|
+
if (session.signal.aborted || this.deadlineExpired(record))
|
|
3352
|
+
throw new Error("Agent handler deadline or claim ended.");
|
|
3353
|
+
return deadline === undefined
|
|
3354
|
+
? undefined
|
|
3355
|
+
: Number(deadline.seconds) * 1_000 + Math.floor(deadline.nanos / 1_000_000);
|
|
3356
|
+
},
|
|
3357
|
+
/**
|
|
3358
|
+
* Awaits an application handler within its saved deadline and current claim.
|
|
3359
|
+
*
|
|
3360
|
+
* @typeParam Value Handler result.
|
|
3361
|
+
* @param work Already-started generated handler invocation.
|
|
3362
|
+
* @param signal Session cancellation when claim authority ends.
|
|
3363
|
+
* @param deadlineEpochMs Original saved invocation deadline.
|
|
3364
|
+
* @returns Handler result only while its claim and deadline remain valid.
|
|
3365
|
+
*/
|
|
3366
|
+
async awaitHandler(work, signal, deadlineEpochMs) {
|
|
3367
|
+
let timer;
|
|
3368
|
+
let abort = () => undefined;
|
|
3369
|
+
const cutoff = new Promise((_resolve, reject) => {
|
|
3370
|
+
abort = () => {
|
|
3371
|
+
reject(new Error("Agent handler claim ended."));
|
|
3372
|
+
};
|
|
3373
|
+
const tick = () => {
|
|
3374
|
+
if (deadlineEpochMs === undefined)
|
|
3375
|
+
return;
|
|
3376
|
+
const remaining = deadlineEpochMs - Time.currentTimeMillis();
|
|
3377
|
+
if (remaining <= 0)
|
|
3378
|
+
reject(new Error("Agent handler deadline expired."));
|
|
3379
|
+
else
|
|
3380
|
+
timer = setTimeout(tick, Math.min(remaining, 2_147_483_647));
|
|
3381
|
+
};
|
|
3382
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
3383
|
+
if (signal.aborted)
|
|
3384
|
+
abort();
|
|
3385
|
+
else
|
|
3386
|
+
tick();
|
|
3387
|
+
});
|
|
3388
|
+
try {
|
|
3389
|
+
const result = await Promise.race([work, cutoff]);
|
|
3390
|
+
if (signal.aborted ||
|
|
3391
|
+
(deadlineEpochMs !== undefined && Time.currentTimeMillis() >= deadlineEpochMs))
|
|
3392
|
+
throw new Error("Agent handler deadline or claim ended.");
|
|
3393
|
+
return result;
|
|
3394
|
+
}
|
|
3395
|
+
finally {
|
|
3396
|
+
clearTimeout(timer);
|
|
3397
|
+
signal.removeEventListener("abort", abort);
|
|
3398
|
+
}
|
|
3399
|
+
},
|
|
3400
|
+
};
|
|
3401
|
+
/**
|
|
3402
|
+
* Adds a positive timeout to a Time-sampled Protobuf timestamp.
|
|
3403
|
+
*
|
|
3404
|
+
* @param at Provider-independent current time.
|
|
3405
|
+
* @param millis Bounded additional milliseconds.
|
|
3406
|
+
* @returns Absolute timestamp retaining nanosecond precision.
|
|
3407
|
+
*/
|
|
3408
|
+
function addMillis(at, millis) {
|
|
3409
|
+
const nanos = at.nanos + (millis % 1000) * 1_000_000;
|
|
3410
|
+
return {
|
|
3411
|
+
...at,
|
|
3412
|
+
seconds: at.seconds + BigInt(Math.floor(millis / 1000)) + BigInt(Math.floor(nanos / 1_000_000_000)),
|
|
3413
|
+
nanos: nanos % 1_000_000_000,
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
/**
|
|
3417
|
+
* Executes one Process Manager Command against its loaded Entity state.
|
|
1755
3418
|
*/
|
|
1756
3419
|
class ProcessManagerCommandExecution {
|
|
1757
3420
|
#repository;
|
|
@@ -1764,7 +3427,7 @@ class ProcessManagerCommandExecution {
|
|
|
1764
3427
|
*
|
|
1765
3428
|
* @param repository Process Manager repository receiving the Command.
|
|
1766
3429
|
* @param routing Registered Command routes and schemas.
|
|
1767
|
-
* @param runtime Built
|
|
3430
|
+
* @param runtime Built Bounded Context services.
|
|
1768
3431
|
* @param command Source Command to execute.
|
|
1769
3432
|
*/
|
|
1770
3433
|
constructor(repository, routing, runtime, command) {
|
|
@@ -1787,7 +3450,7 @@ class ProcessManagerCommandExecution {
|
|
|
1787
3450
|
return undefined;
|
|
1788
3451
|
const tenantOptions = RepositoryTenants.commandStandOptions(this.#runtime.context, this.#command);
|
|
1789
3452
|
const loaded = await this.#support.load(intake.route.entityId, tenantOptions);
|
|
1790
|
-
this.#publishDispatch(intake.route.entityId);
|
|
3453
|
+
await this.#publishDispatch(loaded, intake.route.entityId);
|
|
1791
3454
|
try {
|
|
1792
3455
|
const produced = await this.#invoke(loaded.entity, intake.assignee, intake.message, tenantOptions.tenantId);
|
|
1793
3456
|
return await this.#commitAndPublish(loaded, tenantOptions, intake, produced);
|
|
@@ -1800,12 +3463,29 @@ class ProcessManagerCommandExecution {
|
|
|
1800
3463
|
}
|
|
1801
3464
|
}
|
|
1802
3465
|
/**
|
|
1803
|
-
*
|
|
3466
|
+
* Persists required Agent dispatch history before the handler runs.
|
|
3467
|
+
* Process Manager diagnostic publication remains best effort.
|
|
1804
3468
|
*
|
|
1805
|
-
* @param
|
|
3469
|
+
* @param loaded Entity with mandatory Agent history when applicable.
|
|
3470
|
+
* @param entityId Target Process Manager or Agent identifier.
|
|
3471
|
+
* @returns Completion after the Agent audit append and publication.
|
|
1806
3472
|
*/
|
|
1807
|
-
#publishDispatch(entityId) {
|
|
1808
|
-
|
|
3473
|
+
async #publishDispatch(loaded, entityId) {
|
|
3474
|
+
if (this.#repository.entityFamily !== "agent") {
|
|
3475
|
+
HandlerDispatchPublisher.command(this.#runtime, this.#repository, this.#command, entityId);
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3478
|
+
const history = loaded.agentHistory;
|
|
3479
|
+
if (history === undefined)
|
|
3480
|
+
throw new Error("Agent history storage is required.");
|
|
3481
|
+
const event = HandlerDispatchPublisher.commandEvent(this.#runtime, this.#repository, this.#command, entityId);
|
|
3482
|
+
if (event.context?.timestamp === undefined)
|
|
3483
|
+
throw new Error("Agent System Event requires its original timestamp.");
|
|
3484
|
+
await history.append(entityId, create(AgentHistoryEntrySchema, {
|
|
3485
|
+
occurredAt: event.context.timestamp,
|
|
3486
|
+
item: { case: "systemEvent", value: event },
|
|
3487
|
+
}));
|
|
3488
|
+
HandlerDispatchPublisher.publishCommand(this.#runtime, event);
|
|
1809
3489
|
}
|
|
1810
3490
|
/**
|
|
1811
3491
|
* Decodes a registered Command and finds its assignee.
|
|
@@ -1833,7 +3513,7 @@ class ProcessManagerCommandExecution {
|
|
|
1833
3513
|
async #commitAndPublish(loaded, tenantOptions, intake, producedSignals) {
|
|
1834
3514
|
const commands = this.#commandOutputs(intake.assignee, producedSignals);
|
|
1835
3515
|
const events = this.#eventOutputs(intake.assignee, producedSignals, intake.route.entityId, RepositoryEntities.priorVersion(loaded.current));
|
|
1836
|
-
const committed = await this.#support.commit(loaded, tenantOptions, events);
|
|
3516
|
+
const committed = await this.#support.commit(loaded, tenantOptions, events, events);
|
|
1837
3517
|
if (!committed)
|
|
1838
3518
|
return undefined;
|
|
1839
3519
|
this.#publishChangedState(loaded, intake.route.entityId);
|
|
@@ -1895,7 +3575,7 @@ class ProcessManagerCommandExecution {
|
|
|
1895
3575
|
/**
|
|
1896
3576
|
* Binds query access for one Command handler invocation.
|
|
1897
3577
|
*
|
|
1898
|
-
* @param entity
|
|
3578
|
+
* @param entity Entity instance.
|
|
1899
3579
|
* @param assignee Registered Command handler.
|
|
1900
3580
|
* @param message Decoded Command message.
|
|
1901
3581
|
* @param tenantId Tenant for query reads, when present.
|
|
@@ -1913,7 +3593,7 @@ class ProcessManagerCommandExecution {
|
|
|
1913
3593
|
/**
|
|
1914
3594
|
* Invokes a Command handler and checks delivery before the Entity transaction commits.
|
|
1915
3595
|
*
|
|
1916
|
-
* @param entity
|
|
3596
|
+
* @param entity Entity instance to mutate.
|
|
1917
3597
|
* @param assignee Registered Command handler.
|
|
1918
3598
|
* @param message Decoded Command message.
|
|
1919
3599
|
* @returns Validated handler signals after commit.
|
|
@@ -1924,7 +3604,7 @@ class ProcessManagerCommandExecution {
|
|
|
1924
3604
|
const produced = await EntityInvocation.invokeEntityMethod(entity, assignee.handler.methodName, message, assignee.handler.parameterCount, EntityInvocation.commandHandlerContext(this.#command));
|
|
1925
3605
|
const signals = this.#support.normalizeProducedSignals(produced);
|
|
1926
3606
|
if (signals.length === 0)
|
|
1927
|
-
throw new Error("Repository
|
|
3607
|
+
throw new Error("Repository command handlers must return a signal.");
|
|
1928
3608
|
RepositoryHandlers.requireDeclaredOutputs(assignee.handler, signals);
|
|
1929
3609
|
const commit = await commitFenced(entity, (current) => transactionalEntityAccess.commit(current, assignee.handler.kind === "command-assignment" && signals.length > 0));
|
|
1930
3610
|
if (commit.status === "rejected") {
|
|
@@ -1938,11 +3618,11 @@ class ProcessManagerCommandExecution {
|
|
|
1938
3618
|
}
|
|
1939
3619
|
}
|
|
1940
3620
|
/**
|
|
1941
|
-
* Binds assignment results as
|
|
3621
|
+
* Binds assignment results as Entity Events.
|
|
1942
3622
|
*
|
|
1943
3623
|
* @param produced Domain Event messages from the handler.
|
|
1944
|
-
* @param entityId Target
|
|
1945
|
-
* @param version
|
|
3624
|
+
* @param entityId Target Entity identifier.
|
|
3625
|
+
* @param version Entity Version before this dispatch.
|
|
1946
3626
|
* @returns Frozen Event envelope list.
|
|
1947
3627
|
*/
|
|
1948
3628
|
#bindProducedEvents(produced, entityId, version) {
|
|
@@ -1952,15 +3632,15 @@ class ProcessManagerCommandExecution {
|
|
|
1952
3632
|
* Packs one declared Event with source and producer context.
|
|
1953
3633
|
*
|
|
1954
3634
|
* @param signal Domain Event message to pack.
|
|
1955
|
-
* @param entityId Target
|
|
1956
|
-
* @param version
|
|
3635
|
+
* @param entityId Target Entity identifier.
|
|
3636
|
+
* @param version Entity Version before this dispatch.
|
|
1957
3637
|
* @returns Bound Event envelope.
|
|
1958
3638
|
*/
|
|
1959
3639
|
#bindProducedEvent(signal, entityId, version) {
|
|
1960
3640
|
const typeName = EntityInvocation.messageTypeName(signal);
|
|
1961
3641
|
const schema = this.#routing.producedEventSchemas.find((candidate) => candidate.typeName === typeName);
|
|
1962
3642
|
if (schema === undefined) {
|
|
1963
|
-
throw new Error(`Repository
|
|
3643
|
+
throw new Error(`Repository Entity handler cannot pack Event message "${typeName}".`);
|
|
1964
3644
|
}
|
|
1965
3645
|
const metadata = this.#runtime.signalMetadata.eventFromCommand(this.#command, {
|
|
1966
3646
|
version: version.number,
|
|
@@ -1982,7 +3662,7 @@ class ProcessManagerCommandExecution {
|
|
|
1982
3662
|
const typeName = EntityInvocation.messageTypeName(signal);
|
|
1983
3663
|
const schema = this.#routing.producedCommandSchemas.find((candidate) => candidate.typeName === typeName);
|
|
1984
3664
|
if (schema === undefined) {
|
|
1985
|
-
throw new Error(`Repository
|
|
3665
|
+
throw new Error(`Repository Entity handler cannot pack Command message "${typeName}".`);
|
|
1986
3666
|
}
|
|
1987
3667
|
const metadata = this.#runtime.signalMetadata.commandFromCommand(this.#command);
|
|
1988
3668
|
return create(CommandSchema, {
|
|
@@ -2013,12 +3693,12 @@ class ProcessManagerCommandExecution {
|
|
|
2013
3693
|
#postEvents(events) {
|
|
2014
3694
|
for (const event of events) {
|
|
2015
3695
|
// spine-log-boundary: server.repository_event_follow_up
|
|
2016
|
-
void this.#runtime.publisher.
|
|
3696
|
+
void this.#runtime.publisher.publishReactorEvent(event);
|
|
2017
3697
|
}
|
|
2018
3698
|
}
|
|
2019
3699
|
}
|
|
2020
3700
|
/**
|
|
2021
|
-
* Runs Process Manager Event handlers and persists their results.
|
|
3701
|
+
* Runs Process Manager or Agent Event handlers and persists their results.
|
|
2022
3702
|
*/
|
|
2023
3703
|
class ProcessManagerEventExecution {
|
|
2024
3704
|
#repository;
|
|
@@ -2027,9 +3707,9 @@ class ProcessManagerEventExecution {
|
|
|
2027
3707
|
#event;
|
|
2028
3708
|
#support;
|
|
2029
3709
|
/**
|
|
2030
|
-
* Captures
|
|
3710
|
+
* Captures Entity routing, runtime, and source Event.
|
|
2031
3711
|
*
|
|
2032
|
-
* @param repository
|
|
3712
|
+
* @param repository Entity repository receiving the Event.
|
|
2033
3713
|
* @param routing Registered Event routes and schemas.
|
|
2034
3714
|
* @param runtime Built context services.
|
|
2035
3715
|
* @param event Source Event to execute.
|
|
@@ -2042,7 +3722,7 @@ class ProcessManagerEventExecution {
|
|
|
2042
3722
|
this.#support = new ProcessManagerExecutionSupport(repository, runtime);
|
|
2043
3723
|
}
|
|
2044
3724
|
/**
|
|
2045
|
-
* Delivers a routed Event to the durable
|
|
3725
|
+
* Delivers a routed Event to the durable Entity inbox.
|
|
2046
3726
|
*
|
|
2047
3727
|
* @param acceptedRoute Route accepted before dispatch.
|
|
2048
3728
|
* @returns Completion after inbox handoff.
|
|
@@ -2062,7 +3742,7 @@ class ProcessManagerEventExecution {
|
|
|
2062
3742
|
/**
|
|
2063
3743
|
* Delivers a routed Event for one target under the duplicate-dispatch guard.
|
|
2064
3744
|
*
|
|
2065
|
-
* @param entityId Target
|
|
3745
|
+
* @param entityId Target Entity identifier.
|
|
2066
3746
|
* @param acceptedRoute Route accepted before replay.
|
|
2067
3747
|
* @returns Completion after guarded target execution.
|
|
2068
3748
|
*/
|
|
@@ -2099,7 +3779,7 @@ class ProcessManagerEventExecution {
|
|
|
2099
3779
|
/**
|
|
2100
3780
|
* Invokes handlers, commits state, and publishes resulting Events and Commands.
|
|
2101
3781
|
*
|
|
2102
|
-
* @param entityId Target
|
|
3782
|
+
* @param entityId Target Entity identifier.
|
|
2103
3783
|
* @param intake Decoded Event and matching handlers.
|
|
2104
3784
|
* @returns Completion after accepted results are published.
|
|
2105
3785
|
*/
|
|
@@ -2113,7 +3793,7 @@ class ProcessManagerEventExecution {
|
|
|
2113
3793
|
DispatchGuards.guardedJournalEvent(this.#repository, this.#event, entityId),
|
|
2114
3794
|
...events,
|
|
2115
3795
|
];
|
|
2116
|
-
const committed = await this.#support.commit(loaded, tenantOptions, diagnostics);
|
|
3796
|
+
const committed = await this.#support.commit(loaded, tenantOptions, diagnostics, events);
|
|
2117
3797
|
if (!committed)
|
|
2118
3798
|
return;
|
|
2119
3799
|
this.#publishChangedState(loaded, entityId);
|
|
@@ -2121,10 +3801,10 @@ class ProcessManagerEventExecution {
|
|
|
2121
3801
|
await this.#postCommands(commands);
|
|
2122
3802
|
}
|
|
2123
3803
|
/**
|
|
2124
|
-
* Publishes a state-change System Event when the
|
|
3804
|
+
* Publishes a state-change System Event when the Entity changed.
|
|
2125
3805
|
*
|
|
2126
|
-
* @param loaded
|
|
2127
|
-
* @param entityId Target
|
|
3806
|
+
* @param loaded Entity before and after handling.
|
|
3807
|
+
* @param entityId Target Entity identifier.
|
|
2128
3808
|
*/
|
|
2129
3809
|
#publishChangedState(loaded, entityId) {
|
|
2130
3810
|
if (!RepositoryEntities.repositoryChanged(loaded.entity))
|
|
@@ -2153,8 +3833,8 @@ class ProcessManagerEventExecution {
|
|
|
2153
3833
|
/**
|
|
2154
3834
|
* Invokes Event handlers in one transaction with temporary query access.
|
|
2155
3835
|
*
|
|
2156
|
-
* @param entityId Target
|
|
2157
|
-
* @param entity
|
|
3836
|
+
* @param entityId Target Entity identifier.
|
|
3837
|
+
* @param entity Entity instance.
|
|
2158
3838
|
* @param intake Decoded Event and matching handlers.
|
|
2159
3839
|
* @param tenantId Tenant for query reads, when present.
|
|
2160
3840
|
* @returns Validated Command and Event message lists.
|
|
@@ -2184,8 +3864,8 @@ class ProcessManagerEventExecution {
|
|
|
2184
3864
|
/**
|
|
2185
3865
|
* Invokes reactors and Command producers, collecting their separate outputs.
|
|
2186
3866
|
*
|
|
2187
|
-
* @param entityId Target
|
|
2188
|
-
* @param entity
|
|
3867
|
+
* @param entityId Target Entity identifier.
|
|
3868
|
+
* @param entity Entity instance.
|
|
2189
3869
|
* @param intake Decoded Event and matching handlers.
|
|
2190
3870
|
* @param context Copied source Event context.
|
|
2191
3871
|
* @param events Output list for Event messages.
|
|
@@ -2193,15 +3873,23 @@ class ProcessManagerEventExecution {
|
|
|
2193
3873
|
* @returns Completion after matching handlers run.
|
|
2194
3874
|
*/
|
|
2195
3875
|
async #invokeEventHandlers(entityId, entity, intake, context, events, commands) {
|
|
2196
|
-
if (intake.reactors.length > 0)
|
|
2197
|
-
|
|
3876
|
+
if (intake.reactors.length > 0) {
|
|
3877
|
+
if (this.#repository.entityFamily === "agent") {
|
|
3878
|
+
const diagnostic = HandlerDispatchPublisher.reactorEvent(this.#runtime, this.#repository, this.#event, entityId);
|
|
3879
|
+
await AgentHistoryReads.appendSystem(entity, diagnostic);
|
|
3880
|
+
HandlerDispatchPublisher.publishReactor(this.#runtime, diagnostic);
|
|
3881
|
+
}
|
|
3882
|
+
else {
|
|
3883
|
+
HandlerDispatchPublisher.reactor(this.#runtime, this.#repository, this.#event, entityId);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
2198
3886
|
await this.#invokeHandlersInto(entity, intake.reactors, intake.message, context, events);
|
|
2199
3887
|
await this.#invokeHandlersInto(entity, intake.commanders, intake.message, context, commands);
|
|
2200
3888
|
}
|
|
2201
3889
|
/**
|
|
2202
3890
|
* Invokes each selected handler and appends its declared results.
|
|
2203
3891
|
*
|
|
2204
|
-
* @param entity
|
|
3892
|
+
* @param entity Entity instance.
|
|
2205
3893
|
* @param handlers Handler registrations to invoke.
|
|
2206
3894
|
* @param message Decoded source Event message.
|
|
2207
3895
|
* @param context Copied source Event context.
|
|
@@ -2219,7 +3907,7 @@ class ProcessManagerEventExecution {
|
|
|
2219
3907
|
/**
|
|
2220
3908
|
* Checks delivery immediately before the Entity transaction commits and raises transition rejection.
|
|
2221
3909
|
*
|
|
2222
|
-
* @param entity
|
|
3910
|
+
* @param entity Entity instance to commit.
|
|
2223
3911
|
* @param producedEvents Whether handlers produced Events.
|
|
2224
3912
|
* @returns Completion after a successful commit.
|
|
2225
3913
|
*/
|
|
@@ -2229,11 +3917,11 @@ class ProcessManagerEventExecution {
|
|
|
2229
3917
|
throw new TransitionValidationError(commit.validation.error);
|
|
2230
3918
|
}
|
|
2231
3919
|
/**
|
|
2232
|
-
* Binds reactor results as
|
|
3920
|
+
* Binds reactor results as Entity Events.
|
|
2233
3921
|
*
|
|
2234
3922
|
* @param produced Domain Event messages from handlers.
|
|
2235
|
-
* @param entityId Target
|
|
2236
|
-
* @param version
|
|
3923
|
+
* @param entityId Target Entity identifier.
|
|
3924
|
+
* @param version Entity Version before this dispatch.
|
|
2237
3925
|
* @returns Frozen Event envelope list.
|
|
2238
3926
|
*/
|
|
2239
3927
|
#bindProducedEvents(produced, entityId, version) {
|
|
@@ -2243,15 +3931,15 @@ class ProcessManagerEventExecution {
|
|
|
2243
3931
|
* Packs one declared Event with source and producer context.
|
|
2244
3932
|
*
|
|
2245
3933
|
* @param signal Domain Event message to pack.
|
|
2246
|
-
* @param entityId Target
|
|
2247
|
-
* @param version
|
|
3934
|
+
* @param entityId Target Entity identifier.
|
|
3935
|
+
* @param version Entity Version before this dispatch.
|
|
2248
3936
|
* @returns Bound Event envelope.
|
|
2249
3937
|
*/
|
|
2250
3938
|
#bindProducedEvent(signal, entityId, version) {
|
|
2251
3939
|
const typeName = EntityInvocation.messageTypeName(signal);
|
|
2252
3940
|
const schema = this.#routing.producedEventSchemas.find((candidate) => candidate.typeName === typeName);
|
|
2253
3941
|
if (schema === undefined) {
|
|
2254
|
-
throw new Error(`Repository
|
|
3942
|
+
throw new Error(`Repository Entity handler cannot pack Event message "${typeName}".`);
|
|
2255
3943
|
}
|
|
2256
3944
|
const metadata = this.#runtime.signalMetadata.eventFromEvent(this.#event, {
|
|
2257
3945
|
version: version.number,
|
|
@@ -2273,7 +3961,7 @@ class ProcessManagerEventExecution {
|
|
|
2273
3961
|
const typeName = EntityInvocation.messageTypeName(signal);
|
|
2274
3962
|
const schema = this.#routing.producedCommandSchemas.find((candidate) => candidate.typeName === typeName);
|
|
2275
3963
|
if (schema === undefined) {
|
|
2276
|
-
throw new Error(`Repository
|
|
3964
|
+
throw new Error(`Repository Entity handler cannot pack Command message "${typeName}".`);
|
|
2277
3965
|
}
|
|
2278
3966
|
const metadata = this.#runtime.signalMetadata.commandFromEvent(this.#event);
|
|
2279
3967
|
return create(CommandSchema, {
|
|
@@ -2302,7 +3990,7 @@ class ProcessManagerEventExecution {
|
|
|
2302
3990
|
#postEvents(events) {
|
|
2303
3991
|
for (const event of events) {
|
|
2304
3992
|
// spine-log-boundary: server.process_manager_event_follow_up
|
|
2305
|
-
void this.#runtime.publisher.
|
|
3993
|
+
void this.#runtime.publisher.publishReactorEvent(event);
|
|
2306
3994
|
}
|
|
2307
3995
|
}
|
|
2308
3996
|
}
|
|
@@ -2323,7 +4011,7 @@ const RepositoryIdentity = {
|
|
|
2323
4011
|
const displayName = RepositoryIdentity.entityTypeName(entityType);
|
|
2324
4012
|
const schema = RepositoryIdentity.readRepositorySchemaOption(options, displayName, entityFamily);
|
|
2325
4013
|
const metadata = RepositoryIdentity.describeRepositoryEntityMetadata(displayName, entityFamily, schema);
|
|
2326
|
-
if (metadata.kind !== entityFamily) {
|
|
4014
|
+
if (metadata.kind !== (entityFamily === "agent" ? "entity" : entityFamily)) {
|
|
2327
4015
|
throw new RepositoryIdentityError("ENTITY_SCHEMA_KIND_MISMATCH", `Repository entity type "${displayName}" does not match the supplied state schema.`);
|
|
2328
4016
|
}
|
|
2329
4017
|
if (options.stateUpdateRouting !== undefined && entityFamily !== "projection") {
|
|
@@ -2340,34 +4028,34 @@ const RepositoryIdentity = {
|
|
|
2340
4028
|
requireEntityType(options) {
|
|
2341
4029
|
if (!RepositoryIdentity.isRepositoryOptionsObject(options)) {
|
|
2342
4030
|
throw new RepositoryIdentityError("UNSUPPORTED_ENTITY_TYPE", "Repository options must be a non-null object with an entity type class constructor " +
|
|
2343
|
-
"extending Aggregate, Projection, or
|
|
4031
|
+
"extending Aggregate, Projection, ProcessManager, or Agent.");
|
|
2344
4032
|
}
|
|
2345
4033
|
const entityType = RepositoryIdentity.readEntityTypeOption(options);
|
|
2346
4034
|
if (typeof entityType !== "function" || !RepositoryIdentity.isClassConstructor(entityType)) {
|
|
2347
4035
|
throw new RepositoryIdentityError("UNSUPPORTED_ENTITY_TYPE", `Repository entity type "${RepositoryIdentity.entityTypeName(entityType)}" must be a class constructor ` +
|
|
2348
|
-
"extending Aggregate, Projection, or
|
|
4036
|
+
"extending Aggregate, Projection, ProcessManager, or Agent.");
|
|
2349
4037
|
}
|
|
2350
4038
|
return entityType;
|
|
2351
4039
|
},
|
|
2352
4040
|
/**
|
|
2353
|
-
* Validates and returns Aggregate, Projection,
|
|
4041
|
+
* Validates and returns Aggregate, Projection, Process Manager, or Agent inheritance.
|
|
2354
4042
|
*
|
|
2355
4043
|
* @param entityType Validated class constructor.
|
|
2356
|
-
* @returns Aggregate, Projection,
|
|
4044
|
+
* @returns Aggregate, Projection, Process Manager, or Agent.
|
|
2357
4045
|
*/
|
|
2358
4046
|
requireEntityFamily(entityType) {
|
|
2359
4047
|
const family = RepositoryIdentity.resolveRepositoryEntityFamily(entityType);
|
|
2360
4048
|
if (family !== undefined)
|
|
2361
4049
|
return family;
|
|
2362
4050
|
throw new RepositoryIdentityError("UNSUPPORTED_ENTITY_TYPE", `Repository entity type "${RepositoryIdentity.entityTypeName(entityType)}" must extend ` +
|
|
2363
|
-
"Aggregate, Projection, or
|
|
4051
|
+
"Aggregate, Projection, ProcessManager, or Agent.");
|
|
2364
4052
|
},
|
|
2365
4053
|
/**
|
|
2366
4054
|
* Creates a frozen identity snapshot from validated Entity metadata.
|
|
2367
4055
|
*
|
|
2368
4056
|
* @typeParam EntityType Concrete Entity constructor represented by the snapshot.
|
|
2369
4057
|
* @param entityType Entity constructor.
|
|
2370
|
-
* @param entityFamily Aggregate, Projection,
|
|
4058
|
+
* @param entityFamily Aggregate, Projection, Process Manager, or Agent family.
|
|
2371
4059
|
* @param metadata Descriptor-derived state metadata.
|
|
2372
4060
|
* @returns Copy-safe repository identity snapshot.
|
|
2373
4061
|
*/
|
|
@@ -2421,7 +4109,7 @@ const RepositoryIdentity = {
|
|
|
2421
4109
|
}
|
|
2422
4110
|
catch {
|
|
2423
4111
|
throw new RepositoryIdentityError("UNSUPPORTED_ENTITY_TYPE", "Repository options entityType must be readable and resolve to a class constructor " +
|
|
2424
|
-
"extending Aggregate, Projection, or
|
|
4112
|
+
"extending Aggregate, Projection, ProcessManager, or Agent.");
|
|
2425
4113
|
}
|
|
2426
4114
|
},
|
|
2427
4115
|
/**
|
|
@@ -2471,22 +4159,21 @@ const RepositoryIdentity = {
|
|
|
2471
4159
|
return undefined;
|
|
2472
4160
|
}
|
|
2473
4161
|
const runtimeEntityType = entityType;
|
|
2474
|
-
if (RepositoryIdentity.hasEntityFamilyInheritance(runtimeEntityType, Aggregate, Aggregate.prototype))
|
|
4162
|
+
if (RepositoryIdentity.hasEntityFamilyInheritance(runtimeEntityType, Aggregate, Aggregate.prototype))
|
|
2475
4163
|
return "aggregate";
|
|
2476
|
-
|
|
2477
|
-
if (RepositoryIdentity.hasEntityFamilyInheritance(runtimeEntityType, Projection, Projection.prototype)) {
|
|
4164
|
+
if (RepositoryIdentity.hasEntityFamilyInheritance(runtimeEntityType, Projection, Projection.prototype))
|
|
2478
4165
|
return "projection";
|
|
2479
|
-
|
|
2480
|
-
if (RepositoryIdentity.hasEntityFamilyInheritance(runtimeEntityType, ProcessManager, ProcessManager.prototype)) {
|
|
4166
|
+
if (RepositoryIdentity.hasEntityFamilyInheritance(runtimeEntityType, ProcessManager, ProcessManager.prototype))
|
|
2481
4167
|
return "process-manager";
|
|
2482
|
-
|
|
4168
|
+
if (RepositoryIdentity.hasEntityFamilyInheritance(runtimeEntityType, Agent, Agent.prototype))
|
|
4169
|
+
return "agent";
|
|
2483
4170
|
return undefined;
|
|
2484
4171
|
},
|
|
2485
4172
|
/**
|
|
2486
4173
|
* Checks constructor and prototype inheritance against an Entity family.
|
|
2487
4174
|
*
|
|
2488
4175
|
* @param entityType Candidate constructor.
|
|
2489
|
-
* @param familyConstructor
|
|
4176
|
+
* @param familyConstructor Entity family constructor.
|
|
2490
4177
|
* @param familyPrototype Prototype for the same family.
|
|
2491
4178
|
* @returns Whether both inheritance paths match.
|
|
2492
4179
|
*/
|
|
@@ -2625,7 +4312,23 @@ const EntityInvocation = {
|
|
|
2625
4312
|
if (typeof method !== "function") {
|
|
2626
4313
|
throw new TypeError(`Repository entity execution requires method "${methodName}".`);
|
|
2627
4314
|
}
|
|
2628
|
-
|
|
4315
|
+
let result;
|
|
4316
|
+
try {
|
|
4317
|
+
result = Reflect.apply(method, entity, parameterCount === 2 ? [message, context] : [message]);
|
|
4318
|
+
}
|
|
4319
|
+
catch (error) {
|
|
4320
|
+
receptionObservation.recordHandlerFailure(error);
|
|
4321
|
+
throw error;
|
|
4322
|
+
}
|
|
4323
|
+
if (result !== null &&
|
|
4324
|
+
(typeof result === "object" || typeof result === "function") &&
|
|
4325
|
+
typeof result.then === "function") {
|
|
4326
|
+
return Promise.resolve(result).catch((error) => {
|
|
4327
|
+
receptionObservation.recordHandlerFailure(error);
|
|
4328
|
+
throw error;
|
|
4329
|
+
});
|
|
4330
|
+
}
|
|
4331
|
+
return result;
|
|
2629
4332
|
},
|
|
2630
4333
|
/**
|
|
2631
4334
|
* Copies a Command context or creates an empty context when absent.
|
|
@@ -2697,7 +4400,36 @@ const RepositoryEntities = {
|
|
|
2697
4400
|
const storageInput = RepositoryStorage.entityStorageInput(repository, RepositoryTenants.storageContextForTenant(runtime.context, tenantId));
|
|
2698
4401
|
const storage = RepositoryStorage.openRepositoryEntityStorage(repository, runtime.storageFactory, storageInput);
|
|
2699
4402
|
RepositoryHistoryInternals.bindEntityHistory(entity, storage, entityId, repository.stateSchema);
|
|
2700
|
-
|
|
4403
|
+
if (repository.entityFamily === "agent")
|
|
4404
|
+
this.bindAgentHistory(entity, entityId, storageInput, storage.agentHistory);
|
|
4405
|
+
return {
|
|
4406
|
+
commits: storage.commits,
|
|
4407
|
+
events: storage.events,
|
|
4408
|
+
...(storage.agentHistory === undefined ? {} : { agentHistory: storage.agentHistory }),
|
|
4409
|
+
storageInput,
|
|
4410
|
+
};
|
|
4411
|
+
},
|
|
4412
|
+
/**
|
|
4413
|
+
* Attaches the indexed history port to one restored Agent.
|
|
4414
|
+
*
|
|
4415
|
+
* @param entity Restored Agent instance.
|
|
4416
|
+
* @param entityId Typed Agent identifier.
|
|
4417
|
+
* @param input Entity storage scope.
|
|
4418
|
+
* @param history Mandatory Agent history port.
|
|
4419
|
+
*/
|
|
4420
|
+
bindAgentHistory(entity, entityId, input, history) {
|
|
4421
|
+
if (history === undefined)
|
|
4422
|
+
throw new Error("Agent history storage is required.");
|
|
4423
|
+
AgentHistoryReads.bind(entity, {
|
|
4424
|
+
storage: history,
|
|
4425
|
+
entityId,
|
|
4426
|
+
scope: {
|
|
4427
|
+
context: input.context.name,
|
|
4428
|
+
tenant: JSON.stringify(input.context.tenantId ?? null),
|
|
4429
|
+
repository: input.sourceType.typeName,
|
|
4430
|
+
entity: input.id.key(entityId),
|
|
4431
|
+
},
|
|
4432
|
+
});
|
|
2701
4433
|
},
|
|
2702
4434
|
/**
|
|
2703
4435
|
* Restores an Entity from Stand or creates a fresh instance at Version zero.
|
|
@@ -2729,15 +4461,17 @@ const RepositoryEntities = {
|
|
|
2729
4461
|
* @param stored Prior Stand snapshot, when present.
|
|
2730
4462
|
* @param commits Durable commit storage.
|
|
2731
4463
|
* @param events Durable diagnostic Event history.
|
|
4464
|
+
* @param agentHistory Mandatory Agent history when this is an Agent.
|
|
2732
4465
|
* @param storageInput Entity storage specification.
|
|
2733
4466
|
* @param schema Entity state schema.
|
|
2734
4467
|
* @param entityId Entity identifier.
|
|
2735
4468
|
* @returns The loaded Entity and prior record.
|
|
2736
4469
|
*/
|
|
2737
|
-
loaded(entity, stored, commits, events, storageInput, schema, entityId) {
|
|
4470
|
+
loaded(entity, stored, commits, events, agentHistory, storageInput, schema, entityId) {
|
|
2738
4471
|
return Object.freeze({
|
|
2739
4472
|
commits,
|
|
2740
4473
|
events,
|
|
4474
|
+
...(agentHistory === undefined ? {} : { agentHistory }),
|
|
2741
4475
|
current: stored === undefined
|
|
2742
4476
|
? undefined
|
|
2743
4477
|
: EntityRecords.pack(schema, entityId, stored.state, stored.versionMessage, {
|
|
@@ -2863,8 +4597,30 @@ const RepositorySignals = {
|
|
|
2863
4597
|
* @returns Deferred rejection Event publication.
|
|
2864
4598
|
*/
|
|
2865
4599
|
postRejectionEvent(runtime, repository, command, entityId, rejection) {
|
|
4600
|
+
const event = this.rejectionEvent(runtime, repository, command, entityId, rejection);
|
|
4601
|
+
return async () => {
|
|
4602
|
+
try {
|
|
4603
|
+
// spine-log-boundary: server.repository_rejection_follow_up
|
|
4604
|
+
await runtime.publisher.publishRejectionEvent(event);
|
|
4605
|
+
}
|
|
4606
|
+
catch (error) {
|
|
4607
|
+
runtime.publisher.reportFailure("event", event, error);
|
|
4608
|
+
}
|
|
4609
|
+
};
|
|
4610
|
+
},
|
|
4611
|
+
/**
|
|
4612
|
+
* Creates the original declared rejection envelope without publishing it.
|
|
4613
|
+
*
|
|
4614
|
+
* @param runtime Bounded Context storage and publication services.
|
|
4615
|
+
* @param repository Registered Entity repository for this operation.
|
|
4616
|
+
* @param command Original typed Command envelope.
|
|
4617
|
+
* @param entityId Typed recipient Entity identifier.
|
|
4618
|
+
* @param rejection Declared rejection from the selected handler.
|
|
4619
|
+
* @returns Original declared-rejection Event envelope.
|
|
4620
|
+
*/
|
|
4621
|
+
rejectionEvent(runtime, repository, command, entityId, rejection) {
|
|
2866
4622
|
const metadata = runtime.signalMetadata.eventFromCommand(command, {});
|
|
2867
|
-
|
|
4623
|
+
return create(EventSchema, {
|
|
2868
4624
|
id: metadata.id,
|
|
2869
4625
|
message: AnyMessages.pack(rejection.schema, rejection.messageThrown()),
|
|
2870
4626
|
context: create(EventContextSchema, {
|
|
@@ -2875,15 +4631,6 @@ const RepositorySignals = {
|
|
|
2875
4631
|
}),
|
|
2876
4632
|
}),
|
|
2877
4633
|
});
|
|
2878
|
-
return async () => {
|
|
2879
|
-
try {
|
|
2880
|
-
// spine-log-boundary: server.repository_rejection_follow_up
|
|
2881
|
-
await runtime.publisher.publishRejectionEvent(event);
|
|
2882
|
-
}
|
|
2883
|
-
catch (error) {
|
|
2884
|
-
runtime.publisher.reportFailure("event", event, error);
|
|
2885
|
-
}
|
|
2886
|
-
};
|
|
2887
4634
|
},
|
|
2888
4635
|
/**
|
|
2889
4636
|
* Validates a nonempty Command ID for Event origin metadata.
|
|
@@ -3200,20 +4947,40 @@ class HandlerDispatchPublishing {
|
|
|
3200
4947
|
*/
|
|
3201
4948
|
command(runtime, repository, command, entityId) {
|
|
3202
4949
|
try {
|
|
3203
|
-
|
|
3204
|
-
origin: runtime.signalMetadata.originFromCommand(command),
|
|
3205
|
-
});
|
|
3206
|
-
const event = create(EventSchema, {
|
|
3207
|
-
id: runtime.signalMetadata.eventId(),
|
|
3208
|
-
message: AnyMessages.pack(EntityLog.CommandDispatchedToHandlerSchema, this.#message(repository, command, entityId, context.timestamp)),
|
|
3209
|
-
context,
|
|
3210
|
-
});
|
|
3211
|
-
this.#post(runtime, EntityLog.CommandDispatchedToHandlerSchema, event);
|
|
4950
|
+
this.publishCommand(runtime, this.commandEvent(runtime, repository, command, entityId));
|
|
3212
4951
|
}
|
|
3213
4952
|
catch (error) {
|
|
3214
4953
|
runtime.publisher.reportFailure("event", create(EventSchema), error);
|
|
3215
4954
|
}
|
|
3216
4955
|
}
|
|
4956
|
+
/**
|
|
4957
|
+
* Builds the genuine Command dispatch diagnostic before its publication.
|
|
4958
|
+
*
|
|
4959
|
+
* @param runtime Bounded Context signal metadata.
|
|
4960
|
+
* @param repository Receiving repository.
|
|
4961
|
+
* @param command Source Command.
|
|
4962
|
+
* @param entityId Target Entity identifier.
|
|
4963
|
+
* @returns Original System Event envelope.
|
|
4964
|
+
*/
|
|
4965
|
+
commandEvent(runtime, repository, command, entityId) {
|
|
4966
|
+
const context = runtime.signalMetadata.eventContext({
|
|
4967
|
+
origin: runtime.signalMetadata.originFromCommand(command),
|
|
4968
|
+
});
|
|
4969
|
+
return create(EventSchema, {
|
|
4970
|
+
id: runtime.signalMetadata.eventId(),
|
|
4971
|
+
message: AnyMessages.pack(EntityLog.CommandDispatchedToHandlerSchema, this.#message(repository, command, entityId, context.timestamp)),
|
|
4972
|
+
context,
|
|
4973
|
+
});
|
|
4974
|
+
}
|
|
4975
|
+
/**
|
|
4976
|
+
* Sends the exact System envelope already retained for an Agent.
|
|
4977
|
+
*
|
|
4978
|
+
* @param runtime Bounded Context publication services.
|
|
4979
|
+
* @param event Original System Event envelope.
|
|
4980
|
+
*/
|
|
4981
|
+
publishCommand(runtime, event) {
|
|
4982
|
+
this.#post(runtime, EntityLog.CommandDispatchedToHandlerSchema, event);
|
|
4983
|
+
}
|
|
3217
4984
|
/**
|
|
3218
4985
|
* Publishes a best-effort EventDispatchedToSubscriber diagnostic.
|
|
3219
4986
|
*
|
|
@@ -3236,6 +5003,34 @@ class HandlerDispatchPublishing {
|
|
|
3236
5003
|
reactor(runtime, repository, event, entityId) {
|
|
3237
5004
|
this.#publishEvent(runtime, repository, event, entityId, EntityLog.EventDispatchedToReactorSchema);
|
|
3238
5005
|
}
|
|
5006
|
+
/**
|
|
5007
|
+
* Builds the genuine Event-to-reactor diagnostic with its original envelope.
|
|
5008
|
+
*
|
|
5009
|
+
* @param runtime Bounded Context signal metadata.
|
|
5010
|
+
* @param repository Receiving repository.
|
|
5011
|
+
* @param source Source Event.
|
|
5012
|
+
* @param entityId Target Entity identifier.
|
|
5013
|
+
* @returns Original System Event envelope.
|
|
5014
|
+
*/
|
|
5015
|
+
reactorEvent(runtime, repository, source, entityId) {
|
|
5016
|
+
const context = runtime.signalMetadata.eventContext({
|
|
5017
|
+
origin: runtime.signalMetadata.originFromEvent(source),
|
|
5018
|
+
});
|
|
5019
|
+
return create(EventSchema, {
|
|
5020
|
+
id: runtime.signalMetadata.eventId(),
|
|
5021
|
+
message: AnyMessages.pack(EntityLog.EventDispatchedToReactorSchema, this.#eventMessage(EntityLog.EventDispatchedToReactorSchema, repository, source, entityId, context.timestamp)),
|
|
5022
|
+
context,
|
|
5023
|
+
});
|
|
5024
|
+
}
|
|
5025
|
+
/**
|
|
5026
|
+
* Publishes the same Event-to-reactor diagnostic retained by Agent history.
|
|
5027
|
+
*
|
|
5028
|
+
* @param runtime Bounded Context publication services.
|
|
5029
|
+
* @param event Original System Event envelope.
|
|
5030
|
+
*/
|
|
5031
|
+
publishReactor(runtime, event) {
|
|
5032
|
+
this.#post(runtime, EntityLog.EventDispatchedToReactorSchema, event);
|
|
5033
|
+
}
|
|
3239
5034
|
/**
|
|
3240
5035
|
* Builds and publishes one subscriber or reactor dispatch diagnostic.
|
|
3241
5036
|
*
|
|
@@ -3751,9 +5546,10 @@ const RepositoryHandlers = {
|
|
|
3751
5546
|
throw new RepositoryIdentityError("ENTITY_SCHEMA_KIND_MISMATCH", `Repository entity type "${entityType.name}" does not match the supplied handler metadata.`);
|
|
3752
5547
|
}
|
|
3753
5548
|
if (metadata.kind !== "process-manager" &&
|
|
5549
|
+
metadata.kind !== "entity" &&
|
|
3754
5550
|
(handlersMetadata.commandSubstitutions.length > 0 ||
|
|
3755
5551
|
handlersMetadata.commandReactions.length > 0)) {
|
|
3756
|
-
throw new RepositoryIdentityError("UNSUPPORTED_ENTITY_TYPE", "Only Process Manager repositories support @Command handlers.");
|
|
5552
|
+
throw new RepositoryIdentityError("UNSUPPORTED_ENTITY_TYPE", "Only Process Manager and Agent repositories support @Command handlers.");
|
|
3757
5553
|
}
|
|
3758
5554
|
}
|
|
3759
5555
|
},
|
|
@@ -4711,24 +6507,64 @@ const RepositoryStorage = {
|
|
|
4711
6507
|
* @typeParam S Generated Entity state type.
|
|
4712
6508
|
* @param factory Provider storage factory.
|
|
4713
6509
|
* @param input Entity storage location and schema.
|
|
6510
|
+
* @param agent Whether to open mandatory Agent history.
|
|
4714
6511
|
* @returns Storage ports closed together by one handle.
|
|
4715
6512
|
*/
|
|
4716
|
-
openEntityStorage(factory, input) {
|
|
6513
|
+
openEntityStorage(factory, input, agent = false) {
|
|
4717
6514
|
const candidate = factory;
|
|
4718
|
-
if (candidate.createEntityStorage === undefined)
|
|
4719
|
-
throw new Error("StorageFactory does not provide
|
|
4720
|
-
}
|
|
6515
|
+
if (candidate.createEntityStorage === undefined)
|
|
6516
|
+
throw new Error("StorageFactory does not provide required atomic Entity commit storage.");
|
|
4721
6517
|
const entity = candidate.createEntityStorage(input);
|
|
4722
6518
|
const commits = EntityCommitStorageFactories.create(factory, input);
|
|
6519
|
+
let ports;
|
|
6520
|
+
try {
|
|
6521
|
+
if (agent)
|
|
6522
|
+
ports = this.openAgentPorts(factory, input);
|
|
6523
|
+
}
|
|
6524
|
+
catch (error) {
|
|
6525
|
+
commits.close();
|
|
6526
|
+
entity.close();
|
|
6527
|
+
throw error;
|
|
6528
|
+
}
|
|
4723
6529
|
return {
|
|
4724
6530
|
...entity,
|
|
4725
6531
|
commits,
|
|
6532
|
+
...ports,
|
|
4726
6533
|
close: () => {
|
|
6534
|
+
ports?.agentExecution.close();
|
|
6535
|
+
ports?.agentHistory.close();
|
|
4727
6536
|
commits.close();
|
|
4728
6537
|
entity.close();
|
|
4729
6538
|
},
|
|
4730
6539
|
};
|
|
4731
6540
|
},
|
|
6541
|
+
/**
|
|
6542
|
+
* Opens both mandatory Agent ports and closes the first if the second fails.
|
|
6543
|
+
*
|
|
6544
|
+
* @typeParam I Typed Agent identifier.
|
|
6545
|
+
* @typeParam S Generated Agent state message.
|
|
6546
|
+
* @param factory Storage factory registered with the Bounded Context.
|
|
6547
|
+
* @param input Original Inbox or storage input.
|
|
6548
|
+
* @returns Indexed history and execution handles for the same tenant.
|
|
6549
|
+
*/
|
|
6550
|
+
openAgentPorts(factory, input) {
|
|
6551
|
+
const agentHistory = AgentHistoryStorageFactories.create(factory, {
|
|
6552
|
+
context: input.context,
|
|
6553
|
+
stateType: input.sourceType.typeName,
|
|
6554
|
+
id: { key: input.id.key },
|
|
6555
|
+
});
|
|
6556
|
+
try {
|
|
6557
|
+
const agentExecution = AgentExecutionStorageFactories.create(factory, {
|
|
6558
|
+
entity: input,
|
|
6559
|
+
stateType: input.sourceType.typeName,
|
|
6560
|
+
});
|
|
6561
|
+
return { agentHistory, agentExecution };
|
|
6562
|
+
}
|
|
6563
|
+
catch (error) {
|
|
6564
|
+
agentHistory.close();
|
|
6565
|
+
throw error;
|
|
6566
|
+
}
|
|
6567
|
+
},
|
|
4732
6568
|
/**
|
|
4733
6569
|
* Returns one shared Entity storage handle per context and state type.
|
|
4734
6570
|
*
|
|
@@ -4742,7 +6578,7 @@ const RepositoryStorage = {
|
|
|
4742
6578
|
openRepositoryEntityStorage(repository, factory, input) {
|
|
4743
6579
|
const key = JSON.stringify({ context: input.context, state: input.sourceType.typeName });
|
|
4744
6580
|
let handles = repositoryEntityHandles.get(repository);
|
|
4745
|
-
const handle = RepositoryStorage.openEntityStorage(factory, input);
|
|
6581
|
+
const handle = RepositoryStorage.openEntityStorage(factory, input, repository.entityFamily === "agent");
|
|
4746
6582
|
if (handles === undefined) {
|
|
4747
6583
|
handles = new Map();
|
|
4748
6584
|
repositoryEntityHandles.set(repository, handles);
|
|
@@ -5202,21 +7038,40 @@ const DispatchGuards = {
|
|
|
5202
7038
|
async dispatchOnce(repository, runtime, event, entityId, eventId, journalEventId, depth, guard, dispatch) {
|
|
5203
7039
|
if (guard.completed.has(eventId))
|
|
5204
7040
|
return;
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
}
|
|
5210
|
-
finally {
|
|
5211
|
-
storage.close();
|
|
7041
|
+
if (repository.entityFamily === "agent") {
|
|
7042
|
+
await dispatch();
|
|
7043
|
+
DispatchGuards.rememberGuardCompletion(guard, eventId, depth);
|
|
7044
|
+
return;
|
|
5212
7045
|
}
|
|
5213
|
-
if (
|
|
7046
|
+
if (await this.wasDispatched(repository, runtime, event, entityId, journalEventId, depth)) {
|
|
5214
7047
|
DispatchGuards.rememberGuardCompletion(guard, eventId, depth);
|
|
5215
7048
|
return;
|
|
5216
7049
|
}
|
|
5217
7050
|
await dispatch();
|
|
5218
7051
|
DispatchGuards.rememberGuardCompletion(guard, eventId, depth);
|
|
5219
7052
|
},
|
|
7053
|
+
/**
|
|
7054
|
+
* Finds an already dispatched Process Manager Event in retained diagnostic history.
|
|
7055
|
+
*
|
|
7056
|
+
* @param repository Process Manager repository.
|
|
7057
|
+
* @param runtime Repository storage runtime.
|
|
7058
|
+
* @param event Incoming Event envelope.
|
|
7059
|
+
* @param entityId Target Process Manager identifier.
|
|
7060
|
+
* @param journalEventId Target-specific diagnostic identifier.
|
|
7061
|
+
* @param depth Maximum retained history read.
|
|
7062
|
+
* @returns Whether the Event was already dispatched.
|
|
7063
|
+
*/
|
|
7064
|
+
async wasDispatched(repository, runtime, event, entityId, journalEventId, depth) {
|
|
7065
|
+
const input = RepositoryStorage.entityStorageInput(repository, RepositoryTenants.storageContextForEvent(runtime.context, event));
|
|
7066
|
+
const storage = RepositoryStorage.openEntityStorage(runtime.storageFactory, input);
|
|
7067
|
+
try {
|
|
7068
|
+
const persisted = await storage.events.backward(entityId, depth);
|
|
7069
|
+
return persisted.some((candidate) => candidate.id?.value === journalEventId);
|
|
7070
|
+
}
|
|
7071
|
+
finally {
|
|
7072
|
+
storage.close();
|
|
7073
|
+
}
|
|
7074
|
+
},
|
|
5220
7075
|
/**
|
|
5221
7076
|
* Records a successful delivery and discards the oldest excess identifiers.
|
|
5222
7077
|
*
|
|
@@ -5461,6 +7316,10 @@ const InboxReplay = {
|
|
|
5461
7316
|
* @returns Any Command follow-up work that must wait for Inbox completion.
|
|
5462
7317
|
*/
|
|
5463
7318
|
async replayPmInbox(repository, routing, message, deliveryTenantId) {
|
|
7319
|
+
if (repository.entityFamily === "agent") {
|
|
7320
|
+
await InboxReplay.replayAgentInbox(repository, routing, message, deliveryTenantId);
|
|
7321
|
+
return undefined;
|
|
7322
|
+
}
|
|
5464
7323
|
if (message.label === "HANDLE_COMMAND") {
|
|
5465
7324
|
return await InboxReplay.replayProcessManagerCommand(repository, routing, message, deliveryTenantId);
|
|
5466
7325
|
}
|
|
@@ -5470,6 +7329,152 @@ const InboxReplay = {
|
|
|
5470
7329
|
}
|
|
5471
7330
|
throw new Error(`Entity Inbox replay does not handle "${message.label}" messages.`);
|
|
5472
7331
|
},
|
|
7332
|
+
/**
|
|
7333
|
+
* Persists selected Agent work before the normal Inbox acknowledges delivery.
|
|
7334
|
+
*
|
|
7335
|
+
* @param repository Agent repository receiving the original signal.
|
|
7336
|
+
* @param routing Registered handler routes and schemas.
|
|
7337
|
+
* @param message Original durable Inbox message.
|
|
7338
|
+
* @param deliveryTenantId Tenant selected by delivery.
|
|
7339
|
+
* @returns When the fenced repository operation completes.
|
|
7340
|
+
*/
|
|
7341
|
+
async replayAgentInbox(repository, routing, message, deliveryTenantId) {
|
|
7342
|
+
const runtime = repositoryRuntimes.get(repository);
|
|
7343
|
+
if (runtime === undefined)
|
|
7344
|
+
throw new Error("Agent Inbox replay requires a bound runtime.");
|
|
7345
|
+
if (message.label === "HANDLE_COMMAND") {
|
|
7346
|
+
await InboxReplay.replayAgentCommand(repository, routing, runtime, message, deliveryTenantId);
|
|
7347
|
+
return;
|
|
7348
|
+
}
|
|
7349
|
+
if (message.label !== "REACT_UPON_EVENT")
|
|
7350
|
+
throw new Error(`Agent Inbox replay does not handle "${message.label}" messages.`);
|
|
7351
|
+
await InboxReplay.replayAgentEvent(repository, routing, runtime, message, deliveryTenantId);
|
|
7352
|
+
},
|
|
7353
|
+
/**
|
|
7354
|
+
* Admits a Command using its stored target and registered assignee.
|
|
7355
|
+
*
|
|
7356
|
+
* @param repository Agent repository.
|
|
7357
|
+
* @param routing Registered Command routes.
|
|
7358
|
+
* @param runtime Bounded Context services.
|
|
7359
|
+
* @param message Original Inbox row.
|
|
7360
|
+
* @param tenantId Delivery tenant.
|
|
7361
|
+
* @returns When the fenced repository operation completes.
|
|
7362
|
+
*/
|
|
7363
|
+
async replayAgentCommand(repository, routing, runtime, message, tenantId) {
|
|
7364
|
+
const command = InboxMessages.readInboxCommand(message);
|
|
7365
|
+
InboxReplay.validateReplayTenant(runtime.context, tenantId, command);
|
|
7366
|
+
InboxReplay.validateReplayedCommandPayload(routing, command);
|
|
7367
|
+
const route = InboxReplay.replayCommandRoute(repository, routing, message, command);
|
|
7368
|
+
const assignee = routing.commandReadiness?.findCommandAssignee(route.messageFullTypeName);
|
|
7369
|
+
if (assignee === undefined)
|
|
7370
|
+
throw new Error("Agent Command has no selected assignee.");
|
|
7371
|
+
await InboxReplay.admitAgent(repository, runtime, message, tenantId, route.entityId, { kind: "command", value: command }, command.context?.actorContext ?? create(ActorContextSchema), [assignee.registeredHandler]);
|
|
7372
|
+
},
|
|
7373
|
+
/**
|
|
7374
|
+
* Admits matching Event handlers from the stored target without rerouting.
|
|
7375
|
+
*
|
|
7376
|
+
* @param repository Agent repository.
|
|
7377
|
+
* @param routing Registered Event routes.
|
|
7378
|
+
* @param runtime Bounded Context services.
|
|
7379
|
+
* @param message Original Inbox row.
|
|
7380
|
+
* @param tenantId Delivery tenant.
|
|
7381
|
+
* @returns When the fenced repository operation completes.
|
|
7382
|
+
*/
|
|
7383
|
+
async replayAgentEvent(repository, routing, runtime, message, tenantId) {
|
|
7384
|
+
const event = InboxMessages.readPmInboxEvent(message);
|
|
7385
|
+
InboxReplay.validatePmReplayTenant(runtime.context, tenantId, event);
|
|
7386
|
+
InboxReplay.validateReplayedEventPayload(routing, event, "Agent Inbox replay requires an Event payload.");
|
|
7387
|
+
const route = InboxReplay.replayEventRoute(repository, routing, message, event, "Agent Inbox replay");
|
|
7388
|
+
const handlers = InboxReplay.agentEventHandlers(routing, route.messageFullTypeName, event);
|
|
7389
|
+
if (handlers.length === 0)
|
|
7390
|
+
return;
|
|
7391
|
+
await InboxReplay.admitAgent(repository, runtime, message, tenantId, route.entityIds[0], { kind: "event", value: event }, runtime.signalMetadata.originFromEvent(event).actorContext ?? create(ActorContextSchema), handlers);
|
|
7392
|
+
},
|
|
7393
|
+
/**
|
|
7394
|
+
* Restores selected Event and Command reactions from the original payload.
|
|
7395
|
+
*
|
|
7396
|
+
* @param routing Registered generated handler routing metadata.
|
|
7397
|
+
* @param typeName Generated message type name.
|
|
7398
|
+
* @param event Original typed Event envelope.
|
|
7399
|
+
* @returns Generated reactions selected for the accepted Event.
|
|
7400
|
+
*/
|
|
7401
|
+
agentEventHandlers(routing, typeName, event) {
|
|
7402
|
+
const packed = EntityInvocation.requireSignalMessage(event.message, "event");
|
|
7403
|
+
const schema = RepositoryRoutes.schemaForTypeUrl(routing.eventSchemas, packed.typeUrl, "event");
|
|
7404
|
+
const value = EntityInvocation.unpackRequired(packed, schema, "event");
|
|
7405
|
+
const external = event.context?.external === true;
|
|
7406
|
+
return [
|
|
7407
|
+
...routing.eventReactors(typeName, value, external),
|
|
7408
|
+
...routing.commandReactions(typeName, value, external),
|
|
7409
|
+
];
|
|
7410
|
+
},
|
|
7411
|
+
/**
|
|
7412
|
+
* Opens the actual tenant handle and records a selected invocation before handoff.
|
|
7413
|
+
*
|
|
7414
|
+
* @param repository Registered Agent repository.
|
|
7415
|
+
* @param runtime Bounded Context services including the effective registry.
|
|
7416
|
+
* @param message Durable Inbox receipt.
|
|
7417
|
+
* @param tenantId Selected delivery tenant.
|
|
7418
|
+
* @param entityId Typed target from the stored route.
|
|
7419
|
+
* @param signal Original source envelope.
|
|
7420
|
+
* @param actor Original actor metadata.
|
|
7421
|
+
* @param handlers Selected handler metadata in execution order.
|
|
7422
|
+
* @returns When the fenced repository operation completes.
|
|
7423
|
+
*/
|
|
7424
|
+
async admitAgent(repository, runtime, message, tenantId, entityId, signal, actor, handlers) {
|
|
7425
|
+
const revisions = InboxReplay.agentRevisions(repository, runtime, handlers);
|
|
7426
|
+
const input = RepositoryStorage.entityStorageInput(repository, RepositoryTenants.storageContextForTenant(runtime.context, tenantId));
|
|
7427
|
+
const execution = InboxReplay.agentExecutionHandle(repository, runtime, input);
|
|
7428
|
+
await execution.admit(AgentAdmission.create({
|
|
7429
|
+
stateType: repository.stateSchema.typeName,
|
|
7430
|
+
agentKey: input.id.key(entityId),
|
|
7431
|
+
recipientId: message.inboxId.targetId,
|
|
7432
|
+
signal,
|
|
7433
|
+
actor,
|
|
7434
|
+
inbox: message,
|
|
7435
|
+
handlers,
|
|
7436
|
+
codeRevision: revisions.codeRevision,
|
|
7437
|
+
schemaRevision: revisions.schema,
|
|
7438
|
+
policyRevision: revisions.policy,
|
|
7439
|
+
}));
|
|
7440
|
+
runtime.wakeAcceptedAgent(repository, tenantId);
|
|
7441
|
+
},
|
|
7442
|
+
/**
|
|
7443
|
+
* Opens the actual tenant's mandatory execution handle.
|
|
7444
|
+
*
|
|
7445
|
+
* @typeParam I Typed Agent identifier.
|
|
7446
|
+
* @typeParam S Generated Agent state message.
|
|
7447
|
+
* @param repository Registered Entity repository for this operation.
|
|
7448
|
+
* @param runtime Bounded Context storage and publication services.
|
|
7449
|
+
* @param input Original Inbox or storage input.
|
|
7450
|
+
* @returns Fenced execution handle for the accepted tenant.
|
|
7451
|
+
*/
|
|
7452
|
+
agentExecutionHandle(repository, runtime, input) {
|
|
7453
|
+
const storage = RepositoryStorage.openRepositoryEntityStorage(repository, runtime.storageFactory, input);
|
|
7454
|
+
if (storage.agentExecution === undefined)
|
|
7455
|
+
throw new Error("Agent Inbox replay requires durable execution storage.");
|
|
7456
|
+
return storage.agentExecution;
|
|
7457
|
+
},
|
|
7458
|
+
/**
|
|
7459
|
+
* Records the registered code, schema, and AI policy before Inbox handoff.
|
|
7460
|
+
*
|
|
7461
|
+
* @param repository Registered Entity repository for this operation.
|
|
7462
|
+
* @param runtime Bounded Context storage and publication services.
|
|
7463
|
+
* @param handlers Generated handlers in their saved order.
|
|
7464
|
+
* @returns Pinned code, schema, and AI policy digests.
|
|
7465
|
+
*/
|
|
7466
|
+
agentRevisions(repository, runtime, handlers) {
|
|
7467
|
+
const ai = runtime.ai;
|
|
7468
|
+
const configuration = repositoryAccess.agentConfiguration(repository);
|
|
7469
|
+
if (ai === undefined ||
|
|
7470
|
+
configuration.ai === undefined ||
|
|
7471
|
+
configuration.codeRevision === undefined)
|
|
7472
|
+
throw new Error("Agent Inbox replay requires registered AI capabilities and revisions.");
|
|
7473
|
+
return {
|
|
7474
|
+
codeRevision: configuration.codeRevision,
|
|
7475
|
+
...AgentRevisions.atAdmission(repository.stateSchema, handlers, ai, configuration.ai),
|
|
7476
|
+
};
|
|
7477
|
+
},
|
|
5473
7478
|
/**
|
|
5474
7479
|
* Dispatches a stored Process Manager Command after tenant and payload validation.
|
|
5475
7480
|
*
|
|
@@ -5780,9 +7785,10 @@ const InboxHandoff = {
|
|
|
5780
7785
|
* @param runtime Inbox, storage, tenant mode, and delivery strategy.
|
|
5781
7786
|
* @param command Command envelope to retain and deliver.
|
|
5782
7787
|
* @returns Completion of Inbox receipt and its selected delivery operation.
|
|
7788
|
+
* @param acceptedRoute Recipient route saved when the signal was accepted.
|
|
5783
7789
|
*/
|
|
5784
|
-
async handoffEntityCommand(repository, runtime, command) {
|
|
5785
|
-
const route = await repository.routeCommand(command);
|
|
7790
|
+
async handoffEntityCommand(repository, runtime, command, acceptedRoute) {
|
|
7791
|
+
const route = acceptedRoute ?? (await repository.routeCommand(command));
|
|
5786
7792
|
const commandId = RepositorySignals.requireCommandId(command);
|
|
5787
7793
|
const keepUntil = new Date(Time.currentTimeMillis() + inboxDedupMs);
|
|
5788
7794
|
const deliveryTenantId = RepositoryTenants.requireCommandTenant(runtime.context, command);
|
|
@@ -5903,6 +7909,182 @@ const InboxHandoff = {
|
|
|
5903
7909
|
},
|
|
5904
7910
|
};
|
|
5905
7911
|
Object.freeze(InboxHandoff);
|
|
7912
|
+
/**
|
|
7913
|
+
* Rebinds generated repository recipients without repeating accepted route queries.
|
|
7914
|
+
*/
|
|
7915
|
+
const SavedRepositoryDispatch = {
|
|
7916
|
+
/**
|
|
7917
|
+
* Maps a generated repository family to its serialized binding value.
|
|
7918
|
+
*
|
|
7919
|
+
* @param repository Registered Entity repository for this operation.
|
|
7920
|
+
* @returns Repository family encoded in a saved dispatch target.
|
|
7921
|
+
*/
|
|
7922
|
+
family(repository) {
|
|
7923
|
+
switch (repository.entityFamily) {
|
|
7924
|
+
case "aggregate":
|
|
7925
|
+
return AgentSavedRepositoryFamily.AGENT_SAVED_AGGREGATE;
|
|
7926
|
+
case "projection":
|
|
7927
|
+
return AgentSavedRepositoryFamily.AGENT_SAVED_PROJECTION;
|
|
7928
|
+
case "process-manager":
|
|
7929
|
+
return AgentSavedRepositoryFamily.AGENT_SAVED_PROCESS_MANAGER;
|
|
7930
|
+
case "agent":
|
|
7931
|
+
return AgentSavedRepositoryFamily.AGENT_SAVED_AGENT;
|
|
7932
|
+
}
|
|
7933
|
+
},
|
|
7934
|
+
/**
|
|
7935
|
+
* Finds the current generated handlers that determine an Event handoff.
|
|
7936
|
+
*
|
|
7937
|
+
* @param routing Registered generated handler routing metadata.
|
|
7938
|
+
* @param event Original typed Event envelope.
|
|
7939
|
+
* @returns Generated handlers matching the outgoing Event.
|
|
7940
|
+
*/
|
|
7941
|
+
eventHandlers(routing, event) {
|
|
7942
|
+
const packed = EntityInvocation.requireSignalMessage(event.message, "event");
|
|
7943
|
+
const schema = RepositoryRoutes.schemaForTypeUrl(routing.eventSchemas, packed.typeUrl, "event");
|
|
7944
|
+
const value = EntityInvocation.unpackRequired(packed, schema, "event");
|
|
7945
|
+
const external = event.context?.external === true;
|
|
7946
|
+
return [
|
|
7947
|
+
...routing.eventReactors(schema.typeName, value, external),
|
|
7948
|
+
...routing.commandReactions(schema.typeName, value, external),
|
|
7949
|
+
...routing
|
|
7950
|
+
.eventSubscribers(schema.typeName, value, external)
|
|
7951
|
+
.map((entry) => entry.registeredHandler),
|
|
7952
|
+
];
|
|
7953
|
+
},
|
|
7954
|
+
/**
|
|
7955
|
+
* Calculates a digest of ordered generated metadata and message descriptors.
|
|
7956
|
+
*
|
|
7957
|
+
* @param repository Registered Entity repository for this operation.
|
|
7958
|
+
* @param handlers Generated handlers in their saved order.
|
|
7959
|
+
* @returns Digest of ordered generated handler metadata.
|
|
7960
|
+
*/
|
|
7961
|
+
fingerprint(repository, handlers) {
|
|
7962
|
+
const selected = handlers.map(({ entity, handler }, ordinal) => ({
|
|
7963
|
+
ordinal,
|
|
7964
|
+
receiver: entity.schema.typeName,
|
|
7965
|
+
kind: handler.kind,
|
|
7966
|
+
method: handler.methodName,
|
|
7967
|
+
signal: handler.messageFullTypeName,
|
|
7968
|
+
origin: handler.origin,
|
|
7969
|
+
parameters: handler.parameterCount,
|
|
7970
|
+
where: handler.where === undefined ? null : [handler.where.eventField, handler.where.equals],
|
|
7971
|
+
descriptor: Buffer.from(toBinary(FileDescriptorProtoSchema, handler.schema.file.proto)).toString("base64"),
|
|
7972
|
+
}));
|
|
7973
|
+
const state = Buffer.from(toBinary(FileDescriptorProtoSchema, repository.stateSchema.file.proto));
|
|
7974
|
+
return createHash("sha256").update(state).update(JSON.stringify(selected)).digest("hex");
|
|
7975
|
+
},
|
|
7976
|
+
/**
|
|
7977
|
+
* Restores a typed ID only when its canonical wire representation is unchanged.
|
|
7978
|
+
*
|
|
7979
|
+
* @param repository Registered Entity repository for this operation.
|
|
7980
|
+
* @param packed Original typed Protobuf payload.
|
|
7981
|
+
* @returns Typed recipient identifier from the frozen route.
|
|
7982
|
+
*/
|
|
7983
|
+
recipient(repository, packed) {
|
|
7984
|
+
const id = InboxMessages.targetEntityId(packed, repository.idField);
|
|
7985
|
+
if (!equals(AnySchema, InboxMessages.inboxTargetId(id, repository.idField), packed))
|
|
7986
|
+
throw new Error("Saved Agent output recipient ID is not canonical for its repository.");
|
|
7987
|
+
return id;
|
|
7988
|
+
},
|
|
7989
|
+
/**
|
|
7990
|
+
* Checks the stable repository and signal identity before saved transport.
|
|
7991
|
+
*
|
|
7992
|
+
* @param repository Registered Entity repository for this operation.
|
|
7993
|
+
* @param target Frozen recipient binding.
|
|
7994
|
+
* @param kind Generation or decision model kind.
|
|
7995
|
+
* @returns Whether the checked condition holds.
|
|
7996
|
+
*/
|
|
7997
|
+
matches(repository, target, kind) {
|
|
7998
|
+
return (target.kind === kind &&
|
|
7999
|
+
target.receiverStateType === repository.stateSchema.typeName &&
|
|
8000
|
+
target.repositoryFamily === SavedRepositoryDispatch.family(repository));
|
|
8001
|
+
},
|
|
8002
|
+
/**
|
|
8003
|
+
* Prepares one typed Command recipient with its selected generated assignee.
|
|
8004
|
+
*
|
|
8005
|
+
* @param repository Registered Entity repository for this operation.
|
|
8006
|
+
* @param routing Registered generated handler routing metadata.
|
|
8007
|
+
* @param command Original typed Command envelope.
|
|
8008
|
+
* @returns Validated frozen Command recipient target.
|
|
8009
|
+
*/
|
|
8010
|
+
async prepareCommand(repository, routing, command) {
|
|
8011
|
+
const route = await repository.routeCommand(command);
|
|
8012
|
+
const assignee = routing.commandReadiness?.findCommandAssignee(route.messageFullTypeName);
|
|
8013
|
+
if (assignee === undefined)
|
|
8014
|
+
throw new Error("Saved Agent Command has no generated assignee.");
|
|
8015
|
+
return create(SavedTargetSchema, {
|
|
8016
|
+
kind: AgentSavedTargetKind.AGENT_SAVED_REPOSITORY_COMMAND,
|
|
8017
|
+
receiverStateType: repository.stateSchema.typeName,
|
|
8018
|
+
repositoryFamily: SavedRepositoryDispatch.family(repository),
|
|
8019
|
+
signalType: route.messageFullTypeName,
|
|
8020
|
+
bindingFingerprint: SavedRepositoryDispatch.fingerprint(repository, [
|
|
8021
|
+
assignee.registeredHandler,
|
|
8022
|
+
]),
|
|
8023
|
+
recipients: [InboxMessages.inboxTargetId(route.entityId, repository.idField)],
|
|
8024
|
+
});
|
|
8025
|
+
},
|
|
8026
|
+
/**
|
|
8027
|
+
* Resolves the original Command recipient without calling its route again.
|
|
8028
|
+
*
|
|
8029
|
+
* @param repository Registered Entity repository for this operation.
|
|
8030
|
+
* @param routing Registered generated handler routing metadata.
|
|
8031
|
+
* @param command Original typed Command envelope.
|
|
8032
|
+
* @param target Frozen recipient binding.
|
|
8033
|
+
* @returns When the fenced repository operation completes.
|
|
8034
|
+
*/
|
|
8035
|
+
bindCommand(repository, routing, command, target) {
|
|
8036
|
+
const { runtime, signalType, recipient } = SavedRepositoryDispatch.requireCommandBinding(repository, routing, command, target);
|
|
8037
|
+
const entityId = SavedRepositoryDispatch.recipient(repository, recipient);
|
|
8038
|
+
const route = {
|
|
8039
|
+
entityId,
|
|
8040
|
+
messageFullTypeName: signalType,
|
|
8041
|
+
invocation: "deferred",
|
|
8042
|
+
};
|
|
8043
|
+
return Promise.resolve(() => InboxHandoff.handoffEntityCommand(repository, runtime, command, route));
|
|
8044
|
+
},
|
|
8045
|
+
/**
|
|
8046
|
+
* Checks saved Command metadata, typed target presence, and runtime registration.
|
|
8047
|
+
*
|
|
8048
|
+
* @param repository Registered Entity repository for this operation.
|
|
8049
|
+
* @param routing Registered generated handler routing metadata.
|
|
8050
|
+
* @param command Original typed Command envelope.
|
|
8051
|
+
* @param target Frozen recipient binding.
|
|
8052
|
+
* @returns Registered Command route and original recipient.
|
|
8053
|
+
*/
|
|
8054
|
+
requireCommandBinding(repository, routing, command, target) {
|
|
8055
|
+
const packed = EntityInvocation.requireSignalMessage(command.message, "command");
|
|
8056
|
+
const schema = RepositoryRoutes.schemaForTypeUrl(routing.commandSchemas, packed.typeUrl, "command");
|
|
8057
|
+
const assignee = routing.commandReadiness?.findCommandAssignee(schema.typeName);
|
|
8058
|
+
const fingerprint = assignee === undefined
|
|
8059
|
+
? ""
|
|
8060
|
+
: SavedRepositoryDispatch.fingerprint(repository, [assignee.registeredHandler]);
|
|
8061
|
+
const recipient = target.recipients[0];
|
|
8062
|
+
const runtime = repositoryRuntimes.get(repository);
|
|
8063
|
+
if (!SavedRepositoryDispatch.matches(repository, target, AgentSavedTargetKind.AGENT_SAVED_REPOSITORY_COMMAND) ||
|
|
8064
|
+
target.signalType !== schema.typeName ||
|
|
8065
|
+
target.bindingFingerprint !== fingerprint ||
|
|
8066
|
+
target.recipients.length !== 1 ||
|
|
8067
|
+
recipient === undefined ||
|
|
8068
|
+
runtime === undefined)
|
|
8069
|
+
throw new Error("Saved Agent Command repository binding changed before delivery.");
|
|
8070
|
+
return { runtime, signalType: schema.typeName, recipient };
|
|
8071
|
+
},
|
|
8072
|
+
/**
|
|
8073
|
+
* Creates one internal Command binding from the repository registration.
|
|
8074
|
+
*
|
|
8075
|
+
* @param repository Registered Entity repository for this operation.
|
|
8076
|
+
* @param routing Registered generated handler routing metadata.
|
|
8077
|
+
* @returns Saved Command binding for this registration.
|
|
8078
|
+
*/
|
|
8079
|
+
commandBinding(repository, routing) {
|
|
8080
|
+
return {
|
|
8081
|
+
prepare: (command) => SavedRepositoryDispatch.prepareCommand(repository, routing, command),
|
|
8082
|
+
matches: (target) => SavedRepositoryDispatch.matches(repository, target, AgentSavedTargetKind.AGENT_SAVED_REPOSITORY_COMMAND),
|
|
8083
|
+
bind: (command, target) => SavedRepositoryDispatch.bindCommand(repository, routing, command, target),
|
|
8084
|
+
};
|
|
8085
|
+
},
|
|
8086
|
+
};
|
|
8087
|
+
Object.freeze(SavedRepositoryDispatch);
|
|
5906
8088
|
/**
|
|
5907
8089
|
* Connects bus dispatchers and Inbox replay to repository execution.
|
|
5908
8090
|
*/
|
|
@@ -5931,12 +8113,13 @@ const RepositoryDispatch = {
|
|
|
5931
8113
|
* @returns The dispatcher, or undefined when no Command schema is registered.
|
|
5932
8114
|
*/
|
|
5933
8115
|
commandDispatcher(repository, routing) {
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
8116
|
+
if (routing.commandSchemas.length === 0)
|
|
8117
|
+
return undefined;
|
|
8118
|
+
const dispatcher = Object.freeze({
|
|
8119
|
+
messageSchemas: () => routing.commandSchemas,
|
|
8120
|
+
dispatch: (command) => RepositoryDispatch.dispatchRepositoryCommand(repository, routing, command),
|
|
8121
|
+
});
|
|
8122
|
+
return SavedDispatcherBindings.command(dispatcher, SavedRepositoryDispatch.commandBinding(repository, routing));
|
|
5940
8123
|
},
|
|
5941
8124
|
/**
|
|
5942
8125
|
* Creates an Event dispatcher that preserves routes computed during acceptance.
|
|
@@ -5961,7 +8144,8 @@ const RepositoryDispatch = {
|
|
|
5961
8144
|
return RepositoryDispatch.dispatchRepositoryEvent(repository, routing, event, route);
|
|
5962
8145
|
},
|
|
5963
8146
|
});
|
|
5964
|
-
|
|
8147
|
+
const originAware = EventDispatcherOriginSchemas.define(dispatcher, routing.domesticEventSchemas, routing.externalEventSchemas);
|
|
8148
|
+
return originAware;
|
|
5965
8149
|
},
|
|
5966
8150
|
/**
|
|
5967
8151
|
* Creates a system-Event dispatcher for registered Entity-state subscriptions.
|
|
@@ -6010,7 +8194,9 @@ const RepositoryDispatch = {
|
|
|
6010
8194
|
* @returns The Inbox target and supported operation labels, when applicable.
|
|
6011
8195
|
*/
|
|
6012
8196
|
createEntityInboxTarget(repository, routing) {
|
|
6013
|
-
if ((repository.entityFamily !== "aggregate" &&
|
|
8197
|
+
if ((repository.entityFamily !== "aggregate" &&
|
|
8198
|
+
repository.entityFamily !== "process-manager" &&
|
|
8199
|
+
repository.entityFamily !== "agent") ||
|
|
6014
8200
|
(routing.commandSchemas.length === 0 && routing.eventSchemas.length === 0)) {
|
|
6015
8201
|
return undefined;
|
|
6016
8202
|
}
|
|
@@ -6018,7 +8204,9 @@ const RepositoryDispatch = {
|
|
|
6018
8204
|
targetTypeUrl: TypeUrls.derive(repository.stateSchema),
|
|
6019
8205
|
labels: Object.freeze([
|
|
6020
8206
|
...(routing.commandSchemas.length === 0 ? [] : ["HANDLE_COMMAND"]),
|
|
6021
|
-
...(repository.entityFamily === "process-manager"
|
|
8207
|
+
...((repository.entityFamily === "process-manager" ||
|
|
8208
|
+
repository.entityFamily === "agent") &&
|
|
8209
|
+
routing.eventSchemas.length > 0
|
|
6022
8210
|
? ["REACT_UPON_EVENT"]
|
|
6023
8211
|
: []),
|
|
6024
8212
|
]),
|
|
@@ -6082,6 +8270,7 @@ const RepositoryDispatch = {
|
|
|
6082
8270
|
await RepositoryDispatch.dispatchAggregateEvent(repository, routing, runtime, event, route);
|
|
6083
8271
|
return;
|
|
6084
8272
|
case "process-manager":
|
|
8273
|
+
case "agent":
|
|
6085
8274
|
await new ProcessManagerEventExecution(repository, routing, runtime, event).run(route);
|
|
6086
8275
|
return;
|
|
6087
8276
|
case "projection":
|
|
@@ -6146,7 +8335,7 @@ const RepositoryDispatch = {
|
|
|
6146
8335
|
await InboxHandoff.handoffEntityCommand(repository, runtime, command);
|
|
6147
8336
|
return;
|
|
6148
8337
|
}
|
|
6149
|
-
if (repository.entityFamily === "process-manager") {
|
|
8338
|
+
if (repository.entityFamily === "process-manager" || repository.entityFamily === "agent") {
|
|
6150
8339
|
await InboxHandoff.handoffEntityCommand(repository, runtime, command);
|
|
6151
8340
|
return;
|
|
6152
8341
|
}
|