@electric-ax/agents-server 0.4.16 → 0.4.18
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/dist/entrypoint.js +169 -12
- package/dist/index.cjs +168 -11
- package/dist/index.d.cts +304 -236
- package/dist/index.d.ts +304 -236
- package/dist/index.js +169 -12
- package/drizzle/0014_entity_type_slash_commands.sql +1 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +5 -5
- package/src/db/schema.ts +1 -0
- package/src/electric-agents-types.ts +4 -0
- package/src/entity-manager.ts +210 -12
- package/src/entity-registry.ts +7 -0
- package/src/routing/entities-router.ts +122 -1
- package/src/routing/entity-types-router.ts +23 -0
- package/src/utils/server-utils.ts +1 -1
package/dist/index.cjs
CHANGED
|
@@ -78,6 +78,7 @@ const entityTypes = (0, drizzle_orm_pg_core.pgTable)(`entity_types`, {
|
|
|
78
78
|
creationSchema: (0, drizzle_orm_pg_core.jsonb)(`creation_schema`),
|
|
79
79
|
inboxSchemas: (0, drizzle_orm_pg_core.jsonb)(`inbox_schemas`),
|
|
80
80
|
stateSchemas: (0, drizzle_orm_pg_core.jsonb)(`state_schemas`),
|
|
81
|
+
slashCommands: (0, drizzle_orm_pg_core.jsonb)(`slash_commands`),
|
|
81
82
|
serveEndpoint: (0, drizzle_orm_pg_core.text)(`serve_endpoint`),
|
|
82
83
|
defaultDispatchPolicy: (0, drizzle_orm_pg_core.jsonb)(`default_dispatch_policy`),
|
|
83
84
|
revision: (0, drizzle_orm_pg_core.integer)(`revision`).notNull().default(1),
|
|
@@ -849,6 +850,7 @@ var PostgresRegistry = class {
|
|
|
849
850
|
creationSchema: et.creation_schema ?? null,
|
|
850
851
|
inboxSchemas: et.inbox_schemas ?? null,
|
|
851
852
|
stateSchemas: et.state_schemas ?? null,
|
|
853
|
+
slashCommands: et.slash_commands ?? null,
|
|
852
854
|
serveEndpoint: et.serve_endpoint ?? null,
|
|
853
855
|
defaultDispatchPolicy: et.default_dispatch_policy ?? null,
|
|
854
856
|
revision: et.revision,
|
|
@@ -861,6 +863,7 @@ var PostgresRegistry = class {
|
|
|
861
863
|
creationSchema: et.creation_schema ?? null,
|
|
862
864
|
inboxSchemas: et.inbox_schemas ?? null,
|
|
863
865
|
stateSchemas: et.state_schemas ?? null,
|
|
866
|
+
slashCommands: et.slash_commands ?? null,
|
|
864
867
|
serveEndpoint: et.serve_endpoint ?? null,
|
|
865
868
|
defaultDispatchPolicy: et.default_dispatch_policy ?? null,
|
|
866
869
|
revision: et.revision,
|
|
@@ -878,6 +881,7 @@ var PostgresRegistry = class {
|
|
|
878
881
|
creationSchema: et.creation_schema ?? null,
|
|
879
882
|
inboxSchemas: et.inbox_schemas ?? null,
|
|
880
883
|
stateSchemas: et.state_schemas ?? null,
|
|
884
|
+
slashCommands: et.slash_commands ?? null,
|
|
881
885
|
serveEndpoint: et.serve_endpoint ?? null,
|
|
882
886
|
defaultDispatchPolicy: et.default_dispatch_policy ?? null,
|
|
883
887
|
revision: et.revision,
|
|
@@ -904,6 +908,7 @@ var PostgresRegistry = class {
|
|
|
904
908
|
creationSchema: et.creation_schema ?? null,
|
|
905
909
|
inboxSchemas: et.inbox_schemas ?? null,
|
|
906
910
|
stateSchemas: et.state_schemas ?? null,
|
|
911
|
+
slashCommands: et.slash_commands ?? null,
|
|
907
912
|
serveEndpoint: et.serve_endpoint ?? null,
|
|
908
913
|
defaultDispatchPolicy: et.default_dispatch_policy ?? null,
|
|
909
914
|
revision: et.revision,
|
|
@@ -1494,6 +1499,7 @@ var PostgresRegistry = class {
|
|
|
1494
1499
|
creation_schema: row.creationSchema,
|
|
1495
1500
|
inbox_schemas: row.inboxSchemas,
|
|
1496
1501
|
state_schemas: row.stateSchemas,
|
|
1502
|
+
slash_commands: row.slashCommands ?? void 0,
|
|
1497
1503
|
serve_endpoint: row.serveEndpoint ?? void 0,
|
|
1498
1504
|
default_dispatch_policy: row.defaultDispatchPolicy ?? void 0,
|
|
1499
1505
|
revision: row.revision,
|
|
@@ -3387,6 +3393,7 @@ var EntityManager = class {
|
|
|
3387
3393
|
this.validateSchema(req.creation_schema);
|
|
3388
3394
|
this.validateSchemaMap(req.inbox_schemas);
|
|
3389
3395
|
this.validateSchemaMap(req.state_schemas);
|
|
3396
|
+
this.validateSlashCommands(req.slash_commands);
|
|
3390
3397
|
const defaultDispatchPolicy = req.default_dispatch_policy ? this.validateDispatchPolicy(req.default_dispatch_policy, { label: `default_dispatch_policy` }) : void 0;
|
|
3391
3398
|
const existing = await this.registry.getEntityType(req.name);
|
|
3392
3399
|
const now = new Date().toISOString();
|
|
@@ -3396,6 +3403,7 @@ var EntityManager = class {
|
|
|
3396
3403
|
creation_schema: req.creation_schema,
|
|
3397
3404
|
inbox_schemas: req.inbox_schemas,
|
|
3398
3405
|
state_schemas: req.state_schemas,
|
|
3406
|
+
slash_commands: req.slash_commands,
|
|
3399
3407
|
serve_endpoint: req.serve_endpoint,
|
|
3400
3408
|
default_dispatch_policy: defaultDispatchPolicy,
|
|
3401
3409
|
revision: existing ? existing.revision + 1 : 1,
|
|
@@ -3557,6 +3565,18 @@ var EntityManager = class {
|
|
|
3557
3565
|
}
|
|
3558
3566
|
});
|
|
3559
3567
|
const initialEvents = [createdEvent];
|
|
3568
|
+
const slashCommandTimestamp = new Date().toISOString();
|
|
3569
|
+
for (const command of entityType.slash_commands ?? []) {
|
|
3570
|
+
const slashCommandEvent = __electric_ax_agents_runtime.entityStateSchema.slashCommands.insert({
|
|
3571
|
+
key: command.name,
|
|
3572
|
+
value: {
|
|
3573
|
+
...command,
|
|
3574
|
+
source: `static`,
|
|
3575
|
+
updated_at: slashCommandTimestamp
|
|
3576
|
+
}
|
|
3577
|
+
});
|
|
3578
|
+
initialEvents.push(slashCommandEvent);
|
|
3579
|
+
}
|
|
3560
3580
|
if (req.initialMessage !== void 0) {
|
|
3561
3581
|
const msgNow = new Date().toISOString();
|
|
3562
3582
|
const inboxEvent = __electric_ax_agents_runtime.entityStateSchema.inbox.insert({
|
|
@@ -3564,6 +3584,7 @@ var EntityManager = class {
|
|
|
3564
3584
|
value: {
|
|
3565
3585
|
from: req.created_by ?? req.parent ?? `spawn`,
|
|
3566
3586
|
payload: req.initialMessage,
|
|
3587
|
+
message_type: req.initialMessageType,
|
|
3567
3588
|
timestamp: msgNow
|
|
3568
3589
|
}
|
|
3569
3590
|
});
|
|
@@ -3643,9 +3664,10 @@ var EntityManager = class {
|
|
|
3643
3664
|
const workLocks = new Set();
|
|
3644
3665
|
const writeEntityLocks = new Set();
|
|
3645
3666
|
const writeStreamLocks = new Set();
|
|
3667
|
+
const usePointerPath = opts.forkPointer !== void 0 || opts.anchor !== void 0;
|
|
3646
3668
|
try {
|
|
3647
3669
|
let sourceTree;
|
|
3648
|
-
if (
|
|
3670
|
+
if (usePointerPath) {
|
|
3649
3671
|
const rootEntity = await this.registry.getEntity(rootUrl);
|
|
3650
3672
|
if (!rootEntity) throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404);
|
|
3651
3673
|
if (isTerminalEntityStatus(rootEntity.status)) throw new ElectricAgentsError(ErrCodeNotRunning, `Cannot fork terminal entity "${rootEntity.url}"`, 409);
|
|
@@ -3654,10 +3676,13 @@ var EntityManager = class {
|
|
|
3654
3676
|
const sourceRoot = sourceTree[0];
|
|
3655
3677
|
if (sourceRoot.parent) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Only top-level entities can be forked`, 400);
|
|
3656
3678
|
let preFilteredRoot;
|
|
3657
|
-
|
|
3679
|
+
let effectiveForkPointer = opts.forkPointer;
|
|
3680
|
+
if (usePointerPath) {
|
|
3658
3681
|
const sourceEvents = await this.streamClient.readJson(sourceRoot.streams.main);
|
|
3659
3682
|
const flat = sourceEvents.flatMap((item) => Array.isArray(item) ? item : [item]);
|
|
3660
|
-
|
|
3683
|
+
if (!effectiveForkPointer && opts.anchor === `latest_completed_run`) effectiveForkPointer = this.resolveLatestCompletedRunPointer(flat, sourceRoot.streams.main);
|
|
3684
|
+
if (!effectiveForkPointer) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Internal: pointer-path fork with no resolved pointer`, 500);
|
|
3685
|
+
const target = this.resolveForkPointerTarget(flat, effectiveForkPointer, sourceRoot.streams.main);
|
|
3661
3686
|
const filteredEvents = flat.slice(0, target);
|
|
3662
3687
|
const rootManifests = this.reduceStateRows(filteredEvents, `manifest`);
|
|
3663
3688
|
const sharedStateIds = new Set();
|
|
@@ -3670,7 +3695,7 @@ var EntityManager = class {
|
|
|
3670
3695
|
};
|
|
3671
3696
|
}
|
|
3672
3697
|
const effectiveSubtree = preFilteredRoot ? this.computeEffectiveSubtree(sourceTree, sourceRoot.url, preFilteredRoot.manifests) : sourceTree;
|
|
3673
|
-
if (
|
|
3698
|
+
if (usePointerPath) {
|
|
3674
3699
|
const descendants = effectiveSubtree.filter((entity) => entity.url !== sourceRoot.url);
|
|
3675
3700
|
if (descendants.length > 0) await this.waitForGivenEntitiesIdle(descendants, opts, workLocks);
|
|
3676
3701
|
}
|
|
@@ -3695,6 +3720,14 @@ var EntityManager = class {
|
|
|
3695
3720
|
const sharedStateIdMap = await this.buildForkSharedStateIdMap(snapshot.sharedStateIds, suffix);
|
|
3696
3721
|
const stringMap = this.buildForkStringMap(entityUrlMap, sharedStateIdMap);
|
|
3697
3722
|
const entityPlans = this.buildForkEntityPlans(effectiveSubtree, entityUrlMap, stringMap, opts.createdBy);
|
|
3723
|
+
const rootPlanForOverrides = entityPlans.find((plan) => plan.source.url === rootUrl);
|
|
3724
|
+
if (rootPlanForOverrides) {
|
|
3725
|
+
if (opts.parent !== void 0) rootPlanForOverrides.fork.parent = opts.parent;
|
|
3726
|
+
if (opts.tags !== void 0) rootPlanForOverrides.fork.tags = {
|
|
3727
|
+
...rootPlanForOverrides.fork.tags ?? {},
|
|
3728
|
+
...opts.tags
|
|
3729
|
+
};
|
|
3730
|
+
}
|
|
3698
3731
|
this.addForkLocks(this.forkWriteLockedEntities, effectiveSubtree.map((entity) => entity.url), writeEntityLocks);
|
|
3699
3732
|
this.addForkLocks(this.forkWriteLockedStreams, [...snapshot.sharedStateIds].map((id) => (0, __electric_ax_agents_runtime.getSharedStateStreamPath)(id)), writeStreamLocks);
|
|
3700
3733
|
const createdStreams = [];
|
|
@@ -3703,7 +3736,7 @@ var EntityManager = class {
|
|
|
3703
3736
|
try {
|
|
3704
3737
|
for (const plan of entityPlans) {
|
|
3705
3738
|
const isRoot = plan.source.url === rootUrl;
|
|
3706
|
-
await this.streamClient.fork(plan.fork.streams.main, plan.source.streams.main, isRoot &&
|
|
3739
|
+
await this.streamClient.fork(plan.fork.streams.main, plan.source.streams.main, isRoot && effectiveForkPointer ? { forkPointer: effectiveForkPointer } : void 0);
|
|
3707
3740
|
createdStreams.push(plan.fork.streams.main);
|
|
3708
3741
|
}
|
|
3709
3742
|
for (const [sourceId, forkId] of sharedStateIdMap) {
|
|
@@ -3730,6 +3763,20 @@ var EntityManager = class {
|
|
|
3730
3763
|
await this.registry.createEntity(plan.fork);
|
|
3731
3764
|
createdEntities.push(plan.fork.url);
|
|
3732
3765
|
}
|
|
3766
|
+
if (opts.wake !== void 0) {
|
|
3767
|
+
const rootPlan = entityPlans.find((plan) => plan.source.url === rootUrl);
|
|
3768
|
+
if (rootPlan) await this.wakeRegistry.register({
|
|
3769
|
+
tenantId: this.tenantId,
|
|
3770
|
+
subscriberUrl: opts.wake.subscriberUrl,
|
|
3771
|
+
sourceUrl: rootPlan.fork.url,
|
|
3772
|
+
condition: opts.wake.condition,
|
|
3773
|
+
debounceMs: opts.wake.debounceMs,
|
|
3774
|
+
timeoutMs: opts.wake.timeoutMs,
|
|
3775
|
+
oneShot: false,
|
|
3776
|
+
includeResponse: opts.wake.includeResponse,
|
|
3777
|
+
manifestKey: opts.wake.manifestKey
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3733
3780
|
for (const plan of entityPlans) {
|
|
3734
3781
|
const manifests = activeManifestsByEntity.get(plan.fork.url) ?? new Map();
|
|
3735
3782
|
await this.materializeForkManifestSideEffects(plan.fork.url, manifests);
|
|
@@ -3890,6 +3937,45 @@ var EntityManager = class {
|
|
|
3890
3937
|
return positionAtAnchor + pointer.subOffset;
|
|
3891
3938
|
}
|
|
3892
3939
|
/**
|
|
3940
|
+
* Find an `EventPointer` that addresses the most recent `runs` row on
|
|
3941
|
+
* the source's `main` stream with `status === 'completed'`. Mirrors the
|
|
3942
|
+
* eligibility rule the per-row "Fork from here" UI applies — only
|
|
3943
|
+
* completed runs are valid fork anchors; `started`/`failed` rows are
|
|
3944
|
+
* skipped.
|
|
3945
|
+
*
|
|
3946
|
+
* The pointer is computed in the same coordinate system the runtime
|
|
3947
|
+
* mints pointers in (see entity-stream-db's onBeforeBatch): for a row
|
|
3948
|
+
* R in log entry E, the pointer is `{ offset: P, subOffset: K }` where
|
|
3949
|
+
* `P` is the END offset of the log entry preceding E (or `null` for
|
|
3950
|
+
* stream start) and `K` is R's 1-indexed position within E.
|
|
3951
|
+
*/
|
|
3952
|
+
resolveLatestCompletedRunPointer(events, streamPath) {
|
|
3953
|
+
let priorEntryOffset = null;
|
|
3954
|
+
let currentEntryOffset = null;
|
|
3955
|
+
let positionInEntry = 0;
|
|
3956
|
+
let latest = null;
|
|
3957
|
+
for (const event of events) {
|
|
3958
|
+
const headers = isRecord(event.headers) ? event.headers : void 0;
|
|
3959
|
+
const eventOffset = typeof headers?.offset === `string` ? headers.offset : null;
|
|
3960
|
+
if (eventOffset !== currentEntryOffset) {
|
|
3961
|
+
priorEntryOffset = currentEntryOffset;
|
|
3962
|
+
currentEntryOffset = eventOffset;
|
|
3963
|
+
positionInEntry = 0;
|
|
3964
|
+
}
|
|
3965
|
+
positionInEntry++;
|
|
3966
|
+
if (event.type !== `run`) continue;
|
|
3967
|
+
if (headers?.operation === `delete`) continue;
|
|
3968
|
+
if (!isRecord(event.value)) continue;
|
|
3969
|
+
if (event.value.status !== `completed`) continue;
|
|
3970
|
+
latest = {
|
|
3971
|
+
offset: priorEntryOffset,
|
|
3972
|
+
subOffset: positionInEntry
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3975
|
+
if (!latest) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Source ${streamPath} has no completed run to fork from`, 400);
|
|
3976
|
+
return latest;
|
|
3977
|
+
}
|
|
3978
|
+
/**
|
|
3893
3979
|
* Compute the subset of `sourceTree` that survives the manifest filter
|
|
3894
3980
|
* applied at the root. After filtering the root's manifest at the fork
|
|
3895
3981
|
* pointer, only children whose manifest entries landed at or before the
|
|
@@ -5009,7 +5095,9 @@ var EntityManager = class {
|
|
|
5009
5095
|
creation_schema: existing.creation_schema,
|
|
5010
5096
|
inbox_schemas: mergedInbox,
|
|
5011
5097
|
state_schemas: mergedState,
|
|
5098
|
+
slash_commands: existing.slash_commands,
|
|
5012
5099
|
serve_endpoint: existing.serve_endpoint,
|
|
5100
|
+
default_dispatch_policy: existing.default_dispatch_policy,
|
|
5013
5101
|
revision: nextRevision,
|
|
5014
5102
|
created_at: existing.created_at,
|
|
5015
5103
|
updated_at: now
|
|
@@ -5063,11 +5151,19 @@ var EntityManager = class {
|
|
|
5063
5151
|
throw new ElectricAgentsError(ErrCodeInvalidRequest, error instanceof Error ? error.message : `Invalid tags`, 400);
|
|
5064
5152
|
}
|
|
5065
5153
|
}
|
|
5154
|
+
validateSlashCommands(input) {
|
|
5155
|
+
const validationError = (0, __electric_ax_agents_runtime.validateSlashCommandDefinitions)(input);
|
|
5156
|
+
if (!validationError) return;
|
|
5157
|
+
throw new ElectricAgentsError(ErrCodeSchemaValidationFailed, validationError.message, 422, validationError.details);
|
|
5158
|
+
}
|
|
5066
5159
|
async validateSendRequest(entityUrl, req) {
|
|
5067
5160
|
const entity = await this.registry.getEntity(entityUrl);
|
|
5068
5161
|
if (!entity) throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404);
|
|
5069
5162
|
if (rejectsNormalWrites(entity.status)) throw new ElectricAgentsError(ErrCodeNotRunning, `Entity is not accepting writes`, 409);
|
|
5070
|
-
if (req.type
|
|
5163
|
+
if (req.type === __electric_ax_agents_runtime.COMPOSER_INPUT_MESSAGE_TYPE) {
|
|
5164
|
+
const valErr = (0, __electric_ax_agents_runtime.validateComposerInputPayload)(req.payload);
|
|
5165
|
+
if (valErr) throw new ElectricAgentsError(ErrCodeSchemaValidationFailed, valErr.message, 422, valErr.details);
|
|
5166
|
+
} else if (req.type && entity.type) {
|
|
5071
5167
|
const { inboxSchemas } = await this.getEffectiveSchemas(entity);
|
|
5072
5168
|
if (inboxSchemas) {
|
|
5073
5169
|
const schema = inboxSchemas[req.type];
|
|
@@ -7140,7 +7236,7 @@ function buildElectricProxyTarget(options) {
|
|
|
7140
7236
|
permissionBypass: options.permissionBypass
|
|
7141
7237
|
}));
|
|
7142
7238
|
} else if (table === `entity_types`) {
|
|
7143
|
-
target.searchParams.set(`columns`, `"tenant_id","name","description","creation_schema","inbox_schemas","state_schemas","serve_endpoint","default_dispatch_policy","revision","created_at","updated_at"`);
|
|
7239
|
+
target.searchParams.set(`columns`, `"tenant_id","name","description","creation_schema","inbox_schemas","state_schemas","slash_commands","serve_endpoint","default_dispatch_policy","revision","created_at","updated_at"`);
|
|
7144
7240
|
applyShapeWhere(target, buildSpawnableEntityTypesWhere({
|
|
7145
7241
|
tenantId: options.tenantId,
|
|
7146
7242
|
principalUrl: options.principalUrl ?? ``,
|
|
@@ -7948,6 +8044,7 @@ const spawnBodySchema = __sinclair_typebox.Type.Object({
|
|
|
7948
8044
|
sandbox: __sinclair_typebox.Type.Optional(sandboxChoiceSchema),
|
|
7949
8045
|
initialMessage: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Unknown()),
|
|
7950
8046
|
grants: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Array(entityPermissionGrantInputSchema)),
|
|
8047
|
+
initialMessageType: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
|
|
7951
8048
|
wake: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Object({
|
|
7952
8049
|
subscriberUrl: __sinclair_typebox.Type.String(),
|
|
7953
8050
|
condition: wakeConditionSchema,
|
|
@@ -7985,6 +8082,14 @@ function agentUrlPath(value) {
|
|
|
7985
8082
|
return value;
|
|
7986
8083
|
}
|
|
7987
8084
|
}
|
|
8085
|
+
async function hasValidAgentWriteToken(request, ctx, fromAgent) {
|
|
8086
|
+
const agentUrl = agentUrlPath(fromAgent);
|
|
8087
|
+
const token = writeTokenFromRequest(request);
|
|
8088
|
+
if (!token) return false;
|
|
8089
|
+
const agentEntity = await ctx.entityManager.registry.getEntity(agentUrl);
|
|
8090
|
+
if (!agentEntity) return false;
|
|
8091
|
+
return ctx.entityManager.isValidWriteToken(agentEntity, token);
|
|
8092
|
+
}
|
|
7988
8093
|
const inboxMessageBodySchema = __sinclair_typebox.Type.Object({
|
|
7989
8094
|
payload: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Unknown()),
|
|
7990
8095
|
position: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
|
|
@@ -8006,7 +8111,19 @@ const forkBodySchema = __sinclair_typebox.Type.Object({
|
|
|
8006
8111
|
fork_pointer: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Object({
|
|
8007
8112
|
offset: __sinclair_typebox.Type.Union([__sinclair_typebox.Type.String(), __sinclair_typebox.Type.Null()]),
|
|
8008
8113
|
sub_offset: __sinclair_typebox.Type.Number()
|
|
8009
|
-
}))
|
|
8114
|
+
})),
|
|
8115
|
+
anchor: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Literal(`latest_completed_run`)),
|
|
8116
|
+
parent: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
|
|
8117
|
+
wake: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Object({
|
|
8118
|
+
subscriberUrl: __sinclair_typebox.Type.String(),
|
|
8119
|
+
condition: wakeConditionSchema,
|
|
8120
|
+
debounceMs: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Number()),
|
|
8121
|
+
timeoutMs: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Number()),
|
|
8122
|
+
includeResponse: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Boolean()),
|
|
8123
|
+
manifestKey: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String())
|
|
8124
|
+
})),
|
|
8125
|
+
initialMessage: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Unknown()),
|
|
8126
|
+
tags: __sinclair_typebox.Type.Optional(stringRecordSchema$1)
|
|
8010
8127
|
});
|
|
8011
8128
|
const setTagBodySchema = __sinclair_typebox.Type.Object({ value: __sinclair_typebox.Type.String() });
|
|
8012
8129
|
const entitySignalSchema = __sinclair_typebox.Type.Union([
|
|
@@ -8393,8 +8510,18 @@ async function forkEntity(request, ctx) {
|
|
|
8393
8510
|
const principalMutationError = rejectPrincipalEntityMutation(request, `forked`);
|
|
8394
8511
|
if (principalMutationError) return principalMutationError;
|
|
8395
8512
|
const parsed = routeBody(request);
|
|
8513
|
+
if (parsed.fork_pointer && parsed.anchor) return apiError(400, ErrCodeInvalidRequest, `fork_pointer and anchor are mutually exclusive`);
|
|
8396
8514
|
const { entityUrl, entity } = requireExistingEntityRoute(request);
|
|
8397
8515
|
await assertDispatchPolicyAllowed(ctx, entity.dispatch_policy);
|
|
8516
|
+
if (parsed.parent !== void 0) {
|
|
8517
|
+
const parent = await ctx.entityManager.registry.getEntity(parsed.parent);
|
|
8518
|
+
if (!parent) return apiError(404, ErrCodeNotFound, `Parent entity not found`);
|
|
8519
|
+
if (!await canAccessEntity(ctx, parent, `spawn`, request)) return apiError(401, ErrCodeUnauthorized, `Principal is not allowed to spawn children from ${parent.url}`);
|
|
8520
|
+
}
|
|
8521
|
+
if (parsed.wake !== void 0) {
|
|
8522
|
+
if (parsed.parent === void 0) return apiError(400, ErrCodeInvalidRequest, `wake requires parent (the fork's wake fires to its parent)`);
|
|
8523
|
+
if (parsed.wake.subscriberUrl !== parsed.parent) return apiError(401, ErrCodeUnauthorized, `wake.subscriberUrl must match parent`);
|
|
8524
|
+
}
|
|
8398
8525
|
const result = await ctx.entityManager.forkSubtree(entityUrl, {
|
|
8399
8526
|
rootInstanceId: parsed.instance_id,
|
|
8400
8527
|
waitTimeoutMs: parsed.waitTimeoutMs,
|
|
@@ -8402,9 +8529,17 @@ async function forkEntity(request, ctx) {
|
|
|
8402
8529
|
...parsed.fork_pointer && { forkPointer: {
|
|
8403
8530
|
offset: parsed.fork_pointer.offset,
|
|
8404
8531
|
subOffset: parsed.fork_pointer.sub_offset
|
|
8405
|
-
} }
|
|
8532
|
+
} },
|
|
8533
|
+
...parsed.anchor && { anchor: parsed.anchor },
|
|
8534
|
+
...parsed.parent !== void 0 && { parent: parsed.parent },
|
|
8535
|
+
...parsed.wake !== void 0 && { wake: parsed.wake },
|
|
8536
|
+
...parsed.tags !== void 0 && { tags: parsed.tags }
|
|
8406
8537
|
});
|
|
8407
8538
|
for (const forkedEntity of result.entities) await linkEntityDispatchSubscription(ctx, forkedEntity);
|
|
8539
|
+
if (parsed.initialMessage !== void 0) await ctx.entityManager.send(result.root.url, {
|
|
8540
|
+
from: parsed.parent ?? ctx.principal.url,
|
|
8541
|
+
payload: parsed.initialMessage
|
|
8542
|
+
});
|
|
8408
8543
|
return (0, itty_router.json)({
|
|
8409
8544
|
root: toPublicEntity(result.root),
|
|
8410
8545
|
entities: result.entities.map((entity$1) => toPublicEntity(entity$1))
|
|
@@ -8417,7 +8552,10 @@ async function sendEntity(request, ctx) {
|
|
|
8417
8552
|
if (parsed.from_principal !== void 0 && parsed.from_principal !== principal.url) return apiError(400, ErrCodeInvalidRequest, `Request from_principal must match Electric-Principal`);
|
|
8418
8553
|
if (parsed.from_agent !== void 0) {
|
|
8419
8554
|
const principalAgentUrl = agentUrlForPrincipal(principal);
|
|
8420
|
-
|
|
8555
|
+
const fromAgentUrl = agentUrlPath(parsed.from_agent);
|
|
8556
|
+
const matchesPrincipalAgent = fromAgentUrl === principalAgentUrl;
|
|
8557
|
+
const hasAgentWriteToken = await hasValidAgentWriteToken(request, ctx, parsed.from_agent);
|
|
8558
|
+
if (!matchesPrincipalAgent && !hasAgentWriteToken) return apiError(400, ErrCodeInvalidRequest, `Request from_agent must match authenticated agent principal`);
|
|
8421
8559
|
}
|
|
8422
8560
|
await ctx.entityManager.ensurePrincipal(principal);
|
|
8423
8561
|
const { entityUrl, entity } = requireExistingEntityRoute(request);
|
|
@@ -8503,6 +8641,7 @@ async function spawnEntity(request, ctx) {
|
|
|
8503
8641
|
dispatch_policy: dispatchPolicy,
|
|
8504
8642
|
sandbox: parsed.sandbox,
|
|
8505
8643
|
initialMessage: void 0,
|
|
8644
|
+
initialMessageType: void 0,
|
|
8506
8645
|
wake: parsed.wake,
|
|
8507
8646
|
created_by: principal.url
|
|
8508
8647
|
});
|
|
@@ -8521,7 +8660,8 @@ async function spawnEntity(request, ctx) {
|
|
|
8521
8660
|
if (linkBeforeInitialMessage) await linkEntityDispatchSubscription(ctx, entity);
|
|
8522
8661
|
if (parsed.initialMessage !== void 0) await ctx.entityManager.send(entity.url, {
|
|
8523
8662
|
from: principal.url,
|
|
8524
|
-
payload: parsed.initialMessage
|
|
8663
|
+
payload: parsed.initialMessage,
|
|
8664
|
+
type: parsed.initialMessageType
|
|
8525
8665
|
});
|
|
8526
8666
|
if (!linkBeforeInitialMessage) await linkEntityDispatchSubscription(ctx, entity);
|
|
8527
8667
|
return (0, itty_router.json)({
|
|
@@ -8568,6 +8708,21 @@ async function signalEntity(request, ctx) {
|
|
|
8568
8708
|
//#region src/routing/entity-types-router.ts
|
|
8569
8709
|
const jsonObjectSchema = __sinclair_typebox.Type.Record(__sinclair_typebox.Type.String(), __sinclair_typebox.Type.Unknown());
|
|
8570
8710
|
const schemaMapSchema = __sinclair_typebox.Type.Record(__sinclair_typebox.Type.String(), jsonObjectSchema);
|
|
8711
|
+
const slashCommandArgumentSchema = __sinclair_typebox.Type.Object({
|
|
8712
|
+
name: __sinclair_typebox.Type.String(),
|
|
8713
|
+
type: __sinclair_typebox.Type.Union([
|
|
8714
|
+
__sinclair_typebox.Type.Literal(`string`),
|
|
8715
|
+
__sinclair_typebox.Type.Literal(`number`),
|
|
8716
|
+
__sinclair_typebox.Type.Literal(`boolean`)
|
|
8717
|
+
]),
|
|
8718
|
+
required: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Boolean()),
|
|
8719
|
+
description: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String())
|
|
8720
|
+
}, { additionalProperties: false });
|
|
8721
|
+
const slashCommandSchema = __sinclair_typebox.Type.Object({
|
|
8722
|
+
name: __sinclair_typebox.Type.String(),
|
|
8723
|
+
description: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
|
|
8724
|
+
arguments: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Array(slashCommandArgumentSchema))
|
|
8725
|
+
}, { additionalProperties: false });
|
|
8571
8726
|
const typePermissionGrantInputSchema = __sinclair_typebox.Type.Object({
|
|
8572
8727
|
subject_kind: __sinclair_typebox.Type.Union([__sinclair_typebox.Type.Literal(`principal`), __sinclair_typebox.Type.Literal(`principal_kind`)]),
|
|
8573
8728
|
subject_value: __sinclair_typebox.Type.String(),
|
|
@@ -8580,6 +8735,7 @@ const registerEntityTypeBodySchema = __sinclair_typebox.Type.Object({
|
|
|
8580
8735
|
creation_schema: __sinclair_typebox.Type.Optional(jsonObjectSchema),
|
|
8581
8736
|
inbox_schemas: __sinclair_typebox.Type.Optional(schemaMapSchema),
|
|
8582
8737
|
state_schemas: __sinclair_typebox.Type.Optional(schemaMapSchema),
|
|
8738
|
+
slash_commands: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Array(slashCommandSchema)),
|
|
8583
8739
|
serve_endpoint: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
|
|
8584
8740
|
default_dispatch_policy: __sinclair_typebox.Type.Optional(dispatchPolicySchema),
|
|
8585
8741
|
permission_grants: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Array(typePermissionGrantInputSchema))
|
|
@@ -8721,6 +8877,7 @@ function normalizeEntityTypeRequest(parsed) {
|
|
|
8721
8877
|
creation_schema: parsed.creation_schema,
|
|
8722
8878
|
inbox_schemas: parsed.inbox_schemas,
|
|
8723
8879
|
state_schemas: parsed.state_schemas,
|
|
8880
|
+
slash_commands: parsed.slash_commands,
|
|
8724
8881
|
serve_endpoint: serveEndpoint,
|
|
8725
8882
|
default_dispatch_policy: parsed.default_dispatch_policy ?? (serveEndpoint ? { targets: [{
|
|
8726
8883
|
type: `webhook`,
|