@electric-ax/agents-server 0.4.9 → 0.4.11

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
@@ -422,7 +422,7 @@ const ErrCodeForkInProgress = `FORK_IN_PROGRESS`;
422
422
  const ErrCodeForkWaitTimeout = `FORK_WAIT_TIMEOUT`;
423
423
  const ErrCodeEntityPersistFailed = `ENTITY_PERSIST_FAILED`;
424
424
  const ErrCodeSubscriptionNotFound = `SUBSCRIPTION_NOT_FOUND`;
425
- const ErrCodeCallbackNotFound = `CALLBACK_NOT_FOUND`;
425
+ const ErrCodeWakeCallbackNotFound = `WAKE_CALLBACK_NOT_FOUND`;
426
426
 
427
427
  //#endregion
428
428
  //#region src/tenant.ts
@@ -2503,7 +2503,7 @@ async function linkStreamToTargetSubscription(ctx, target, entity, subscriptionI
2503
2503
  }
2504
2504
  const webhookUrl = rewriteLoopbackWebhookUrl(target.url);
2505
2505
  if (!webhookUrl) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Webhook dispatch target must include a valid URL`, 400);
2506
- const forwardUrl = appendPathToUrl(ctx.publicUrl, `/_electric/webhook-forward/${encodeURIComponent(subscriptionId)}`);
2506
+ const forwardUrl = appendPathToUrl(ctx.publicUrl, `/_electric/subscription-webhooks/${encodeURIComponent(subscriptionId)}`);
2507
2507
  await ensureSubscriptionIncludesStream(ctx, subscriptionId, streamPath, {
2508
2508
  type: `webhook`,
2509
2509
  streams: [streamPath],
@@ -3588,7 +3588,7 @@ var EntityManager = class {
3588
3588
  };
3589
3589
  }
3590
3590
  /**
3591
- * Deliver a message to an entity's main stream, with optional input schema
3591
+ * Deliver a message to an entity's main stream, with optional inbox schema
3592
3592
  * validation.
3593
3593
  */
3594
3594
  async send(entityUrl, req, opts) {
@@ -3676,7 +3676,7 @@ var EntityManager = class {
3676
3676
  if (result.changed && this.entityBridgeManager) await this.entityBridgeManager.onEntityChanged(entityUrl);
3677
3677
  return updated;
3678
3678
  }
3679
- async removeTag(entityUrl, key, token) {
3679
+ async deleteTag(entityUrl, key, token) {
3680
3680
  const entity = await this.registry.getEntity(entityUrl);
3681
3681
  if (!entity) throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404);
3682
3682
  if (!this.isValidWriteToken(entity, token)) throw new ElectricAgentsError(ErrCodeUnauthorized, `Invalid write token`, 401);
@@ -3687,7 +3687,7 @@ var EntityManager = class {
3687
3687
  if (result.changed && this.entityBridgeManager) await this.entityBridgeManager.onEntityChanged(entityUrl);
3688
3688
  return updated;
3689
3689
  }
3690
- async registerEntitiesSource(tags) {
3690
+ async ensureEntitiesMembershipStream(tags) {
3691
3691
  if (!this.entityBridgeManager) throw new Error(`Entity bridge manager not configured`);
3692
3692
  return this.entityBridgeManager.register(this.validateTags(tags));
3693
3693
  }
@@ -4116,7 +4116,7 @@ var EntityManager = class {
4116
4116
  return null;
4117
4117
  }
4118
4118
  /**
4119
- * Add new input/output schema keys to an entity type directly in Postgres.
4119
+ * Add new inbox/state schema keys to an entity type directly in Postgres.
4120
4120
  */
4121
4121
  async amendSchemas(typeName, schemas) {
4122
4122
  if (typeName === `principal`) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Entity type "principal" is built in and cannot be amended`, 400);
@@ -4156,7 +4156,7 @@ var EntityManager = class {
4156
4156
  }
4157
4157
  /**
4158
4158
  * Enrich webhook payload with entity context.
4159
- * Called by ElectricAgentsServer during webhook forwarding to inject entity context.
4159
+ * Called by ElectricAgentsServer during subscription webhook dispatch to inject entity context.
4160
4160
  */
4161
4161
  async enrichPayload(payload, consumer) {
4162
4162
  const entity = await this.registry.getEntityByStream(consumer.primary_stream);
@@ -5415,7 +5415,7 @@ var WakeRegistry = class {
5415
5415
  try {
5416
5416
  for (const message of messages) {
5417
5417
  await this.applyShapeMessage(message);
5418
- if (!settled && `control` in message.headers && message.headers.control === `up-to-date`) {
5418
+ if (!settled && isControlMessage(message) && message.headers.control === `up-to-date`) {
5419
5419
  settled = true;
5420
5420
  resolve$1();
5421
5421
  }
@@ -6202,7 +6202,7 @@ function validateParsedBody(schema, parsed) {
6202
6202
  //#region src/routing/durable-streams-routing-adapter.ts
6203
6203
  function appendSearch(target, source) {
6204
6204
  source.searchParams.forEach((value, key) => {
6205
- if (key !== `service`) target.searchParams.append(key, value);
6205
+ target.searchParams.append(key, value);
6206
6206
  });
6207
6207
  return target;
6208
6208
  }
@@ -6522,7 +6522,7 @@ async function rewriteSubscriptionRequestBody(request, ctx, subscriptionId, rout
6522
6522
  let targetWebhookUrl = null;
6523
6523
  if (payload.webhook?.url !== void 0) {
6524
6524
  targetWebhookUrl = rewriteLoopbackWebhookUrl(payload.webhook.url) ?? null;
6525
- payload.webhook.url = appendPathToUrl(ctx.publicUrl, `/_electric/webhook-forward/${encodeURIComponent(subscriptionId)}`);
6525
+ payload.webhook.url = appendPathToUrl(ctx.publicUrl, `/_electric/subscription-webhooks/${encodeURIComponent(subscriptionId)}`);
6526
6526
  }
6527
6527
  rewriteSubscriptionBodyForBackend(payload, ctx.service, routingAdapter);
6528
6528
  return {
@@ -6645,20 +6645,6 @@ async function proxyPassThrough(request, ctx) {
6645
6645
  }
6646
6646
  }
6647
6647
 
6648
- //#endregion
6649
- //#region src/routing/cron-router.ts
6650
- const cronRegisterBodySchema = Type.Object({
6651
- expression: Type.String(),
6652
- timezone: Type.Optional(Type.String())
6653
- });
6654
- const cronRouter = Router({ base: `/_electric/cron` });
6655
- cronRouter.post(`/register`, withSchema(cronRegisterBodySchema), registerCron);
6656
- async function registerCron(request, ctx) {
6657
- const parsed = routeBody(request);
6658
- const streamPath = await ctx.entityManager.getOrCreateCronStream(parsed.expression, parsed.timezone);
6659
- return json({ streamUrl: streamPath });
6660
- }
6661
-
6662
6648
  //#endregion
6663
6649
  //#region src/routing/electric-proxy-router.ts
6664
6650
  const electricProxyRouter = Router({ base: `/_electric/electric` });
@@ -6692,7 +6678,7 @@ async function proxyElectric(request, ctx) {
6692
6678
 
6693
6679
  //#endregion
6694
6680
  //#region src/routing/entities-router.ts
6695
- const stringRecordSchema = Type.Record(Type.String(), Type.String());
6681
+ const stringRecordSchema$1 = Type.Record(Type.String(), Type.String());
6696
6682
  function writeTokenFromRequest(request) {
6697
6683
  const electricClaimToken = request.headers.get(`electric-claim-token`)?.trim();
6698
6684
  if (electricClaimToken) return electricClaimToken;
@@ -6709,7 +6695,7 @@ const wakeConditionSchema = Type.Union([Type.Literal(`runFinished`), Type.Object
6709
6695
  })]);
6710
6696
  const spawnBodySchema = Type.Object({
6711
6697
  args: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
6712
- tags: Type.Optional(stringRecordSchema),
6698
+ tags: Type.Optional(stringRecordSchema$1),
6713
6699
  parent: Type.Optional(Type.String()),
6714
6700
  dispatch_policy: Type.Optional(dispatchPolicySchema),
6715
6701
  initialMessage: Type.Optional(Type.Unknown()),
@@ -6801,10 +6787,8 @@ const eventSourceSubscriptionBodySchema = Type.Object({
6801
6787
  lifetime: Type.Optional(subscriptionLifetimeSchema),
6802
6788
  reason: Type.Optional(Type.String())
6803
6789
  });
6804
- const entitiesRegisterBodySchema = Type.Object({ tags: Type.Optional(stringRecordSchema) });
6805
6790
  const entitiesRouter = Router({ base: `/_electric/entities` });
6806
6791
  entitiesRouter.get(`/`, listEntities);
6807
- entitiesRouter.post(`/register`, withSchema(entitiesRegisterBodySchema), registerEntitiesSource);
6808
6792
  entitiesRouter.put(`/:type/:instanceId`, withSpawnableEntityType, withSchema(spawnBodySchema), spawnEntity);
6809
6793
  entitiesRouter.get(`/:type/:instanceId`, withExistingEntity, getEntity);
6810
6794
  entitiesRouter.head(`/:type/:instanceId`, withExistingEntity, headEntity);
@@ -6815,7 +6799,7 @@ entitiesRouter.patch(`/:type/:instanceId/inbox/:messageKey`, withExistingEntity,
6815
6799
  entitiesRouter.delete(`/:type/:instanceId/inbox/:messageKey`, withExistingEntity, deleteInboxMessage);
6816
6800
  entitiesRouter.post(`/:type/:instanceId/fork`, withExistingEntity, withSchema(forkBodySchema), forkEntity);
6817
6801
  entitiesRouter.post(`/:type/:instanceId/tags/:tagKey`, withExistingEntity, withSchema(setTagBodySchema), setTag);
6818
- entitiesRouter.delete(`/:type/:instanceId/tags/:tagKey`, withExistingEntity, removeTag);
6802
+ entitiesRouter.delete(`/:type/:instanceId/tags/:tagKey`, withExistingEntity, deleteTag);
6819
6803
  entitiesRouter.put(`/:type/:instanceId/schedules/:scheduleId`, withExistingEntity, withSchema(scheduleBodySchema), upsertSchedule);
6820
6804
  entitiesRouter.delete(`/:type/:instanceId/schedules/:scheduleId`, withExistingEntity, deleteSchedule);
6821
6805
  entitiesRouter.put(`/:type/:instanceId/event-source-subscriptions/:subscriptionId`, withExistingEntity, withSchema(eventSourceSubscriptionBodySchema), upsertEventSourceSubscription);
@@ -6883,11 +6867,6 @@ async function listEntities({ query }, ctx) {
6883
6867
  });
6884
6868
  return json(entities$1.map((entity) => toPublicEntity(entity)));
6885
6869
  }
6886
- async function registerEntitiesSource(request, ctx) {
6887
- const parsed = routeBody(request);
6888
- const result = await ctx.entityManager.registerEntitiesSource(parsed.tags ?? {});
6889
- return json(result);
6890
- }
6891
6870
  async function upsertSchedule(request, ctx) {
6892
6871
  const principalMutationError = rejectPrincipalEntityMutation(request, `scheduled`);
6893
6872
  if (principalMutationError) return principalMutationError;
@@ -6968,7 +6947,7 @@ async function deleteEventSourceSubscription(request, ctx) {
6968
6947
  return json(result);
6969
6948
  }
6970
6949
  async function setTag(request, ctx) {
6971
- const principalMutationError = rejectPrincipalEntityMutation(request, `tagged`);
6950
+ const principalMutationError = rejectPrincipalEntityMutation(request, `tag updated`);
6972
6951
  if (principalMutationError) return principalMutationError;
6973
6952
  const parsed = routeBody(request);
6974
6953
  const { entityUrl } = requireExistingEntityRoute(request);
@@ -6976,12 +6955,12 @@ async function setTag(request, ctx) {
6976
6955
  const updated = await ctx.entityManager.setTag(entityUrl, decodeURIComponent(request.params.tagKey), { value: parsed.value }, token);
6977
6956
  return json(toPublicEntity(updated));
6978
6957
  }
6979
- async function removeTag(request, ctx) {
6980
- const principalMutationError = rejectPrincipalEntityMutation(request, `untagged`);
6958
+ async function deleteTag(request, ctx) {
6959
+ const principalMutationError = rejectPrincipalEntityMutation(request, `tag deleted`);
6981
6960
  if (principalMutationError) return principalMutationError;
6982
6961
  const { entityUrl } = requireExistingEntityRoute(request);
6983
6962
  const token = writeTokenFromRequest(request);
6984
- const updated = await ctx.entityManager.removeTag(entityUrl, decodeURIComponent(request.params.tagKey), token);
6963
+ const updated = await ctx.entityManager.deleteTag(entityUrl, decodeURIComponent(request.params.tagKey), token);
6985
6964
  return json(toPublicEntity(updated));
6986
6965
  }
6987
6966
  async function forkEntity(request, ctx) {
@@ -7113,17 +7092,13 @@ const registerEntityTypeBodySchema = Type.Object({
7113
7092
  creation_schema: Type.Optional(jsonObjectSchema),
7114
7093
  inbox_schemas: Type.Optional(schemaMapSchema),
7115
7094
  state_schemas: Type.Optional(schemaMapSchema),
7116
- input_schemas: Type.Optional(schemaMapSchema),
7117
- output_schemas: Type.Optional(schemaMapSchema),
7118
7095
  serve_endpoint: Type.Optional(Type.String()),
7119
7096
  default_dispatch_policy: Type.Optional(dispatchPolicySchema)
7120
- });
7097
+ }, { additionalProperties: false });
7121
7098
  const amendEntityTypeSchemasBodySchema = Type.Object({
7122
- input_schemas: Type.Optional(schemaMapSchema),
7123
- output_schemas: Type.Optional(schemaMapSchema),
7124
7099
  inbox_schemas: Type.Optional(schemaMapSchema),
7125
7100
  state_schemas: Type.Optional(schemaMapSchema)
7126
- });
7101
+ }, { additionalProperties: false });
7127
7102
  const entityTypesRouter = Router({ base: `/_electric/entity-types` });
7128
7103
  entityTypesRouter.get(`/`, listEntityTypes);
7129
7104
  entityTypesRouter.post(`/`, withSchema(registerEntityTypeBodySchema), registerEntityType);
@@ -7163,8 +7138,8 @@ async function getEntityType(request, ctx) {
7163
7138
  async function amendSchemas(request, ctx) {
7164
7139
  const parsed = routeBody(request);
7165
7140
  const updated = await ctx.entityManager.amendSchemas(request.params.name, {
7166
- inbox_schemas: parsed.inbox_schemas ?? parsed.input_schemas,
7167
- state_schemas: parsed.state_schemas ?? parsed.output_schemas
7141
+ inbox_schemas: parsed.inbox_schemas,
7142
+ state_schemas: parsed.state_schemas
7168
7143
  });
7169
7144
  return json(toPublicEntityType(updated));
7170
7145
  }
@@ -7174,13 +7149,12 @@ async function deleteEntityType(request, ctx) {
7174
7149
  }
7175
7150
  function normalizeEntityTypeRequest(parsed) {
7176
7151
  const serveEndpoint = rewriteLoopbackWebhookUrl(parsed.serve_endpoint);
7177
- const compatibilityFields = parsed;
7178
7152
  return {
7179
7153
  name: parsed.name ?? ``,
7180
7154
  description: parsed.description ?? ``,
7181
7155
  creation_schema: parsed.creation_schema,
7182
- inbox_schemas: parsed.inbox_schemas ?? compatibilityFields.input_schemas,
7183
- state_schemas: parsed.state_schemas ?? compatibilityFields.output_schemas,
7156
+ inbox_schemas: parsed.inbox_schemas,
7157
+ state_schemas: parsed.state_schemas,
7184
7158
  serve_endpoint: serveEndpoint,
7185
7159
  default_dispatch_policy: parsed.default_dispatch_policy ?? (serveEndpoint ? { targets: [{
7186
7160
  type: `webhook`,
@@ -7191,8 +7165,6 @@ function normalizeEntityTypeRequest(parsed) {
7191
7165
  function toPublicEntityType(entityType) {
7192
7166
  return {
7193
7167
  ...entityType,
7194
- input_schemas: entityType.inbox_schemas,
7195
- output_schemas: entityType.state_schemas,
7196
7168
  revision: entityType.revision
7197
7169
  };
7198
7170
  }
@@ -7276,13 +7248,35 @@ function errorMapper(err, req) {
7276
7248
  function rejectIfShuttingDown(req, ctx) {
7277
7249
  if (!ctx.isShuttingDown()) return void 0;
7278
7250
  const path$1 = new URL(req.url).pathname;
7279
- if (!path$1.startsWith(`/_electric/webhook-forward/`)) return void 0;
7251
+ if (!path$1.startsWith(`/_electric/subscription-webhooks/`)) return void 0;
7280
7252
  return apiError(503, `SERVER_STOPPING`, `Server is shutting down`);
7281
7253
  }
7282
7254
  function getRequestSpan(req) {
7283
7255
  return carrier(req)[SPAN_KEY];
7284
7256
  }
7285
7257
 
7258
+ //#endregion
7259
+ //#region src/routing/observations-router.ts
7260
+ const stringRecordSchema = Type.Record(Type.String(), Type.String());
7261
+ const ensureEntitiesMembershipStreamBodySchema = Type.Object({ tags: Type.Optional(stringRecordSchema) });
7262
+ const ensureCronStreamBodySchema = Type.Object({
7263
+ expression: Type.String(),
7264
+ timezone: Type.Optional(Type.String())
7265
+ });
7266
+ const observationsRouter = Router({ base: `/_electric/observations` });
7267
+ observationsRouter.post(`/entities/ensure-stream`, withSchema(ensureEntitiesMembershipStreamBodySchema), ensureEntitiesMembershipStream);
7268
+ observationsRouter.post(`/cron/ensure-stream`, withSchema(ensureCronStreamBodySchema), ensureCronStream);
7269
+ async function ensureEntitiesMembershipStream(request, ctx) {
7270
+ const parsed = routeBody(request);
7271
+ const result = await ctx.entityManager.ensureEntitiesMembershipStream(parsed.tags ?? {});
7272
+ return json(result);
7273
+ }
7274
+ async function ensureCronStream(request, ctx) {
7275
+ const parsed = routeBody(request);
7276
+ const streamPath = await ctx.entityManager.getOrCreateCronStream(parsed.expression, parsed.timezone);
7277
+ return json({ streamUrl: streamPath });
7278
+ }
7279
+
7286
7280
  //#endregion
7287
7281
  //#region src/routing/tenant-stream-paths.ts
7288
7282
  function withLeadingSlash(path$1) {
@@ -7621,7 +7615,7 @@ async function notificationFromClaim(ctx, input) {
7621
7615
  wakeId: input.claim.wake_id,
7622
7616
  streamPath: primaryStream,
7623
7617
  streams: streams$1,
7624
- callback: appendPathToUrl(ctx.publicUrl, `/_electric/callback-forward/${encodeURIComponent(input.claim.wake_id)}`),
7618
+ callback: appendPathToUrl(ctx.publicUrl, `/_electric/wake-callbacks/${encodeURIComponent(input.claim.wake_id)}`),
7625
7619
  claimToken: input.claim.token,
7626
7620
  triggerEvent: `message_received`,
7627
7621
  entity: {
@@ -7656,7 +7650,7 @@ const wakeRegistrationBodySchema = Type.Object({
7656
7650
  includeResponse: Type.Optional(Type.Boolean()),
7657
7651
  manifestKey: Type.Optional(Type.String())
7658
7652
  });
7659
- const webhookForwardBodySchema = Type.Object({
7653
+ const subscriptionWebhookBodySchema = Type.Object({
7660
7654
  subscription_id: Type.Optional(Type.String()),
7661
7655
  wake_id: Type.Optional(Type.String()),
7662
7656
  generation: Type.Optional(Type.Number()),
@@ -7670,7 +7664,7 @@ const webhookForwardBodySchema = Type.Object({
7670
7664
  consumer_id: Type.Optional(Type.String()),
7671
7665
  callback: Type.Optional(Type.String())
7672
7666
  }, { additionalProperties: true });
7673
- const callbackForwardBodySchema = Type.Object({
7667
+ const wakeCallbackBodySchema = Type.Object({
7674
7668
  epoch: Type.Optional(Type.Number()),
7675
7669
  generation: Type.Optional(Type.Number()),
7676
7670
  wakeId: Type.Optional(Type.String()),
@@ -7683,13 +7677,13 @@ const internalRouter = Router({ base: `/_electric` });
7683
7677
  internalRouter.get(`/health`, () => json({ status: `ok` }));
7684
7678
  internalRouter.get(`/event-sources`, listEventSources);
7685
7679
  internalRouter.post(`/wake`, withSchema(wakeRegistrationBodySchema), registerWake);
7686
- internalRouter.post(`/webhook-forward/:subscriptionId`, webhookForward);
7687
- internalRouter.post(`/callback-forward/:consumerId`, callbackForward);
7680
+ internalRouter.post(`/subscription-webhooks/:subscriptionId`, subscriptionWebhook);
7681
+ internalRouter.post(`/wake-callbacks/:consumerId`, wakeCallback);
7688
7682
  internalRouter.all(`/runners`, runnersRouter.fetch);
7689
7683
  internalRouter.all(`/runners/*`, runnersRouter.fetch);
7690
7684
  internalRouter.all(`/entities/*`, entitiesRouter.fetch);
7691
7685
  internalRouter.all(`/entity-types/*`, entityTypesRouter.fetch);
7692
- internalRouter.all(`/cron/*`, cronRouter.fetch);
7686
+ internalRouter.all(`/observations/*`, observationsRouter.fetch);
7693
7687
  internalRouter.get(`/electric/*`, electricProxyRouter.fetch);
7694
7688
  internalRouter.all(`*`, () => status(404));
7695
7689
  function routeParam(request, name) {
@@ -7722,13 +7716,30 @@ function resolveWebhookSigner(ctx) {
7722
7716
  return ctx.webhookSigner ?? getDefaultWebhookSigner();
7723
7717
  }
7724
7718
  function durableStreamsWebhookJwksUrl(ctx) {
7725
- if (!ctx.durableStreamsRouting) return appendPathToUrl(ctx.durableStreamsUrl, `/__ds/jwks.json`);
7719
+ if (!ctx.durableStreamsRouting) return appendPathToBackendUrl(ctx.durableStreamsUrl, `/__ds/jwks.json`);
7726
7720
  return resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl).controlUrl({
7727
7721
  durableStreamsUrl: ctx.durableStreamsUrl,
7728
7722
  serviceId: ctx.service,
7729
7723
  requestUrl: appendPathToUrl(ctx.publicUrl, `/__ds/jwks.json`)
7730
7724
  }).toString();
7731
7725
  }
7726
+ function appendPathToBackendUrl(baseUrl, path$1) {
7727
+ const base = new URL(baseUrl);
7728
+ const pathUrl = new URL(path$1, `http://electric-agents.local`);
7729
+ const basePath = base.pathname === `/` ? `` : base.pathname.replace(/\/+$/, ``);
7730
+ const suffix = pathUrl.pathname.startsWith(`/`) ? pathUrl.pathname : `/${pathUrl.pathname}`;
7731
+ const target = new URL(base);
7732
+ target.pathname = `${basePath}${suffix}`;
7733
+ target.search = ``;
7734
+ target.hash = pathUrl.hash;
7735
+ base.searchParams.forEach((value, key) => {
7736
+ target.searchParams.append(key, value);
7737
+ });
7738
+ pathUrl.searchParams.forEach((value, key) => {
7739
+ target.searchParams.append(key, value);
7740
+ });
7741
+ return target.toString();
7742
+ }
7732
7743
  function durableStreamsJwksFetchClient(ctx) {
7733
7744
  return async (input, init) => {
7734
7745
  const headers = new Headers(init?.headers);
@@ -7792,10 +7803,10 @@ async function listEventSources(_request, ctx) {
7792
7803
  function isAgentVisibleEventSource(source) {
7793
7804
  return source.agentVisible === true && source.status === `active`;
7794
7805
  }
7795
- async function webhookForward(request, ctx) {
7806
+ async function subscriptionWebhook(request, ctx) {
7796
7807
  const subscriptionId = routeParam(request, `subscriptionId`);
7797
7808
  const rootSpan = getRequestSpan(request);
7798
- rootSpan?.updateName(`webhook-forward`);
7809
+ rootSpan?.updateName(`subscription-webhook`);
7799
7810
  rootSpan?.setAttribute(`electric_agents.webhook.subscription_id`, subscriptionId);
7800
7811
  const body = await readRequestBody(request);
7801
7812
  const signatureError = await verifyDurableStreamsWebhook(request, ctx, body);
@@ -7809,7 +7820,7 @@ async function webhookForward(request, ctx) {
7809
7820
  }
7810
7821
  });
7811
7822
  if (!targetWebhookUrl) return apiError(404, ErrCodeSubscriptionNotFound, `Unknown webhook subscription`);
7812
- const parsedBodyResult = validateOptionalJsonBody(webhookForwardBodySchema, body, request.headers.get(`content-type`));
7823
+ const parsedBodyResult = validateOptionalJsonBody(subscriptionWebhookBodySchema, body, request.headers.get(`content-type`));
7813
7824
  if (!parsedBodyResult.ok) return parsedBodyResult.response;
7814
7825
  let forwardBody = body;
7815
7826
  let runningEntityUrl = null;
@@ -7855,7 +7866,7 @@ async function webhookForward(request, ctx) {
7855
7866
  span.end();
7856
7867
  }
7857
7868
  }).catch((err) => {
7858
- serverLog.warn(`[webhook-forward] consumerCallbacks upsert failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
7869
+ serverLog.warn(`[subscription-webhook] consumerCallbacks upsert failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
7859
7870
  }) : void 0;
7860
7871
  const [entity, enriched] = await Promise.all([entityPromise, enrichPromise]);
7861
7872
  if (entity?.status === `stopped` || entity?.status === `paused`) {
@@ -7876,7 +7887,7 @@ async function webhookForward(request, ctx) {
7876
7887
  runningEntityUrl = entity.url;
7877
7888
  }
7878
7889
  if (consumerId && callbackUrl) {
7879
- const callback = appendPathToUrl(ctx.publicUrl, `/_electric/callback-forward/${encodeURIComponent(consumerId)}`);
7890
+ const callback = appendPathToUrl(ctx.publicUrl, `/_electric/wake-callbacks/${encodeURIComponent(consumerId)}`);
7880
7891
  enriched.callback = callback;
7881
7892
  if (newWebhook) {
7882
7893
  enriched.consumerId = newWebhook.wakeId;
@@ -7913,21 +7924,21 @@ async function webhookForward(request, ctx) {
7913
7924
  });
7914
7925
  } catch (err) {
7915
7926
  if (runningEntityUrl) await ctx.entityManager.registry.updateStatus(runningEntityUrl, `idle`);
7916
- return apiError(502, `WEBHOOK_FORWARD_FAILED`, err instanceof Error ? err.message : String(err));
7927
+ return apiError(502, `SUBSCRIPTION_WEBHOOK_FAILED`, err instanceof Error ? err.message : String(err));
7917
7928
  }
7918
7929
  const responseBytes = upstream.body ? new Uint8Array(await upstream.arrayBuffer()) : new Uint8Array();
7919
7930
  return responseFromUpstream(upstream, responseBytes);
7920
7931
  }
7921
- async function callbackForward(request, ctx) {
7932
+ async function wakeCallback(request, ctx) {
7922
7933
  const consumerId = routeParam(request, `consumerId`);
7923
7934
  const rows = await ctx.pgDb.select().from(consumerCallbacks).where(and(eq(consumerCallbacks.tenantId, ctx.service), eq(consumerCallbacks.consumerId, consumerId))).limit(1);
7924
7935
  const target = rows[0] ? {
7925
7936
  callbackUrl: rows[0].callbackUrl,
7926
7937
  primaryStream: rows[0].primaryStream
7927
7938
  } : void 0;
7928
- if (!target) return apiError(404, ErrCodeCallbackNotFound, `Unknown callback-forward consumer`);
7939
+ if (!target) return apiError(404, ErrCodeWakeCallbackNotFound, `Unknown wake-callback consumer`);
7929
7940
  const body = await readRequestBody(request);
7930
- const parsedBodyResult = validateOptionalJsonBody(callbackForwardBodySchema, body, request.headers.get(`content-type`));
7941
+ const parsedBodyResult = validateOptionalJsonBody(wakeCallbackBodySchema, body, request.headers.get(`content-type`));
7931
7942
  if (!parsedBodyResult.ok) return parsedBodyResult.response;
7932
7943
  const requestBody = parsedBodyResult.value;
7933
7944
  const isClaimRequest = requestBody?.wakeId !== void 0 || requestBody?.wake_id !== void 0;
@@ -7945,14 +7956,14 @@ async function callbackForward(request, ctx) {
7945
7956
  }
7946
7957
  return json(responseBody);
7947
7958
  }
7948
- const upstreamBody = encodeCallbackForwardBody(ctx.service, consumerId, requestBody, resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl));
7959
+ const upstreamBody = encodeWakeCallbackBody(ctx.service, consumerId, requestBody, resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl));
7949
7960
  let upstream;
7950
7961
  try {
7951
7962
  const subscriptionId = durableStreamsSubscriptionCallback(target.callbackUrl);
7952
7963
  if (subscriptionId) {
7953
7964
  const token = claimTokenFromRequest(request);
7954
7965
  if (!token) return apiError(401, `UNAUTHORIZED`, `Missing claim token`);
7955
- const upstreamPayload = encodeCallbackForwardPayload(consumerId, requestBody, (stream) => stream.replace(/^\/+/, ``));
7966
+ const upstreamPayload = encodeWakeCallbackPayload(consumerId, requestBody, (stream) => stream.replace(/^\/+/, ``));
7956
7967
  const result = await ctx.streamClient.ackSubscription(subscriptionId, token, upstreamPayload);
7957
7968
  upstream = json(result);
7958
7969
  } else upstream = await fetch(target.callbackUrl, {
@@ -7961,7 +7972,7 @@ async function callbackForward(request, ctx) {
7961
7972
  body: bodyFromBytes(upstreamBody)
7962
7973
  });
7963
7974
  } catch (err) {
7964
- return apiError(502, `CALLBACK_FORWARD_FAILED`, err instanceof Error ? err.message : String(err));
7975
+ return apiError(502, `WAKE_CALLBACK_FAILED`, err instanceof Error ? err.message : String(err));
7965
7976
  }
7966
7977
  let responseBytes = upstream.body ? new Uint8Array(await upstream.arrayBuffer()) : new Uint8Array();
7967
7978
  if (isClaimRequest && upstream.ok && target.primaryStream) {
@@ -7981,7 +7992,7 @@ async function callbackForward(request, ctx) {
7981
7992
  epoch
7982
7993
  });
7983
7994
  if (upstream.ok && isDoneRequest && target.primaryStream) {
7984
- serverLog.info(`[callback-forward] done received for stream=${target.primaryStream} consumer=${consumerId}`);
7995
+ serverLog.info(`[wake-callback] done received for stream=${target.primaryStream} consumer=${consumerId}`);
7985
7996
  const stillOwnsClaim = ctx.runtime.claimWriteTokens.owns(ctx.service, target.primaryStream, consumerId);
7986
7997
  const entity = await ctx.entityManager.registry.getEntityByStream(target.primaryStream);
7987
7998
  let entityCleared = false;
@@ -8003,13 +8014,13 @@ async function callbackForward(request, ctx) {
8003
8014
  if (entity && (entityCleared || stillOwnsClaim)) {
8004
8015
  await ctx.entityManager.registry.updateStatus(entity.url, entity.status === `stopping` ? `stopped` : `idle`);
8005
8016
  await ctx.entityBridgeManager.onEntityChanged(entity.url);
8006
- serverLog.info(`[callback-forward] status updated after done for ${entity.url}`);
8007
- } else if (!entity) serverLog.warn(`[callback-forward] done received but no entity found for stream=${target.primaryStream}`);
8017
+ serverLog.info(`[wake-callback] status updated after done for ${entity.url}`);
8018
+ } else if (!entity) serverLog.warn(`[wake-callback] done received but no entity found for stream=${target.primaryStream}`);
8008
8019
  if (stillOwnsClaim) ctx.runtime.claimWriteTokens.clearStream(ctx.service, target.primaryStream);
8009
- else if (entity) serverLog.info(`[callback-forward] done arrived after in-memory token evicted (stream=${target.primaryStream} consumer=${consumerId})`);
8010
- } else if (requestBody?.done === true) serverLog.warn(`[callback-forward] done received but skipped: upstream.ok=${upstream.ok} primaryStream=${target.primaryStream ?? `null`} consumer=${consumerId}`);
8020
+ else if (entity) serverLog.info(`[wake-callback] done arrived after in-memory token evicted (stream=${target.primaryStream} consumer=${consumerId})`);
8021
+ } else if (requestBody?.done === true) serverLog.warn(`[wake-callback] done received but skipped: upstream.ok=${upstream.ok} primaryStream=${target.primaryStream ?? `null`} consumer=${consumerId}`);
8011
8022
  } catch (err) {
8012
- serverLog.error(`[callback-forward] error processing done for consumer=${consumerId}: ${err instanceof Error ? err.message : String(err)}`);
8023
+ serverLog.error(`[wake-callback] error processing done for consumer=${consumerId}: ${err instanceof Error ? err.message : String(err)}`);
8013
8024
  }
8014
8025
  return responseFromUpstream(upstream, responseBytes);
8015
8026
  }
@@ -8018,11 +8029,11 @@ async function mintClaimWriteToken(ctx, streamPath, consumerId) {
8018
8029
  if (!entity) return void 0;
8019
8030
  return ctx.runtime.claimWriteTokens.mint(ctx.service, streamPath, consumerId);
8020
8031
  }
8021
- function encodeCallbackForwardBody(service, consumerId, body, routingAdapter) {
8022
- const payload = encodeCallbackForwardPayload(consumerId, body, (stream) => routingAdapter.toBackendStreamPath(service, stream));
8032
+ function encodeWakeCallbackBody(service, consumerId, body, routingAdapter) {
8033
+ const payload = encodeWakeCallbackPayload(consumerId, body, (stream) => routingAdapter.toBackendStreamPath(service, stream));
8023
8034
  return new TextEncoder().encode(JSON.stringify(payload));
8024
8035
  }
8025
- function encodeCallbackForwardPayload(consumerId, body, mapStream) {
8036
+ function encodeWakeCallbackPayload(consumerId, body, mapStream) {
8026
8037
  if (!body) return {};
8027
8038
  const generation = body.generation ?? body.epoch;
8028
8039
  const wakeId = body.wake_id ?? body.wakeId ?? consumerId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electric-ax/agents-server",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
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.4"
57
+ "@electric-ax/agents-runtime": "0.3.5"
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.8",
69
- "@electric-ax/agents-server-conformance-tests": "0.1.7",
70
- "@electric-ax/agents-server-ui": "0.4.9"
68
+ "@electric-ax/agents": "0.4.9",
69
+ "@electric-ax/agents-server-ui": "0.4.11",
70
+ "@electric-ax/agents-server-conformance-tests": "0.1.8"
71
71
  },
72
72
  "files": [
73
73
  "dist",
@@ -1 +1 @@
1
- export { DEFAULT_OUTPUT_SCHEMAS } from '@electric-ax/agents-runtime'
1
+ export { DEFAULT_STATE_SCHEMAS } from '@electric-ax/agents-runtime'
@@ -457,4 +457,4 @@ export const ErrCodeForkWaitTimeout = `FORK_WAIT_TIMEOUT`
457
457
  export const ErrCodeEntityPersistFailed = `ENTITY_PERSIST_FAILED`
458
458
  export const ErrCodeAgentUiNotFound = `AGENT_UI_NOT_FOUND`
459
459
  export const ErrCodeSubscriptionNotFound = `SUBSCRIPTION_NOT_FOUND`
460
- export const ErrCodeCallbackNotFound = `CALLBACK_NOT_FOUND`
460
+ export const ErrCodeWakeCallbackNotFound = `WAKE_CALLBACK_NOT_FOUND`
@@ -1675,7 +1675,7 @@ export class EntityManager {
1675
1675
  // ==========================================================================
1676
1676
 
1677
1677
  /**
1678
- * Deliver a message to an entity's main stream, with optional input schema
1678
+ * Deliver a message to an entity's main stream, with optional inbox schema
1679
1679
  * validation.
1680
1680
  */
1681
1681
  async send(
@@ -1888,7 +1888,7 @@ export class EntityManager {
1888
1888
  return updated
1889
1889
  }
1890
1890
 
1891
- async removeTag(
1891
+ async deleteTag(
1892
1892
  entityUrl: string,
1893
1893
  key: string,
1894
1894
  token: string
@@ -1930,7 +1930,7 @@ export class EntityManager {
1930
1930
  return updated
1931
1931
  }
1932
1932
 
1933
- async registerEntitiesSource(tags: Record<string, string>): Promise<{
1933
+ async ensureEntitiesMembershipStream(tags: Record<string, string>): Promise<{
1934
1934
  sourceRef: string
1935
1935
  streamUrl: string
1936
1936
  }> {
@@ -2742,7 +2742,7 @@ export class EntityManager {
2742
2742
  // ==========================================================================
2743
2743
 
2744
2744
  /**
2745
- * Add new input/output schema keys to an entity type directly in Postgres.
2745
+ * Add new inbox/state schema keys to an entity type directly in Postgres.
2746
2746
  */
2747
2747
  async amendSchemas(
2748
2748
  typeName: string,
@@ -2831,7 +2831,7 @@ export class EntityManager {
2831
2831
 
2832
2832
  /**
2833
2833
  * Enrich webhook payload with entity context.
2834
- * Called by ElectricAgentsServer during webhook forwarding to inject entity context.
2834
+ * Called by ElectricAgentsServer during subscription webhook dispatch to inject entity context.
2835
2835
  */
2836
2836
  async enrichPayload(
2837
2837
  payload: Record<string, unknown>,
@@ -326,7 +326,7 @@ async function linkStreamToTargetSubscription(
326
326
  }
327
327
  const forwardUrl = appendPathToUrl(
328
328
  ctx.publicUrl,
329
- `/_electric/webhook-forward/${encodeURIComponent(subscriptionId)}`
329
+ `/_electric/subscription-webhooks/${encodeURIComponent(subscriptionId)}`
330
330
  )
331
331
  await ensureSubscriptionIncludesStream(
332
332
  ctx,
@@ -332,7 +332,7 @@ async function rewriteSubscriptionRequestBody(
332
332
  targetWebhookUrl = rewriteLoopbackWebhookUrl(payload.webhook.url) ?? null
333
333
  payload.webhook.url = appendPathToUrl(
334
334
  ctx.publicUrl,
335
- `/_electric/webhook-forward/${encodeURIComponent(subscriptionId)}`
335
+ `/_electric/subscription-webhooks/${encodeURIComponent(subscriptionId)}`
336
336
  )
337
337
  }
338
338
 
@@ -13,9 +13,7 @@ export interface DurableStreamsRoutingAdapter {
13
13
 
14
14
  function appendSearch(target: URL, source: URL): URL {
15
15
  source.searchParams.forEach((value, key) => {
16
- if (key !== `service`) {
17
- target.searchParams.append(key, value)
18
- }
16
+ target.searchParams.append(key, value)
19
17
  })
20
18
  return target
21
19
  }