@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/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 (opts.forkPointer) {
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
- if (opts.forkPointer) {
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
- const target = this.resolveForkPointerTarget(flat, opts.forkPointer, sourceRoot.streams.main);
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 (opts.forkPointer) {
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 && opts.forkPointer ? { forkPointer: opts.forkPointer } : void 0);
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 && entity.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
- if (agentUrlPath(parsed.from_agent) !== principalAgentUrl) return apiError(400, ErrCodeInvalidRequest, `Request from_agent must match authenticated agent principal`);
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`,