@electric-ax/agents-server 0.4.16 → 0.4.17

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.js CHANGED
@@ -8,7 +8,7 @@ import postgres from "postgres";
8
8
  import { and, desc, eq, inArray, lt, ne, sql } from "drizzle-orm";
9
9
  import { bigint, bigserial, boolean, check, index, integer, jsonb, pgTable, primaryKey, serial, text, timestamp, unique } from "drizzle-orm/pg-core";
10
10
  import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomUUID, sign } from "node:crypto";
11
- import { appendPathToUrl, assertTags, buildEventSourceManifestEntry, buildTagsIndex, entityStateSchema, eventSourceSubscriptionManifestKey, getCronStreamPath, getCronStreamPathFromSpec, getEntitiesStreamPath, getNextCronFireAt, getSharedStateStreamPath, getWebhookStreamPath, hashString, manifestChildKey, manifestSharedStateKey, manifestSourceKey, normalizeTags, parseCronStreamPath, resolveCronScheduleSpec, resolveEventSourceSubscription, sourceRefForTags, verifyWebhookSignature } from "@electric-ax/agents-runtime";
11
+ import { COMPOSER_INPUT_MESSAGE_TYPE, appendPathToUrl, assertTags, buildEventSourceManifestEntry, buildTagsIndex, entityStateSchema, eventSourceSubscriptionManifestKey, getCronStreamPath, getCronStreamPathFromSpec, getEntitiesStreamPath, getNextCronFireAt, getSharedStateStreamPath, getWebhookStreamPath, hashString, manifestChildKey, manifestSharedStateKey, manifestSourceKey, normalizeTags, parseCronStreamPath, resolveCronScheduleSpec, resolveEventSourceSubscription, sourceRefForTags, validateComposerInputPayload, validateSlashCommandDefinitions, verifyWebhookSignature } from "@electric-ax/agents-runtime";
12
12
  import { DurableStream, DurableStreamError, FetchError, IdempotentProducer } from "@durable-streams/client";
13
13
  import { ShapeStream, isChangeMessage, isControlMessage } from "@electric-sql/client";
14
14
  import pino from "pino";
@@ -49,6 +49,7 @@ const entityTypes = pgTable(`entity_types`, {
49
49
  creationSchema: jsonb(`creation_schema`),
50
50
  inboxSchemas: jsonb(`inbox_schemas`),
51
51
  stateSchemas: jsonb(`state_schemas`),
52
+ slashCommands: jsonb(`slash_commands`),
52
53
  serveEndpoint: text(`serve_endpoint`),
53
54
  defaultDispatchPolicy: jsonb(`default_dispatch_policy`),
54
55
  revision: integer(`revision`).notNull().default(1),
@@ -820,6 +821,7 @@ var PostgresRegistry = class {
820
821
  creationSchema: et.creation_schema ?? null,
821
822
  inboxSchemas: et.inbox_schemas ?? null,
822
823
  stateSchemas: et.state_schemas ?? null,
824
+ slashCommands: et.slash_commands ?? null,
823
825
  serveEndpoint: et.serve_endpoint ?? null,
824
826
  defaultDispatchPolicy: et.default_dispatch_policy ?? null,
825
827
  revision: et.revision,
@@ -832,6 +834,7 @@ var PostgresRegistry = class {
832
834
  creationSchema: et.creation_schema ?? null,
833
835
  inboxSchemas: et.inbox_schemas ?? null,
834
836
  stateSchemas: et.state_schemas ?? null,
837
+ slashCommands: et.slash_commands ?? null,
835
838
  serveEndpoint: et.serve_endpoint ?? null,
836
839
  defaultDispatchPolicy: et.default_dispatch_policy ?? null,
837
840
  revision: et.revision,
@@ -849,6 +852,7 @@ var PostgresRegistry = class {
849
852
  creationSchema: et.creation_schema ?? null,
850
853
  inboxSchemas: et.inbox_schemas ?? null,
851
854
  stateSchemas: et.state_schemas ?? null,
855
+ slashCommands: et.slash_commands ?? null,
852
856
  serveEndpoint: et.serve_endpoint ?? null,
853
857
  defaultDispatchPolicy: et.default_dispatch_policy ?? null,
854
858
  revision: et.revision,
@@ -875,6 +879,7 @@ var PostgresRegistry = class {
875
879
  creationSchema: et.creation_schema ?? null,
876
880
  inboxSchemas: et.inbox_schemas ?? null,
877
881
  stateSchemas: et.state_schemas ?? null,
882
+ slashCommands: et.slash_commands ?? null,
878
883
  serveEndpoint: et.serve_endpoint ?? null,
879
884
  defaultDispatchPolicy: et.default_dispatch_policy ?? null,
880
885
  revision: et.revision,
@@ -1465,6 +1470,7 @@ var PostgresRegistry = class {
1465
1470
  creation_schema: row.creationSchema,
1466
1471
  inbox_schemas: row.inboxSchemas,
1467
1472
  state_schemas: row.stateSchemas,
1473
+ slash_commands: row.slashCommands ?? void 0,
1468
1474
  serve_endpoint: row.serveEndpoint ?? void 0,
1469
1475
  default_dispatch_policy: row.defaultDispatchPolicy ?? void 0,
1470
1476
  revision: row.revision,
@@ -3358,6 +3364,7 @@ var EntityManager = class {
3358
3364
  this.validateSchema(req.creation_schema);
3359
3365
  this.validateSchemaMap(req.inbox_schemas);
3360
3366
  this.validateSchemaMap(req.state_schemas);
3367
+ this.validateSlashCommands(req.slash_commands);
3361
3368
  const defaultDispatchPolicy = req.default_dispatch_policy ? this.validateDispatchPolicy(req.default_dispatch_policy, { label: `default_dispatch_policy` }) : void 0;
3362
3369
  const existing = await this.registry.getEntityType(req.name);
3363
3370
  const now = new Date().toISOString();
@@ -3367,6 +3374,7 @@ var EntityManager = class {
3367
3374
  creation_schema: req.creation_schema,
3368
3375
  inbox_schemas: req.inbox_schemas,
3369
3376
  state_schemas: req.state_schemas,
3377
+ slash_commands: req.slash_commands,
3370
3378
  serve_endpoint: req.serve_endpoint,
3371
3379
  default_dispatch_policy: defaultDispatchPolicy,
3372
3380
  revision: existing ? existing.revision + 1 : 1,
@@ -3528,6 +3536,18 @@ var EntityManager = class {
3528
3536
  }
3529
3537
  });
3530
3538
  const initialEvents = [createdEvent];
3539
+ const slashCommandTimestamp = new Date().toISOString();
3540
+ for (const command of entityType.slash_commands ?? []) {
3541
+ const slashCommandEvent = entityStateSchema.slashCommands.insert({
3542
+ key: command.name,
3543
+ value: {
3544
+ ...command,
3545
+ source: `static`,
3546
+ updated_at: slashCommandTimestamp
3547
+ }
3548
+ });
3549
+ initialEvents.push(slashCommandEvent);
3550
+ }
3531
3551
  if (req.initialMessage !== void 0) {
3532
3552
  const msgNow = new Date().toISOString();
3533
3553
  const inboxEvent = entityStateSchema.inbox.insert({
@@ -3535,6 +3555,7 @@ var EntityManager = class {
3535
3555
  value: {
3536
3556
  from: req.created_by ?? req.parent ?? `spawn`,
3537
3557
  payload: req.initialMessage,
3558
+ message_type: req.initialMessageType,
3538
3559
  timestamp: msgNow
3539
3560
  }
3540
3561
  });
@@ -4980,7 +5001,9 @@ var EntityManager = class {
4980
5001
  creation_schema: existing.creation_schema,
4981
5002
  inbox_schemas: mergedInbox,
4982
5003
  state_schemas: mergedState,
5004
+ slash_commands: existing.slash_commands,
4983
5005
  serve_endpoint: existing.serve_endpoint,
5006
+ default_dispatch_policy: existing.default_dispatch_policy,
4984
5007
  revision: nextRevision,
4985
5008
  created_at: existing.created_at,
4986
5009
  updated_at: now
@@ -5034,11 +5057,19 @@ var EntityManager = class {
5034
5057
  throw new ElectricAgentsError(ErrCodeInvalidRequest, error instanceof Error ? error.message : `Invalid tags`, 400);
5035
5058
  }
5036
5059
  }
5060
+ validateSlashCommands(input) {
5061
+ const validationError = validateSlashCommandDefinitions(input);
5062
+ if (!validationError) return;
5063
+ throw new ElectricAgentsError(ErrCodeSchemaValidationFailed, validationError.message, 422, validationError.details);
5064
+ }
5037
5065
  async validateSendRequest(entityUrl, req) {
5038
5066
  const entity = await this.registry.getEntity(entityUrl);
5039
5067
  if (!entity) throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404);
5040
5068
  if (rejectsNormalWrites(entity.status)) throw new ElectricAgentsError(ErrCodeNotRunning, `Entity is not accepting writes`, 409);
5041
- if (req.type && entity.type) {
5069
+ if (req.type === COMPOSER_INPUT_MESSAGE_TYPE) {
5070
+ const valErr = validateComposerInputPayload(req.payload);
5071
+ if (valErr) throw new ElectricAgentsError(ErrCodeSchemaValidationFailed, valErr.message, 422, valErr.details);
5072
+ } else if (req.type && entity.type) {
5042
5073
  const { inboxSchemas } = await this.getEffectiveSchemas(entity);
5043
5074
  if (inboxSchemas) {
5044
5075
  const schema = inboxSchemas[req.type];
@@ -7111,7 +7142,7 @@ function buildElectricProxyTarget(options) {
7111
7142
  permissionBypass: options.permissionBypass
7112
7143
  }));
7113
7144
  } else if (table === `entity_types`) {
7114
- target.searchParams.set(`columns`, `"tenant_id","name","description","creation_schema","inbox_schemas","state_schemas","serve_endpoint","default_dispatch_policy","revision","created_at","updated_at"`);
7145
+ 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"`);
7115
7146
  applyShapeWhere(target, buildSpawnableEntityTypesWhere({
7116
7147
  tenantId: options.tenantId,
7117
7148
  principalUrl: options.principalUrl ?? ``,
@@ -7919,6 +7950,7 @@ const spawnBodySchema = Type.Object({
7919
7950
  sandbox: Type.Optional(sandboxChoiceSchema),
7920
7951
  initialMessage: Type.Optional(Type.Unknown()),
7921
7952
  grants: Type.Optional(Type.Array(entityPermissionGrantInputSchema)),
7953
+ initialMessageType: Type.Optional(Type.String()),
7922
7954
  wake: Type.Optional(Type.Object({
7923
7955
  subscriberUrl: Type.String(),
7924
7956
  condition: wakeConditionSchema,
@@ -8474,6 +8506,7 @@ async function spawnEntity(request, ctx) {
8474
8506
  dispatch_policy: dispatchPolicy,
8475
8507
  sandbox: parsed.sandbox,
8476
8508
  initialMessage: void 0,
8509
+ initialMessageType: void 0,
8477
8510
  wake: parsed.wake,
8478
8511
  created_by: principal.url
8479
8512
  });
@@ -8492,7 +8525,8 @@ async function spawnEntity(request, ctx) {
8492
8525
  if (linkBeforeInitialMessage) await linkEntityDispatchSubscription(ctx, entity);
8493
8526
  if (parsed.initialMessage !== void 0) await ctx.entityManager.send(entity.url, {
8494
8527
  from: principal.url,
8495
- payload: parsed.initialMessage
8528
+ payload: parsed.initialMessage,
8529
+ type: parsed.initialMessageType
8496
8530
  });
8497
8531
  if (!linkBeforeInitialMessage) await linkEntityDispatchSubscription(ctx, entity);
8498
8532
  return json({
@@ -8539,6 +8573,21 @@ async function signalEntity(request, ctx) {
8539
8573
  //#region src/routing/entity-types-router.ts
8540
8574
  const jsonObjectSchema = Type.Record(Type.String(), Type.Unknown());
8541
8575
  const schemaMapSchema = Type.Record(Type.String(), jsonObjectSchema);
8576
+ const slashCommandArgumentSchema = Type.Object({
8577
+ name: Type.String(),
8578
+ type: Type.Union([
8579
+ Type.Literal(`string`),
8580
+ Type.Literal(`number`),
8581
+ Type.Literal(`boolean`)
8582
+ ]),
8583
+ required: Type.Optional(Type.Boolean()),
8584
+ description: Type.Optional(Type.String())
8585
+ }, { additionalProperties: false });
8586
+ const slashCommandSchema = Type.Object({
8587
+ name: Type.String(),
8588
+ description: Type.Optional(Type.String()),
8589
+ arguments: Type.Optional(Type.Array(slashCommandArgumentSchema))
8590
+ }, { additionalProperties: false });
8542
8591
  const typePermissionGrantInputSchema = Type.Object({
8543
8592
  subject_kind: Type.Union([Type.Literal(`principal`), Type.Literal(`principal_kind`)]),
8544
8593
  subject_value: Type.String(),
@@ -8551,6 +8600,7 @@ const registerEntityTypeBodySchema = Type.Object({
8551
8600
  creation_schema: Type.Optional(jsonObjectSchema),
8552
8601
  inbox_schemas: Type.Optional(schemaMapSchema),
8553
8602
  state_schemas: Type.Optional(schemaMapSchema),
8603
+ slash_commands: Type.Optional(Type.Array(slashCommandSchema)),
8554
8604
  serve_endpoint: Type.Optional(Type.String()),
8555
8605
  default_dispatch_policy: Type.Optional(dispatchPolicySchema),
8556
8606
  permission_grants: Type.Optional(Type.Array(typePermissionGrantInputSchema))
@@ -8692,6 +8742,7 @@ function normalizeEntityTypeRequest(parsed) {
8692
8742
  creation_schema: parsed.creation_schema,
8693
8743
  inbox_schemas: parsed.inbox_schemas,
8694
8744
  state_schemas: parsed.state_schemas,
8745
+ slash_commands: parsed.slash_commands,
8695
8746
  serve_endpoint: serveEndpoint,
8696
8747
  default_dispatch_policy: parsed.default_dispatch_policy ?? (serveEndpoint ? { targets: [{
8697
8748
  type: `webhook`,
@@ -0,0 +1 @@
1
+ ALTER TABLE "entity_types" ADD COLUMN "slash_commands" jsonb;
@@ -99,6 +99,13 @@
99
99
  "when": 1780588695000,
100
100
  "tag": "0013_worker_user_manage_permission",
101
101
  "breakpoints": true
102
+ },
103
+ {
104
+ "idx": 14,
105
+ "version": "7",
106
+ "when": 1780600000000,
107
+ "tag": "0014_entity_type_slash_commands",
108
+ "breakpoints": true
102
109
  }
103
110
  ]
104
111
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electric-ax/agents-server",
3
- "version": "0.4.16",
3
+ "version": "0.4.17",
4
4
  "description": "Electric Agents entity runtime server",
5
5
  "author": "Durable Stream contributors",
6
6
  "bin": {
@@ -54,7 +54,7 @@
54
54
  "pino-pretty": "^13.0.0",
55
55
  "postgres": "^3.4.0",
56
56
  "undici": "^7.24.7",
57
- "@electric-ax/agents-runtime": "0.3.9"
57
+ "@electric-ax/agents-runtime": "0.3.10"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "^22.19.15",
@@ -65,9 +65,9 @@
65
65
  "tsx": "^4.19.0",
66
66
  "typescript": "^5.0.0",
67
67
  "vitest": "^4.1.0",
68
- "@electric-ax/agents": "0.4.13",
69
- "@electric-ax/agents-server-conformance-tests": "0.1.11",
70
- "@electric-ax/agents-server-ui": "0.4.16"
68
+ "@electric-ax/agents": "0.4.14",
69
+ "@electric-ax/agents-server-ui": "0.4.17",
70
+ "@electric-ax/agents-server-conformance-tests": "0.1.11"
71
71
  },
72
72
  "files": [
73
73
  "dist",
package/src/db/schema.ts CHANGED
@@ -24,6 +24,7 @@ export const entityTypes = pgTable(
24
24
  creationSchema: jsonb(`creation_schema`),
25
25
  inboxSchemas: jsonb(`inbox_schemas`),
26
26
  stateSchemas: jsonb(`state_schemas`),
27
+ slashCommands: jsonb(`slash_commands`),
27
28
  serveEndpoint: text(`serve_endpoint`),
28
29
  defaultDispatchPolicy: jsonb(`default_dispatch_policy`),
29
30
  revision: integer(`revision`).notNull().default(1),
@@ -4,6 +4,7 @@
4
4
 
5
5
  import type {
6
6
  PullWakeRunnerHealth,
7
+ SlashCommandDefinition,
7
8
  WebhookNotification,
8
9
  } from '@electric-ax/agents-runtime'
9
10
  import type { Principal } from './principal.js'
@@ -499,6 +500,7 @@ export interface ElectricAgentsEntityType {
499
500
  creation_schema?: Record<string, unknown>
500
501
  inbox_schemas?: Record<string, Record<string, unknown>>
501
502
  state_schemas?: Record<string, Record<string, unknown>>
503
+ slash_commands?: Array<SlashCommandDefinition>
502
504
  serve_endpoint?: string
503
505
  default_dispatch_policy?: DispatchPolicy
504
506
  revision: number
@@ -512,6 +514,7 @@ export interface RegisterEntityTypeRequest {
512
514
  creation_schema?: Record<string, unknown>
513
515
  inbox_schemas?: Record<string, Record<string, unknown>>
514
516
  state_schemas?: Record<string, Record<string, unknown>>
517
+ slash_commands?: Array<SlashCommandDefinition>
515
518
  serve_endpoint?: string
516
519
  default_dispatch_policy?: DispatchPolicy
517
520
  permission_grants?: Array<EntityTypePermissionGrantInput>
@@ -530,6 +533,7 @@ export interface TypedSpawnRequest {
530
533
  */
531
534
  sandbox?: SandboxChoice
532
535
  initialMessage?: unknown
536
+ initialMessageType?: string
533
537
  created_by?: string
534
538
  wake?: {
535
539
  subscriberUrl: string
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomUUID } from 'node:crypto'
2
2
  import fastq from 'fastq'
3
3
  import {
4
+ COMPOSER_INPUT_MESSAGE_TYPE,
4
5
  assertTags,
5
6
  entityStateSchema,
6
7
  getCronStreamPath,
@@ -11,6 +12,8 @@ import {
11
12
  manifestSharedStateKey,
12
13
  manifestSourceKey,
13
14
  resolveCronScheduleSpec,
15
+ validateComposerInputPayload,
16
+ validateSlashCommandDefinitions,
14
17
  } from '@electric-ax/agents-runtime'
15
18
  import type { EventPointer } from '@electric-ax/agents-runtime'
16
19
  import {
@@ -430,6 +433,7 @@ export class EntityManager {
430
433
  this.validateSchema(req.creation_schema)
431
434
  this.validateSchemaMap(req.inbox_schemas)
432
435
  this.validateSchemaMap(req.state_schemas)
436
+ this.validateSlashCommands(req.slash_commands)
433
437
  const defaultDispatchPolicy = req.default_dispatch_policy
434
438
  ? this.validateDispatchPolicy(req.default_dispatch_policy, {
435
439
  label: `default_dispatch_policy`,
@@ -444,6 +448,7 @@ export class EntityManager {
444
448
  creation_schema: req.creation_schema,
445
449
  inbox_schemas: req.inbox_schemas,
446
450
  state_schemas: req.state_schemas,
451
+ slash_commands: req.slash_commands,
447
452
  serve_endpoint: req.serve_endpoint,
448
453
  default_dispatch_policy: defaultDispatchPolicy,
449
454
  revision: existing ? existing.revision + 1 : 1,
@@ -735,6 +740,19 @@ export class EntityManager {
735
740
  createdEvent as Record<string, unknown>,
736
741
  ]
737
742
 
743
+ const slashCommandTimestamp = new Date().toISOString()
744
+ for (const command of entityType.slash_commands ?? []) {
745
+ const slashCommandEvent = entityStateSchema.slashCommands.insert({
746
+ key: command.name,
747
+ value: {
748
+ ...command,
749
+ source: `static`,
750
+ updated_at: slashCommandTimestamp,
751
+ },
752
+ } as any)
753
+ initialEvents.push(slashCommandEvent as Record<string, unknown>)
754
+ }
755
+
738
756
  if (req.initialMessage !== undefined) {
739
757
  const msgNow = new Date().toISOString()
740
758
  const inboxEvent = entityStateSchema.inbox.insert({
@@ -742,6 +760,7 @@ export class EntityManager {
742
760
  value: {
743
761
  from: req.created_by ?? req.parent ?? `spawn`,
744
762
  payload: req.initialMessage,
763
+ message_type: req.initialMessageType,
745
764
  timestamp: msgNow,
746
765
  },
747
766
  } as any)
@@ -3512,7 +3531,9 @@ export class EntityManager {
3512
3531
  creation_schema: existing.creation_schema,
3513
3532
  inbox_schemas: mergedInbox,
3514
3533
  state_schemas: mergedState,
3534
+ slash_commands: existing.slash_commands,
3515
3535
  serve_endpoint: existing.serve_endpoint,
3536
+ default_dispatch_policy: existing.default_dispatch_policy,
3516
3537
  revision: nextRevision,
3517
3538
  created_at: existing.created_at,
3518
3539
  updated_at: now,
@@ -3601,6 +3622,20 @@ export class EntityManager {
3601
3622
  }
3602
3623
  }
3603
3624
 
3625
+ private validateSlashCommands(input: unknown): void {
3626
+ const validationError = validateSlashCommandDefinitions(input)
3627
+ if (!validationError) {
3628
+ return
3629
+ }
3630
+
3631
+ throw new ElectricAgentsError(
3632
+ ErrCodeSchemaValidationFailed,
3633
+ validationError.message,
3634
+ 422,
3635
+ validationError.details
3636
+ )
3637
+ }
3638
+
3604
3639
  private async validateSendRequest(
3605
3640
  entityUrl: string,
3606
3641
  req: SendRequest
@@ -3617,7 +3652,17 @@ export class EntityManager {
3617
3652
  )
3618
3653
  }
3619
3654
 
3620
- if (req.type && entity.type) {
3655
+ if (req.type === COMPOSER_INPUT_MESSAGE_TYPE) {
3656
+ const valErr = validateComposerInputPayload(req.payload)
3657
+ if (valErr) {
3658
+ throw new ElectricAgentsError(
3659
+ ErrCodeSchemaValidationFailed,
3660
+ valErr.message,
3661
+ 422,
3662
+ valErr.details
3663
+ )
3664
+ }
3665
+ } else if (req.type && entity.type) {
3621
3666
  const { inboxSchemas } = await this.getEffectiveSchemas(entity)
3622
3667
  if (inboxSchemas) {
3623
3668
  const schema = inboxSchemas[req.type]
@@ -633,6 +633,7 @@ export class PostgresRegistry {
633
633
  creationSchema: et.creation_schema ?? null,
634
634
  inboxSchemas: et.inbox_schemas ?? null,
635
635
  stateSchemas: et.state_schemas ?? null,
636
+ slashCommands: et.slash_commands ?? null,
636
637
  serveEndpoint: et.serve_endpoint ?? null,
637
638
  defaultDispatchPolicy: et.default_dispatch_policy ?? null,
638
639
  revision: et.revision,
@@ -646,6 +647,7 @@ export class PostgresRegistry {
646
647
  creationSchema: et.creation_schema ?? null,
647
648
  inboxSchemas: et.inbox_schemas ?? null,
648
649
  stateSchemas: et.state_schemas ?? null,
650
+ slashCommands: et.slash_commands ?? null,
649
651
  serveEndpoint: et.serve_endpoint ?? null,
650
652
  defaultDispatchPolicy: et.default_dispatch_policy ?? null,
651
653
  revision: et.revision,
@@ -668,6 +670,7 @@ export class PostgresRegistry {
668
670
  creationSchema: et.creation_schema ?? null,
669
671
  inboxSchemas: et.inbox_schemas ?? null,
670
672
  stateSchemas: et.state_schemas ?? null,
673
+ slashCommands: et.slash_commands ?? null,
671
674
  serveEndpoint: et.serve_endpoint ?? null,
672
675
  defaultDispatchPolicy: et.default_dispatch_policy ?? null,
673
676
  revision: et.revision,
@@ -709,6 +712,7 @@ export class PostgresRegistry {
709
712
  creationSchema: et.creation_schema ?? null,
710
713
  inboxSchemas: et.inbox_schemas ?? null,
711
714
  stateSchemas: et.state_schemas ?? null,
715
+ slashCommands: et.slash_commands ?? null,
712
716
  serveEndpoint: et.serve_endpoint ?? null,
713
717
  defaultDispatchPolicy: et.default_dispatch_policy ?? null,
714
718
  revision: et.revision,
@@ -1829,6 +1833,9 @@ export class PostgresRegistry {
1829
1833
  state_schemas: row.stateSchemas as
1830
1834
  | Record<string, Record<string, unknown>>
1831
1835
  | undefined,
1836
+ slash_commands:
1837
+ (row.slashCommands as ElectricAgentsEntityType[`slash_commands`]) ??
1838
+ undefined,
1832
1839
  serve_endpoint: row.serveEndpoint ?? undefined,
1833
1840
  default_dispatch_policy:
1834
1841
  (row.defaultDispatchPolicy as ElectricAgentsEntityType[`default_dispatch_policy`]) ??
@@ -135,6 +135,7 @@ const spawnBodySchema = Type.Object({
135
135
  sandbox: Type.Optional(sandboxChoiceSchema),
136
136
  initialMessage: Type.Optional(Type.Unknown()),
137
137
  grants: Type.Optional(Type.Array(entityPermissionGrantInputSchema)),
138
+ initialMessageType: Type.Optional(Type.String()),
138
139
  wake: Type.Optional(
139
140
  Type.Object({
140
141
  subscriberUrl: Type.String(),
@@ -1293,6 +1294,7 @@ async function spawnEntity(
1293
1294
  dispatch_policy: dispatchPolicy,
1294
1295
  sandbox: parsed.sandbox,
1295
1296
  initialMessage: undefined,
1297
+ initialMessageType: undefined,
1296
1298
  wake: parsed.wake,
1297
1299
  created_by: principal.url,
1298
1300
  })
@@ -1325,6 +1327,7 @@ async function spawnEntity(
1325
1327
  await ctx.entityManager.send(entity.url, {
1326
1328
  from: principal.url,
1327
1329
  payload: parsed.initialMessage,
1330
+ type: parsed.initialMessageType,
1328
1331
  })
1329
1332
  }
1330
1333
  if (!linkBeforeInitialMessage) {
@@ -45,6 +45,27 @@ type PublicEntityTypeResponse = ElectricAgentsEntityType & {
45
45
 
46
46
  const jsonObjectSchema = Type.Record(Type.String(), Type.Unknown())
47
47
  const schemaMapSchema = Type.Record(Type.String(), jsonObjectSchema)
48
+ const slashCommandArgumentSchema = Type.Object(
49
+ {
50
+ name: Type.String(),
51
+ type: Type.Union([
52
+ Type.Literal(`string`),
53
+ Type.Literal(`number`),
54
+ Type.Literal(`boolean`),
55
+ ]),
56
+ required: Type.Optional(Type.Boolean()),
57
+ description: Type.Optional(Type.String()),
58
+ },
59
+ { additionalProperties: false }
60
+ )
61
+ const slashCommandSchema = Type.Object(
62
+ {
63
+ name: Type.String(),
64
+ description: Type.Optional(Type.String()),
65
+ arguments: Type.Optional(Type.Array(slashCommandArgumentSchema)),
66
+ },
67
+ { additionalProperties: false }
68
+ )
48
69
 
49
70
  const typePermissionGrantInputSchema = Type.Object(
50
71
  {
@@ -66,6 +87,7 @@ const registerEntityTypeBodySchema = Type.Object(
66
87
  creation_schema: Type.Optional(jsonObjectSchema),
67
88
  inbox_schemas: Type.Optional(schemaMapSchema),
68
89
  state_schemas: Type.Optional(schemaMapSchema),
90
+ slash_commands: Type.Optional(Type.Array(slashCommandSchema)),
69
91
  serve_endpoint: Type.Optional(Type.String()),
70
92
  default_dispatch_policy: Type.Optional(dispatchPolicySchema),
71
93
  permission_grants: Type.Optional(
@@ -433,6 +455,7 @@ function normalizeEntityTypeRequest(
433
455
  creation_schema: parsed.creation_schema,
434
456
  inbox_schemas: parsed.inbox_schemas,
435
457
  state_schemas: parsed.state_schemas,
458
+ slash_commands: parsed.slash_commands,
436
459
  serve_endpoint: serveEndpoint,
437
460
  default_dispatch_policy:
438
461
  parsed.default_dispatch_policy ??
@@ -135,7 +135,7 @@ export function buildElectricProxyTarget(options: {
135
135
  } else if (table === `entity_types`) {
136
136
  target.searchParams.set(
137
137
  `columns`,
138
- `"tenant_id","name","description","creation_schema","inbox_schemas","state_schemas","serve_endpoint","default_dispatch_policy","revision","created_at","updated_at"`
138
+ `"tenant_id","name","description","creation_schema","inbox_schemas","state_schemas","slash_commands","serve_endpoint","default_dispatch_policy","revision","created_at","updated_at"`
139
139
  )
140
140
  applyShapeWhere(
141
141
  target,