@electric-ax/agents-server 0.4.6 → 0.4.9

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.
@@ -15,6 +15,7 @@ import {
15
15
  assertEntityStatus,
16
16
  assertRunnerAdminStatus,
17
17
  assertRunnerKind,
18
+ isTerminalEntityStatus,
18
19
  } from './electric-agents-types.js'
19
20
  import { DEFAULT_TENANT_ID } from './tenant.js'
20
21
  import type { DrizzleDB } from './db/index.js'
@@ -726,10 +727,13 @@ export class PostgresRegistry {
726
727
  }
727
728
 
728
729
  async updateStatus(entityUrl: string, status: EntityStatus): Promise<void> {
729
- const whereClause =
730
- status === `stopped`
731
- ? this.entityWhere(entityUrl)
732
- : and(this.entityWhere(entityUrl), ne(entities.status, `stopped`))
730
+ const whereClause = isTerminalEntityStatus(status)
731
+ ? this.entityWhere(entityUrl)
732
+ : and(
733
+ this.entityWhere(entityUrl),
734
+ ne(entities.status, `stopped`),
735
+ ne(entities.status, `killed`)
736
+ )
733
737
 
734
738
  await this.db
735
739
  .update(entities)
@@ -740,21 +744,41 @@ export class PostgresRegistry {
740
744
  async updateStatusWithTxid(
741
745
  entityUrl: string,
742
746
  status: EntityStatus
743
- ): Promise<number> {
747
+ ): Promise<number | null> {
744
748
  return await this.db.transaction(async (tx) => {
745
- const whereClause =
746
- status === `stopped`
747
- ? this.entityWhere(entityUrl)
748
- : and(this.entityWhere(entityUrl), ne(entities.status, `stopped`))
749
-
750
- await tx
749
+ const rows = await tx
751
750
  .update(entities)
752
751
  .set({ status, updatedAt: Date.now() })
753
- .where(whereClause)
754
- const result = await tx.execute(
755
- sql`SELECT pg_current_xact_id()::xid::text AS txid`
756
- )
757
- return parseInt((result[0] as { txid: string }).txid)
752
+ .where(
753
+ and(
754
+ this.entityWhere(entityUrl),
755
+ ne(entities.status, `stopped`),
756
+ ne(entities.status, `killed`)
757
+ )
758
+ )
759
+ .returning({
760
+ txid: sql<string>`pg_current_xact_id()::xid::text`,
761
+ })
762
+ return rows[0] ? parseInt(rows[0].txid) : null
763
+ })
764
+ }
765
+
766
+ async touchEntityWithTxid(entityUrl: string): Promise<number | null> {
767
+ return await this.db.transaction(async (tx) => {
768
+ const rows = await tx
769
+ .update(entities)
770
+ .set({ updatedAt: Date.now() })
771
+ .where(
772
+ and(
773
+ eq(entities.url, entityUrl),
774
+ ne(entities.status, `stopped`),
775
+ ne(entities.status, `killed`)
776
+ )
777
+ )
778
+ .returning({
779
+ txid: sql<string>`pg_current_xact_id()::xid::text`,
780
+ })
781
+ return rows[0] ? parseInt(rows[0].txid) : null
758
782
  })
759
783
  }
760
784
 
package/src/index.ts CHANGED
@@ -15,6 +15,14 @@ export type {
15
15
  SubscriptionResponse,
16
16
  SubscriptionStreamInfo,
17
17
  } from './stream-client.js'
18
+ export {
19
+ assertEntitySignal,
20
+ assertEntityStatus,
21
+ expectedSignalStatus,
22
+ isTerminalEntityStatus,
23
+ rejectsNormalWrites,
24
+ toPublicEntity,
25
+ } from './electric-agents-types.js'
18
26
  export type {
19
27
  AuthenticateRequest,
20
28
  ConsumerClaim,
@@ -32,11 +40,31 @@ export type {
32
40
  RunnerLiveness,
33
41
  SourceStreamOffset,
34
42
  WakeNotificationRow,
43
+ ElectricAgentsEntity,
44
+ ElectricAgentsEntityRow,
45
+ ElectricAgentsEntityType,
46
+ EntityStatus,
47
+ EntitySignal,
48
+ PublicElectricAgentsEntity,
49
+ EntityListFilter,
50
+ RegisterEntityTypeRequest,
51
+ SendRequest,
52
+ SignalRequest,
53
+ SignalResponse,
54
+ TypedSpawnRequest,
35
55
  } from './electric-agents-types.js'
56
+ export type {
57
+ EventSourceBucket,
58
+ EventSourceContract,
59
+ EventSourceFilter,
60
+ EventSourceSubscription,
61
+ EventSourceSubscriptionInput,
62
+ SubscriptionLifetime,
63
+ } from '@electric-ax/agents-runtime'
36
64
  export type { Principal, PrincipalKind } from './principal.js'
37
65
  export { globalRouter } from './routing/global-router.js'
38
66
  export type { GlobalRoutes } from './routing/global-router.js'
39
- export type { TenantContext } from './routing/context.js'
67
+ export type { EventSourceCatalog, TenantContext } from './routing/context.js'
40
68
  export {
41
69
  streamRootDurableStreamsRoutingAdapter,
42
70
  pathPrefixedSingleTenantDurableStreamsRoutingAdapter,
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  getCronStreamPathFromSpec,
3
+ getWebhookStreamPath,
3
4
  getSharedStateStreamPath,
4
5
  resolveCronScheduleSpec,
5
6
  } from '@electric-ax/agents-runtime'
@@ -56,6 +57,16 @@ export function extractManifestSourceUrl(
56
57
  : undefined
57
58
  }
58
59
 
60
+ if (manifest.sourceType === `webhook`) {
61
+ if (typeof config?.streamUrl === `string`) return config.streamUrl
62
+ if (typeof config?.endpointKey === `string`) {
63
+ return getWebhookStreamPath(
64
+ config.endpointKey,
65
+ typeof config.bucket === `string` ? config.bucket : undefined
66
+ )
67
+ }
68
+ }
69
+
59
70
  return undefined
60
71
  }
61
72
 
@@ -1,5 +1,8 @@
1
1
  import type { Agent } from 'undici'
2
- import type { WebhookSignatureVerifierConfig } from '@electric-ax/agents-runtime'
2
+ import type {
3
+ EventSourceContract,
4
+ WebhookSignatureVerifierConfig,
5
+ } from '@electric-ax/agents-runtime'
3
6
  import type { DrizzleDB } from '../db/index.js'
4
7
  import type { EntityBridgeCoordinator } from '../entity-bridge-manager.js'
5
8
  import type { EntityManager } from '../entity-manager.js'
@@ -10,6 +13,18 @@ import type { Principal } from '../principal.js'
10
13
  import type { DurableStreamsBearerProvider } from '../stream-client.js'
11
14
  import type { WebhookSigner } from '../webhook-signing.js'
12
15
 
16
+ export interface EventSourceCatalog {
17
+ listEventSources: () =>
18
+ | Array<EventSourceContract>
19
+ | Promise<Array<EventSourceContract>>
20
+ getEventSource: (
21
+ sourceKey: string
22
+ ) =>
23
+ | EventSourceContract
24
+ | undefined
25
+ | Promise<EventSourceContract | undefined>
26
+ }
27
+
13
28
  /**
14
29
  * Per-request tenant context passed through every router and handler.
15
30
  *
@@ -38,5 +53,7 @@ export interface TenantContext {
38
53
  streamClient: StreamClient
39
54
  runtime: ElectricAgentsTenantRuntime
40
55
  entityBridgeManager: EntityBridgeCoordinator
56
+ eventSources?: EventSourceCatalog
57
+ ensureEventSourceWakeSource?: (sourceUrl: string) => Promise<void> | void
41
58
  isShuttingDown: () => boolean
42
59
  }
@@ -217,6 +217,12 @@ export async function assertDispatchPolicyAllowed(
217
217
  }
218
218
  }
219
219
 
220
+ export function shouldLinkDispatchBeforeInitialMessage(
221
+ policy: DispatchPolicy | undefined
222
+ ): boolean {
223
+ return policy?.targets[0] !== undefined
224
+ }
225
+
220
226
  export async function linkEntityDispatchSubscription(
221
227
  ctx: TenantContext,
222
228
  entity: ElectricAgentsEntity
@@ -3,6 +3,10 @@
3
3
  */
4
4
 
5
5
  import { Type, type Static } from '@sinclair/typebox'
6
+ import {
7
+ buildEventSourceManifestEntry,
8
+ resolveEventSourceSubscription,
9
+ } from '@electric-ax/agents-runtime'
6
10
  import { Router, json, status } from 'itty-router'
7
11
  import { apiError } from '../electric-agents-http.js'
8
12
  import { parsePrincipalKey, principalUrl } from '../principal.js'
@@ -18,6 +22,7 @@ import {
18
22
  backfillEntityDispatchPolicy,
19
23
  linkEntityDispatchSubscription,
20
24
  resolveEffectiveDispatchPolicyForSpawn,
25
+ shouldLinkDispatchBeforeInitialMessage,
21
26
  unlinkEntityDispatchSubscription,
22
27
  } from './dispatch-policy.js'
23
28
  import { routeBody, withSchema } from './schema.js'
@@ -25,6 +30,7 @@ import type { ElectricAgentsEntity } from '../electric-agents-types.js'
25
30
  import type { JsonRouteRequest } from './schema.js'
26
31
  import type { RouterType } from 'itty-router'
27
32
  import type { TenantContext } from './context.js'
33
+ import type { EventSourceSubscriptionInput } from '@electric-ax/agents-runtime'
28
34
 
29
35
  interface AgentsRouteRequest extends JsonRouteRequest {
30
36
  entityRoute?: ExistingEntityRoute
@@ -83,6 +89,7 @@ const spawnBodySchema = Type.Object({
83
89
  debounceMs: Type.Optional(Type.Number()),
84
90
  timeoutMs: Type.Optional(Type.Number()),
85
91
  includeResponse: Type.Optional(Type.Boolean()),
92
+ manifestKey: Type.Optional(Type.String()),
86
93
  })
87
94
  ),
88
95
  })
@@ -133,6 +140,22 @@ const setTagBodySchema = Type.Object({
133
140
  value: Type.String(),
134
141
  })
135
142
 
143
+ const entitySignalSchema = Type.Union([
144
+ Type.Literal(`SIGINT`),
145
+ Type.Literal(`SIGHUP`),
146
+ Type.Literal(`SIGTERM`),
147
+ Type.Literal(`SIGKILL`),
148
+ Type.Literal(`SIGSTOP`),
149
+ Type.Literal(`SIGCONT`),
150
+ Type.Literal(`SIGUSR`),
151
+ ])
152
+
153
+ const signalBodySchema = Type.Object({
154
+ signal: entitySignalSchema,
155
+ reason: Type.Optional(Type.String()),
156
+ payload: Type.Optional(Type.Unknown()),
157
+ })
158
+
136
159
  const scheduleBodySchema = Type.Union([
137
160
  Type.Object({
138
161
  scheduleType: Type.Literal(`cron`),
@@ -152,6 +175,24 @@ const scheduleBodySchema = Type.Union([
152
175
  }),
153
176
  ])
154
177
 
178
+ const subscriptionLifetimeSchema = Type.Union([
179
+ Type.Object({ kind: Type.Literal(`until_entity_stopped`) }),
180
+ Type.Object({
181
+ kind: Type.Literal(`expires_at`),
182
+ at: Type.String(),
183
+ }),
184
+ Type.Object({ kind: Type.Literal(`manual`) }),
185
+ ])
186
+
187
+ const eventSourceSubscriptionBodySchema = Type.Object({
188
+ sourceKey: Type.String(),
189
+ bucketKey: Type.Optional(Type.String()),
190
+ params: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
191
+ filterKey: Type.Optional(Type.String()),
192
+ lifetime: Type.Optional(subscriptionLifetimeSchema),
193
+ reason: Type.Optional(Type.String()),
194
+ })
195
+
155
196
  const entitiesRegisterBodySchema = Type.Object({
156
197
  tags: Type.Optional(stringRecordSchema),
157
198
  })
@@ -161,7 +202,11 @@ type SendBody = Static<typeof sendBodySchema>
161
202
  type InboxMessageBody = Static<typeof inboxMessageBodySchema>
162
203
  type ForkBody = Static<typeof forkBodySchema>
163
204
  type SetTagBody = Static<typeof setTagBodySchema>
205
+ type SignalBody = Static<typeof signalBodySchema>
164
206
  type ScheduleBody = Static<typeof scheduleBodySchema>
207
+ type EventSourceSubscriptionBody = Static<
208
+ typeof eventSourceSubscriptionBodySchema
209
+ >
165
210
  type EntitiesRegisterBody = Static<typeof entitiesRegisterBodySchema>
166
211
 
167
212
  export const entitiesRouter: EntitiesRoutes = Router<
@@ -187,6 +232,12 @@ entitiesRouter.put(
187
232
  entitiesRouter.get(`/:type/:instanceId`, withExistingEntity, getEntity)
188
233
  entitiesRouter.head(`/:type/:instanceId`, withExistingEntity, headEntity)
189
234
  entitiesRouter.delete(`/:type/:instanceId`, withExistingEntity, killEntity)
235
+ entitiesRouter.post(
236
+ `/:type/:instanceId/signal`,
237
+ withExistingEntity,
238
+ withSchema(signalBodySchema),
239
+ signalEntity
240
+ )
190
241
  entitiesRouter.post(
191
242
  `/:type/:instanceId/send`,
192
243
  withExistingEntity,
@@ -232,6 +283,17 @@ entitiesRouter.delete(
232
283
  withExistingEntity,
233
284
  deleteSchedule
234
285
  )
286
+ entitiesRouter.put(
287
+ `/:type/:instanceId/event-source-subscriptions/:subscriptionId`,
288
+ withExistingEntity,
289
+ withSchema(eventSourceSubscriptionBodySchema),
290
+ upsertEventSourceSubscription
291
+ )
292
+ entitiesRouter.delete(
293
+ `/:type/:instanceId/event-source-subscriptions/:subscriptionId`,
294
+ withExistingEntity,
295
+ deleteEventSourceSubscription
296
+ )
235
297
 
236
298
  function entityUrlFromSegments(
237
299
  type: string,
@@ -443,6 +505,98 @@ async function deleteSchedule(
443
505
  return json(result)
444
506
  }
445
507
 
508
+ async function upsertEventSourceSubscription(
509
+ request: AgentsRouteRequest,
510
+ ctx: TenantContext
511
+ ): Promise<Response> {
512
+ const principalMutationError = rejectPrincipalEntityMutation(
513
+ request,
514
+ `subscribed to event sources`
515
+ )
516
+ if (principalMutationError) return principalMutationError
517
+
518
+ const catalog = ctx.eventSources
519
+ if (!catalog) {
520
+ return apiError(
521
+ 404,
522
+ ErrCodeNotFound,
523
+ `No event source catalog is configured`
524
+ )
525
+ }
526
+
527
+ const { entityUrl } = requireExistingEntityRoute(request)
528
+ const parsed = routeBody<EventSourceSubscriptionBody>(request)
529
+ const source = await catalog.getEventSource(parsed.sourceKey)
530
+ if (!source) {
531
+ return apiError(
532
+ 404,
533
+ ErrCodeNotFound,
534
+ `Event source "${parsed.sourceKey}" not found`
535
+ )
536
+ }
537
+
538
+ if (parsed.lifetime?.kind === `expires_at`) {
539
+ const expiresAt = new Date(parsed.lifetime.at)
540
+ if (Number.isNaN(expiresAt.getTime())) {
541
+ return apiError(
542
+ 400,
543
+ ErrCodeInvalidRequest,
544
+ `Invalid expires_at lifetime timestamp`
545
+ )
546
+ }
547
+ }
548
+
549
+ let resolved: ReturnType<typeof resolveEventSourceSubscription>
550
+ try {
551
+ resolved = resolveEventSourceSubscription({
552
+ contract: source,
553
+ entityUrl,
554
+ request: {
555
+ ...(parsed as EventSourceSubscriptionInput),
556
+ id: decodeURIComponent(request.params.subscriptionId),
557
+ },
558
+ createdBy: `tool`,
559
+ })
560
+ } catch (error) {
561
+ return apiError(
562
+ 400,
563
+ ErrCodeInvalidRequest,
564
+ error instanceof Error ? error.message : String(error)
565
+ )
566
+ }
567
+
568
+ await ctx.ensureEventSourceWakeSource?.(resolved.subscription.sourceUrl)
569
+
570
+ const result = await ctx.entityManager.upsertEventSourceSubscription(
571
+ entityUrl,
572
+ {
573
+ subscription: resolved.subscription,
574
+ manifest: buildEventSourceManifestEntry(resolved),
575
+ }
576
+ )
577
+ return json(result)
578
+ }
579
+
580
+ async function deleteEventSourceSubscription(
581
+ request: AgentsRouteRequest,
582
+ ctx: TenantContext
583
+ ): Promise<Response> {
584
+ const principalMutationError = rejectPrincipalEntityMutation(
585
+ request,
586
+ `unsubscribed from event sources`
587
+ )
588
+ if (principalMutationError) return principalMutationError
589
+
590
+ const { entityUrl } = requireExistingEntityRoute(request)
591
+ const result = await ctx.entityManager.deleteEventSourceSubscription(
592
+ entityUrl,
593
+ {
594
+ id: decodeURIComponent(request.params.subscriptionId),
595
+ }
596
+ )
597
+ return json(result)
598
+ }
599
+
446
600
  async function setTag(
447
601
  request: AgentsRouteRequest,
448
602
  ctx: TenantContext
@@ -614,13 +768,21 @@ async function spawnEntity(
614
768
  wake: parsed.wake,
615
769
  created_by: principal.url,
616
770
  })
617
- await linkEntityDispatchSubscription(ctx, entity)
771
+ const linkBeforeInitialMessage =
772
+ parsed.initialMessage !== undefined &&
773
+ shouldLinkDispatchBeforeInitialMessage(dispatchPolicy)
774
+ if (linkBeforeInitialMessage) {
775
+ await linkEntityDispatchSubscription(ctx, entity)
776
+ }
618
777
  if (parsed.initialMessage !== undefined) {
619
778
  await ctx.entityManager.send(entity.url, {
620
779
  from: principal.url,
621
780
  payload: parsed.initialMessage,
622
781
  })
623
782
  }
783
+ if (!linkBeforeInitialMessage) {
784
+ await linkEntityDispatchSubscription(ctx, entity)
785
+ }
624
786
 
625
787
  return json(
626
788
  { ...toPublicEntity(entity), txid: entity.txid },
@@ -655,3 +817,27 @@ async function killEntity(
655
817
  ctx.runtime.claimWriteTokens.clearStream(ctx.service, entity.streams.main)
656
818
  return json(result)
657
819
  }
820
+
821
+ async function signalEntity(
822
+ request: AgentsRouteRequest,
823
+ ctx: TenantContext
824
+ ): Promise<Response> {
825
+ const principalMutationError = rejectPrincipalEntityMutation(
826
+ request,
827
+ `signaled`
828
+ )
829
+ if (principalMutationError) return principalMutationError
830
+
831
+ const parsed = routeBody<SignalBody>(request)
832
+ const { entityUrl, entity } = requireExistingEntityRoute(request)
833
+ const result = await ctx.entityManager.signal(entityUrl, {
834
+ signal: parsed.signal,
835
+ reason: parsed.reason,
836
+ payload: parsed.payload,
837
+ })
838
+ if (result.new_state === `stopped` || result.new_state === `killed`) {
839
+ await unlinkEntityDispatchSubscription(ctx, entity)
840
+ ctx.runtime.claimWriteTokens.clearStream(ctx.service, entity.streams.main)
841
+ }
842
+ return json(result)
843
+ }
@@ -36,7 +36,10 @@ import { runnersRouter } from './runners-router.js'
36
36
  import { routeBody, validateOptionalJsonBody, withSchema } from './schema.js'
37
37
  import { withLeadingSlash } from './tenant-stream-paths.js'
38
38
  import type { IRequest, RouterType } from 'itty-router'
39
- import type { WebhookSignatureVerifierConfig } from '@electric-ax/agents-runtime'
39
+ import type {
40
+ EventSourceContract,
41
+ WebhookSignatureVerifierConfig,
42
+ } from '@electric-ax/agents-runtime'
40
43
  import type { TenantContext } from './context.js'
41
44
  import type { DurableStreamsRoutingAdapter } from './durable-streams-routing-adapter.js'
42
45
  import type { WebhookSigner } from '../webhook-signing.js'
@@ -117,6 +120,7 @@ export const internalRouter: InternalRoutes = Router<
117
120
  })
118
121
 
119
122
  internalRouter.get(`/health`, () => json({ status: `ok` }))
123
+ internalRouter.get(`/event-sources`, listEventSources)
120
124
  internalRouter.post(
121
125
  `/wake`,
122
126
  withSchema(wakeRegistrationBodySchema),
@@ -335,6 +339,20 @@ async function registerWake(
335
339
  return status(204)
336
340
  }
337
341
 
342
+ async function listEventSources(
343
+ _request: IRequest,
344
+ ctx: TenantContext
345
+ ): Promise<Response> {
346
+ const eventSources = ctx.eventSources
347
+ ? await ctx.eventSources.listEventSources()
348
+ : []
349
+ return json({ eventSources: eventSources.filter(isAgentVisibleEventSource) })
350
+ }
351
+
352
+ function isAgentVisibleEventSource(source: EventSourceContract): boolean {
353
+ return source.agentVisible === true && source.status === `active`
354
+ }
355
+
338
356
  async function webhookForward(
339
357
  request: IRequest,
340
358
  ctx: TenantContext
@@ -479,7 +497,7 @@ async function webhookForward(
479
497
  enrichPromise,
480
498
  ])
481
499
 
482
- if (entity?.status === `stopped`) {
500
+ if (entity?.status === `stopped` || entity?.status === `paused`) {
483
501
  if (upsertPromise) await upsertPromise
484
502
  return json({ done: true })
485
503
  }
@@ -764,10 +782,13 @@ async function callbackForward(
764
782
  // the token is intact even though entityDispatchState may diverge.
765
783
  // If both are false, a newer wake owns the entity — leave status as-is.
766
784
  if (entity && (entityCleared || stillOwnsClaim)) {
767
- await ctx.entityManager.registry.updateStatus(entity.url, `idle`)
785
+ await ctx.entityManager.registry.updateStatus(
786
+ entity.url,
787
+ entity.status === `stopping` ? `stopped` : `idle`
788
+ )
768
789
  await ctx.entityBridgeManager.onEntityChanged(entity.url)
769
790
  serverLog.info(
770
- `[callback-forward] status updated to idle for ${entity.url}`
791
+ `[callback-forward] status updated after done for ${entity.url}`
771
792
  )
772
793
  } else if (!entity) {
773
794
  serverLog.warn(
@@ -575,7 +575,7 @@ async function notificationFromClaim(
575
575
  404
576
576
  )
577
577
  }
578
- if (entity.status === `stopped`) {
578
+ if (entity.status === `stopped` || entity.status === `paused`) {
579
579
  await ctx.streamClient.releaseSubscription(
580
580
  input.subscriptionId,
581
581
  input.claim.token,
package/src/runtime.ts CHANGED
@@ -533,7 +533,11 @@ export class ElectricAgentsTenantRuntime {
533
533
  return
534
534
  }
535
535
 
536
- await this.manager.registry.updateStatus(entityUrl, `idle`)
536
+ const entity = await this.manager.registry.getEntity(entityUrl)
537
+ await this.manager.registry.updateStatus(
538
+ entityUrl,
539
+ entity?.status === `stopping` ? `stopped` : `idle`
540
+ )
537
541
  await this.entityBridgeManager.onEntityChanged(entityUrl)
538
542
  }
539
543
  }
package/src/server.ts CHANGED
@@ -34,6 +34,7 @@ import type { Principal } from './principal.js'
34
34
  import type { EntityBridgeCoordinator } from './entity-bridge-manager.js'
35
35
  import type { DurableStreamsRoutingAdapter } from './routing/durable-streams-routing-adapter.js'
36
36
  import type { OssServerContext } from './routing/oss-server-router.js'
37
+ import type { EventSourceCatalog } from './routing/context.js'
37
38
  import type { StartedStandaloneAgentsRuntime } from './standalone-runtime.js'
38
39
  import type { DurableStreamsBearerProvider } from './stream-client.js'
39
40
  import type {
@@ -67,6 +68,8 @@ export interface ElectricAgentsServerOptions {
67
68
  request: Request
68
69
  ) => Promise<Principal | null> | Principal | null
69
70
  allowDevPrincipalFallback?: boolean
71
+ eventSources?: EventSourceCatalog
72
+ ensureEventSourceWakeSource?: (sourceUrl: string) => Promise<void> | void
70
73
  /**
71
74
  * Disabled by default. When set to a positive interval, periodically
72
75
  * recovers expired dispatch claims and stale outstanding wakes.
@@ -441,6 +444,15 @@ export class ElectricAgentsServer {
441
444
  streamClient: this.streamClient,
442
445
  runtime: this.standaloneRuntime.runtime,
443
446
  entityBridgeManager: this.entityBridgeManager,
447
+ ...(this.options.eventSources
448
+ ? { eventSources: this.options.eventSources }
449
+ : {}),
450
+ ...(this.options.ensureEventSourceWakeSource
451
+ ? {
452
+ ensureEventSourceWakeSource:
453
+ this.options.ensureEventSourceWakeSource,
454
+ }
455
+ : {}),
444
456
  isShuttingDown: () => this.shuttingDown,
445
457
  mockAgent: this.mockAgentBootstrap
446
458
  ? { runtime: this.mockAgentBootstrap.runtime }