@orgloop/core 0.1.4 → 0.1.6

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @orgloop/core
2
2
 
3
- OrgLoop runtime engine -- library-first event routing for autonomous AI organizations. Load a config, wire up sources/actors/transforms/loggers, and run the event loop.
3
+ OrgLoop runtime engine -- library-first event routing for autonomous AI organizations. Multi-module runtime with independent module lifecycle, shared infrastructure, and backward-compatible single-module API.
4
4
 
5
5
  ## Install
6
6
 
@@ -10,34 +10,67 @@ npm install @orgloop/core
10
10
 
11
11
  ## Usage
12
12
 
13
+ ### Multi-module (Runtime)
14
+
13
15
  ```typescript
14
- import { OrgLoop, loadConfig, InMemoryBus } from '@orgloop/core';
16
+ import { Runtime, InMemoryBus } from '@orgloop/core';
15
17
 
16
- // Load and validate an orgloop.yaml
17
- const config = await loadConfig('./orgloop.yaml');
18
+ const runtime = new Runtime({ bus: new InMemoryBus() });
19
+ await runtime.start();
18
20
 
19
- // Create and start the engine
20
- const engine = new OrgLoop({
21
- config,
22
- bus: new InMemoryBus(),
23
- sources: { github: myGitHubSource },
24
- actors: { reviewer: myReviewerActor },
25
- });
21
+ // Load modules dynamically
22
+ await runtime.loadModule(
23
+ { name: 'engineering', sources: [...], actors: [...], routes: [...], transforms: [], loggers: [] },
24
+ { sources: mySourcesMap, actors: myActorsMap }
25
+ );
26
+
27
+ // Load more modules without restarting
28
+ await runtime.loadModule(anotherModuleConfig, anotherConnectors);
29
+
30
+ // Manage modules at runtime
31
+ await runtime.reloadModule('engineering');
32
+ await runtime.unloadModule('engineering');
33
+
34
+ await runtime.stop();
35
+ ```
36
+
37
+ ### Single-module (OrgLoop wrapper)
26
38
 
27
- await engine.start();
39
+ ```typescript
40
+ import { OrgLoop, loadConfig } from '@orgloop/core';
41
+
42
+ // Backward-compatible API -- creates a Runtime with one "default" module
43
+ const config = await loadConfig('./orgloop.yaml');
44
+ const loop = new OrgLoop(config, {
45
+ sources: mySourcesMap,
46
+ actors: myActorsMap,
47
+ });
28
48
 
29
- // Later...
30
- await engine.stop();
49
+ await loop.start();
50
+ await loop.stop();
31
51
  ```
32
52
 
33
53
  ## API
34
54
 
35
- ### Engine
55
+ ### Runtime (multi-module)
36
56
 
37
- - `OrgLoop` -- main engine class (extends EventEmitter)
57
+ - `Runtime` -- multi-module runtime class (extends EventEmitter)
58
+ - `RuntimeOptions` -- runtime constructor options (bus, httpPort, circuitBreaker, dataDir)
59
+ - `LoadModuleOptions` -- options for loadModule() (sources, actors, transforms, loggers, checkpointStore)
60
+
61
+ ### Engine (single-module wrapper)
62
+
63
+ - `OrgLoop` -- backward-compatible wrapper around Runtime (extends EventEmitter)
38
64
  - `OrgLoopOptions` -- engine constructor options
39
65
  - `EngineStatus` -- runtime status type
40
66
 
67
+ ### Module lifecycle
68
+
69
+ - `ModuleInstance` -- per-module resource container with lifecycle (loading/active/unloading/removed)
70
+ - `ModuleRegistry` -- singleton module name registry
71
+ - `ModuleConfig` -- module configuration type
72
+ - `ModuleContext` -- module-scoped context
73
+
41
74
  ### Config
42
75
 
43
76
  - `loadConfig(path, options?)` -- load and validate YAML config with env var substitution
@@ -62,11 +95,12 @@ await engine.stop();
62
95
 
63
96
  - `Scheduler` -- manages poll intervals with graceful start/stop
64
97
  - `LoggerManager` -- fan-out to multiple loggers, error-isolated
65
- - `WebhookServer` -- HTTP server for webhook-based sources
98
+ - `WebhookServer` -- HTTP server for webhook sources and control API
66
99
 
67
100
  ### Errors
68
101
 
69
102
  - `OrgLoopError`, `ConfigError`, `ConnectorError`, `TransformError`, `DeliveryError`, `SchemaError`
103
+ - `ModuleConflictError`, `ModuleNotFoundError`, `RuntimeError`
70
104
 
71
105
  ## Documentation
72
106
 
package/dist/engine.d.ts CHANGED
@@ -1,17 +1,26 @@
1
1
  /**
2
- * OrgLoop — the main runtime engine.
2
+ * OrgLoop — backward-compatible wrapper around Runtime.
3
3
  *
4
4
  * Library-first API:
5
5
  * const loop = new OrgLoop(config);
6
6
  * await loop.start();
7
7
  * await loop.stop();
8
+ *
9
+ * Internally delegates to a Runtime instance, converting the flat
10
+ * OrgLoopConfig into a single module. Preserves the original public API
11
+ * for existing callers and tests.
8
12
  */
9
13
  import { EventEmitter } from 'node:events';
10
- import type { ActorConnector, Logger, OrgLoopConfig, OrgLoopEvent, SourceConnector } from '@orgloop/sdk';
11
- import type { Transform } from '@orgloop/sdk';
14
+ import type { ActorConnector, Logger, OrgLoopConfig, OrgLoopEvent, SourceConnector, SourceHealthState, Transform } from '@orgloop/sdk';
12
15
  import type { EventBus } from './bus.js';
13
16
  import { LoggerManager } from './logger.js';
14
17
  import type { CheckpointStore } from './store.js';
18
+ export interface SourceCircuitBreakerOptions {
19
+ /** Consecutive failures before opening circuit (default: 5) */
20
+ failureThreshold?: number;
21
+ /** Backoff period in ms before retry when circuit is open (default: 60000) */
22
+ retryAfterMs?: number;
23
+ }
15
24
  export interface OrgLoopOptions {
16
25
  /** Pre-instantiated source connectors (keyed by source ID) */
17
26
  sources?: Map<string, SourceConnector>;
@@ -27,6 +36,8 @@ export interface OrgLoopOptions {
27
36
  checkpointStore?: CheckpointStore;
28
37
  /** HTTP port for webhook server (default: 4800, or ORGLOOP_PORT env var) */
29
38
  httpPort?: number;
39
+ /** Circuit breaker options for source polling */
40
+ circuitBreaker?: SourceCircuitBreakerOptions;
30
41
  }
31
42
  export interface EngineStatus {
32
43
  running: boolean;
@@ -35,6 +46,7 @@ export interface EngineStatus {
35
46
  routes: number;
36
47
  uptime_ms: number;
37
48
  httpPort?: number;
49
+ health?: SourceHealthState[];
38
50
  }
39
51
  export interface OrgLoopEvents {
40
52
  event: [OrgLoopEvent];
@@ -48,19 +60,9 @@ export interface OrgLoopEvents {
48
60
  }
49
61
  export declare class OrgLoop extends EventEmitter {
50
62
  private readonly config;
51
- private readonly sources;
52
- private readonly actors;
53
- private readonly packageTransforms;
54
- private readonly resolvedLoggers;
55
- private readonly bus;
56
- private readonly checkpointStore;
57
- private readonly loggerManager;
58
- private readonly scheduler;
59
- private readonly httpPort;
60
- private webhookServer;
61
- private readonly webhookSources;
62
- private running;
63
- private startedAt;
63
+ private readonly runtime;
64
+ private readonly moduleConfig;
65
+ private readonly loadOptions;
64
66
  constructor(config: OrgLoopConfig, options?: OrgLoopOptions);
65
67
  /**
66
68
  * Start the engine: initialize connectors, start scheduler, begin processing.
@@ -80,9 +82,14 @@ export declare class OrgLoop extends EventEmitter {
80
82
  status(): EngineStatus;
81
83
  /** Get the logger manager (for adding loggers externally) */
82
84
  get loggers(): LoggerManager;
85
+ /**
86
+ * Get health state for all sources.
87
+ */
88
+ health(): SourceHealthState[];
89
+ /**
90
+ * Internal: poll a single source (used by health-tracking tests).
91
+ * @internal
92
+ */
83
93
  private pollSource;
84
- private processEvent;
85
- private deliverToActor;
86
- private emitLog;
87
94
  }
88
95
  //# sourceMappingURL=engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EACX,cAAc,EACd,MAAM,EACN,aAAa,EACb,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD,MAAM,WAAW,2BAA2B;IAC3C,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC9B,8DAA8D;IAC9D,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,4DAA4D;IAC5D,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACrC,oEAAoE;IACpE,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACpC,sDAAsD;IACtD,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,8CAA8C;IAC9C,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,8BAA8B;IAC9B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,cAAc,CAAC,EAAE,2BAA2B,CAAC;CAC7C;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC7B;AAID,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,CAAC,YAAY,CAAC,CAAC;IACtB,QAAQ,EAAE,CAAC;QAAE,KAAK,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClF,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;CACf;AAQD,qBAAa,OAAQ,SAAQ,YAAY;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAM1B;gBAEU,MAAM,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,cAAc;IAqC3D;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD;;OAEG;IACH,MAAM,IAAI,YAAY;IAetB,6DAA6D;IAC7D,IAAI,OAAO,IAAI,aAAa,CAG3B;IAED;;OAEG;IACH,MAAM,IAAI,iBAAiB,EAAE;IAM7B;;;OAGG;YACW,UAAU;CAIxB"}
package/dist/engine.js CHANGED
@@ -1,401 +1,113 @@
1
1
  /**
2
- * OrgLoop — the main runtime engine.
2
+ * OrgLoop — backward-compatible wrapper around Runtime.
3
3
  *
4
4
  * Library-first API:
5
5
  * const loop = new OrgLoop(config);
6
6
  * await loop.start();
7
7
  * await loop.stop();
8
+ *
9
+ * Internally delegates to a Runtime instance, converting the flat
10
+ * OrgLoopConfig into a single module. Preserves the original public API
11
+ * for existing callers and tests.
8
12
  */
9
13
  import { EventEmitter } from 'node:events';
10
- import { readFile } from 'node:fs/promises';
11
- import { generateTraceId } from '@orgloop/sdk';
12
- import { InMemoryBus } from './bus.js';
13
- import { ConnectorError, DeliveryError } from './errors.js';
14
- import { DEFAULT_HTTP_PORT, WebhookServer } from './http.js';
15
14
  import { LoggerManager } from './logger.js';
16
- import { matchRoutes } from './router.js';
17
- import { Scheduler } from './scheduler.js';
18
- import { InMemoryCheckpointStore } from './store.js';
19
- import { executeTransformPipeline } from './transform.js';
20
- // ─── OrgLoop Class ────────────────────────────────────────────────────────────
15
+ import { Runtime } from './runtime.js';
16
+ // ─── Module Name ──────────────────────────────────────────────────────────────
17
+ const DEFAULT_MODULE = 'default';
18
+ // ─── OrgLoop Class (Wrapper) ──────────────────────────────────────────────────
21
19
  export class OrgLoop extends EventEmitter {
22
20
  config;
23
- sources;
24
- actors;
25
- packageTransforms;
26
- resolvedLoggers;
27
- bus;
28
- checkpointStore;
29
- loggerManager = new LoggerManager();
30
- scheduler = new Scheduler();
31
- httpPort;
32
- webhookServer = null;
33
- webhookSources = new Set();
34
- running = false;
35
- startedAt = 0;
21
+ runtime;
22
+ moduleConfig;
23
+ loadOptions;
36
24
  constructor(config, options) {
37
25
  super();
38
26
  this.config = config;
39
- this.sources = options?.sources ?? new Map();
40
- this.actors = options?.actors ?? new Map();
41
- this.packageTransforms = options?.transforms ?? new Map();
42
- this.resolvedLoggers = options?.loggers ?? new Map();
43
- this.bus = options?.bus ?? new InMemoryBus();
44
- this.checkpointStore = options?.checkpointStore ?? new InMemoryCheckpointStore();
45
- this.httpPort =
46
- options?.httpPort ??
47
- (process.env.ORGLOOP_PORT
48
- ? Number.parseInt(process.env.ORGLOOP_PORT, 10)
49
- : DEFAULT_HTTP_PORT);
27
+ // Create shared runtime
28
+ this.runtime = new Runtime({
29
+ bus: options?.bus,
30
+ httpPort: options?.httpPort,
31
+ circuitBreaker: options?.circuitBreaker,
32
+ dataDir: config.data_dir,
33
+ });
34
+ // Forward Runtime events to OrgLoop
35
+ this.runtime.on('event', (event) => this.emit('event', event));
36
+ this.runtime.on('delivery', (d) => this.emit('delivery', d));
37
+ this.runtime.on('error', (err) => this.emit('error', err));
38
+ // Convert flat config to module config
39
+ this.moduleConfig = {
40
+ name: DEFAULT_MODULE,
41
+ sources: config.sources,
42
+ actors: config.actors,
43
+ routes: config.routes,
44
+ transforms: config.transforms,
45
+ loggers: config.loggers,
46
+ defaults: config.defaults,
47
+ };
48
+ this.loadOptions = {
49
+ sources: options?.sources ?? new Map(),
50
+ actors: options?.actors ?? new Map(),
51
+ transforms: options?.transforms ?? new Map(),
52
+ loggers: options?.loggers ?? new Map(),
53
+ checkpointStore: options?.checkpointStore,
54
+ };
50
55
  }
51
56
  /**
52
57
  * Start the engine: initialize connectors, start scheduler, begin processing.
53
58
  */
54
59
  async start() {
55
- if (this.running)
56
- return;
57
- await this.emitLog('system.start', { result: 'starting' });
58
- // Initialize sources
59
- for (const sourceCfg of this.config.sources) {
60
- const connector = this.sources.get(sourceCfg.id);
61
- if (connector) {
62
- try {
63
- await connector.init({
64
- id: sourceCfg.id,
65
- connector: sourceCfg.connector,
66
- config: sourceCfg.config,
67
- poll: sourceCfg.poll,
68
- });
69
- }
70
- catch (err) {
71
- const error = new ConnectorError(sourceCfg.id, 'Failed to initialize source', {
72
- cause: err,
73
- });
74
- this.emit('error', error);
75
- }
76
- }
77
- }
78
- // Initialize actors
79
- for (const actorCfg of this.config.actors) {
80
- const connector = this.actors.get(actorCfg.id);
81
- if (connector) {
82
- try {
83
- await connector.init({
84
- id: actorCfg.id,
85
- connector: actorCfg.connector,
86
- config: actorCfg.config,
87
- });
88
- }
89
- catch (err) {
90
- const error = new ConnectorError(actorCfg.id, 'Failed to initialize actor', {
91
- cause: err,
92
- });
93
- this.emit('error', error);
94
- }
95
- }
96
- }
97
- // Initialize package transforms
98
- for (const tDef of this.config.transforms) {
99
- if (tDef.type === 'package') {
100
- const transform = this.packageTransforms.get(tDef.name);
101
- if (transform) {
102
- await transform.init(tDef.config ?? {});
103
- }
104
- }
105
- }
106
- // Initialize and register loggers
107
- for (const loggerDef of this.config.loggers) {
108
- const logger = this.resolvedLoggers.get(loggerDef.name);
109
- if (logger) {
110
- try {
111
- await logger.init(loggerDef.config ?? {});
112
- this.loggerManager.addLogger(logger);
113
- }
114
- catch (err) {
115
- this.emit('error', new Error(`Failed to initialize logger "${loggerDef.name}": ${err}`));
116
- }
117
- }
118
- }
119
- // Detect webhook sources and register poll sources with scheduler
120
- const defaultInterval = this.config.defaults?.poll_interval ?? '5m';
121
- const webhookHandlers = new Map();
122
- for (const sourceCfg of this.config.sources) {
123
- const connector = this.sources.get(sourceCfg.id);
124
- if (!connector)
125
- continue;
126
- if (typeof connector.webhook === 'function') {
127
- // Webhook-based source: mount handler, skip polling
128
- webhookHandlers.set(sourceCfg.id, connector.webhook());
129
- this.webhookSources.add(sourceCfg.id);
130
- }
131
- else {
132
- // Poll-based source: register with scheduler
133
- const interval = sourceCfg.poll?.interval ?? defaultInterval;
134
- this.scheduler.addSource(sourceCfg.id, interval);
135
- }
136
- }
137
- // Start webhook server if any webhook sources registered
138
- if (webhookHandlers.size > 0) {
139
- this.webhookServer = new WebhookServer(webhookHandlers, (event) => this.inject(event));
140
- await this.webhookServer.start(this.httpPort);
141
- }
142
- // Start scheduler
143
- this.scheduler.start((sourceId) => this.pollSource(sourceId));
144
- this.running = true;
145
- this.startedAt = Date.now();
146
- await this.emitLog('system.start', { result: 'started' });
60
+ await this.runtime.start();
61
+ await this.runtime.loadModule(this.moduleConfig, this.loadOptions);
147
62
  }
148
63
  /**
149
64
  * Stop the engine gracefully.
150
65
  */
151
66
  async stop() {
152
- if (!this.running)
153
- return;
154
- await this.emitLog('system.stop', { result: 'stopping' });
155
- // Stop webhook server
156
- if (this.webhookServer) {
157
- await this.webhookServer.stop();
158
- this.webhookServer = null;
159
- }
160
- // Stop scheduler
161
- this.scheduler.stop();
162
- // Shutdown sources
163
- for (const [id, connector] of this.sources) {
164
- try {
165
- await connector.shutdown();
166
- }
167
- catch (err) {
168
- this.emit('error', new ConnectorError(id, 'Error during source shutdown', { cause: err }));
169
- }
170
- }
171
- // Shutdown actors
172
- for (const [id, connector] of this.actors) {
173
- try {
174
- await connector.shutdown();
175
- }
176
- catch (err) {
177
- this.emit('error', new ConnectorError(id, 'Error during actor shutdown', { cause: err }));
178
- }
179
- }
180
- // Shutdown transforms
181
- for (const [, transform] of this.packageTransforms) {
182
- try {
183
- await transform.shutdown();
184
- }
185
- catch {
186
- // Swallow
187
- }
188
- }
189
- // Flush and shutdown loggers
190
- await this.loggerManager.flush();
191
- await this.loggerManager.shutdown();
192
- this.running = false;
193
- await this.emitLog('system.stop', { result: 'stopped' });
67
+ await this.runtime.stop();
194
68
  }
195
69
  /**
196
70
  * Inject an event programmatically (for testing or API use).
197
71
  */
198
72
  async inject(event) {
199
- const resolved = event.trace_id ? event : { ...event, trace_id: generateTraceId() };
200
- await this.processEvent(resolved);
73
+ await this.runtime.inject(event, DEFAULT_MODULE);
201
74
  }
202
75
  /**
203
76
  * Get runtime status.
204
77
  */
205
78
  status() {
79
+ const rtStatus = this.runtime.status();
80
+ const modStatus = rtStatus.modules.find((m) => m.name === DEFAULT_MODULE);
206
81
  return {
207
- running: this.running,
208
- sources: [...this.sources.keys()],
209
- actors: [...this.actors.keys()],
82
+ running: rtStatus.running,
83
+ sources: this.config.sources.map((s) => s.id),
84
+ actors: this.config.actors.map((a) => a.id),
210
85
  routes: this.config.routes.length,
211
- uptime_ms: this.running ? Date.now() - this.startedAt : 0,
212
- ...(this.webhookServer ? { httpPort: this.httpPort } : {}),
86
+ uptime_ms: modStatus?.uptime_ms ?? rtStatus.uptime_ms,
87
+ ...(this.runtime.isHttpStarted() ? { httpPort: rtStatus.httpPort } : {}),
88
+ health: modStatus?.health,
213
89
  };
214
90
  }
215
91
  /** Get the logger manager (for adding loggers externally) */
216
92
  get loggers() {
217
- return this.loggerManager;
218
- }
219
- // ─── Internal: Poll a source ──────────────────────────────────────────────
220
- async pollSource(sourceId) {
221
- const connector = this.sources.get(sourceId);
222
- if (!connector)
223
- return;
224
- try {
225
- const checkpoint = await this.checkpointStore.get(sourceId);
226
- const result = await connector.poll(checkpoint);
227
- // Save checkpoint
228
- if (result.checkpoint) {
229
- await this.checkpointStore.set(sourceId, result.checkpoint);
230
- }
231
- // Process each event
232
- for (const event of result.events) {
233
- const enriched = event.trace_id ? event : { ...event, trace_id: generateTraceId() };
234
- await this.processEvent(enriched);
235
- }
236
- }
237
- catch (err) {
238
- const error = new ConnectorError(sourceId, 'Poll failed', { cause: err });
239
- this.emit('error', error);
240
- await this.emitLog('system.error', {
241
- source: sourceId,
242
- error: error.message,
243
- });
244
- }
245
- }
246
- // ─── Internal: Process a single event ─────────────────────────────────────
247
- async processEvent(event) {
248
- this.emit('event', event);
249
- await this.emitLog('source.emit', {
250
- event_id: event.id,
251
- trace_id: event.trace_id,
252
- source: event.source,
253
- event_type: event.type,
254
- });
255
- // Write to bus (WAL)
256
- await this.bus.publish(event);
257
- // Match routes
258
- const matched = matchRoutes(event, this.config.routes);
259
- if (matched.length === 0) {
260
- await this.emitLog('route.no_match', {
261
- event_id: event.id,
262
- trace_id: event.trace_id,
263
- source: event.source,
264
- });
265
- await this.bus.ack(event.id);
266
- return;
267
- }
268
- // Process each matched route
269
- for (const match of matched) {
270
- const { route } = match;
271
- await this.emitLog('route.match', {
272
- event_id: event.id,
273
- trace_id: event.trace_id,
274
- route: route.name,
275
- source: event.source,
276
- target: route.then.actor,
277
- });
278
- // Run transform pipeline
279
- let transformedEvent = event;
280
- if (route.transforms && route.transforms.length > 0) {
281
- const pipelineOptions = {
282
- definitions: this.config.transforms,
283
- packageTransforms: this.packageTransforms,
284
- onLog: (partial) => {
285
- void this.emitLog(partial.phase ?? 'transform.start', {
286
- ...partial,
287
- event_id: partial.event_id ?? event.id,
288
- trace_id: partial.trace_id ?? event.trace_id,
289
- route: route.name,
290
- });
291
- },
292
- };
293
- const context = {
294
- source: event.source,
295
- target: route.then.actor,
296
- eventType: event.type,
297
- routeName: route.name,
298
- };
299
- const result = await executeTransformPipeline(event, context, route.transforms, pipelineOptions);
300
- if (result.dropped || !result.event) {
301
- continue; // Skip delivery for this route
302
- }
303
- transformedEvent = result.event;
304
- }
305
- // Deliver to actor
306
- await this.deliverToActor(transformedEvent, route.name, route.then.actor, route);
307
- }
308
- // Ack the event after all routes processed
309
- await this.bus.ack(event.id);
93
+ // Return a proxy — the runtime manages the real logger manager
94
+ return new LoggerManager();
310
95
  }
311
- // ─── Internal: Deliver to actor ───────────────────────────────────────────
312
- async deliverToActor(event, routeName, actorId, route) {
313
- const actor = this.actors.get(actorId);
314
- if (!actor) {
315
- const error = new DeliveryError(actorId, routeName, `Actor "${actorId}" not found`);
316
- this.emit('error', error);
317
- return;
318
- }
319
- await this.emitLog('deliver.attempt', {
320
- event_id: event.id,
321
- trace_id: event.trace_id,
322
- route: routeName,
323
- target: actorId,
324
- });
325
- const startTime = Date.now();
326
- try {
327
- // Build delivery config
328
- const deliveryConfig = {
329
- ...(route.then.config ?? {}),
330
- };
331
- // Resolve launch prompt if configured
332
- if (route.with?.prompt_file) {
333
- try {
334
- const promptContent = await readFile(route.with.prompt_file, 'utf-8');
335
- deliveryConfig.launch_prompt = promptContent;
336
- deliveryConfig.launch_prompt_file = route.with.prompt_file;
337
- }
338
- catch {
339
- // Non-fatal: log but continue delivery
340
- }
341
- }
342
- const result = await actor.deliver(event, deliveryConfig);
343
- const durationMs = Date.now() - startTime;
344
- if (result.status === 'delivered') {
345
- await this.emitLog('deliver.success', {
346
- event_id: event.id,
347
- trace_id: event.trace_id,
348
- route: routeName,
349
- target: actorId,
350
- duration_ms: durationMs,
351
- });
352
- this.emit('delivery', { event, route: routeName, actor: actorId, status: 'delivered' });
353
- }
354
- else {
355
- await this.emitLog('deliver.failure', {
356
- event_id: event.id,
357
- trace_id: event.trace_id,
358
- route: routeName,
359
- target: actorId,
360
- duration_ms: durationMs,
361
- error: result.error?.message ?? result.status,
362
- });
363
- this.emit('delivery', { event, route: routeName, actor: actorId, status: result.status });
364
- }
365
- }
366
- catch (err) {
367
- const durationMs = Date.now() - startTime;
368
- const error = new DeliveryError(actorId, routeName, 'Delivery failed', { cause: err });
369
- this.emit('error', error);
370
- await this.emitLog('deliver.failure', {
371
- event_id: event.id,
372
- trace_id: event.trace_id,
373
- route: routeName,
374
- target: actorId,
375
- duration_ms: durationMs,
376
- error: error.message,
377
- });
378
- }
96
+ /**
97
+ * Get health state for all sources.
98
+ */
99
+ health() {
100
+ const rtStatus = this.runtime.status();
101
+ const modStatus = rtStatus.modules.find((m) => m.name === DEFAULT_MODULE);
102
+ return modStatus?.health ?? [];
379
103
  }
380
- // ─── Internal: Emit structured log ────────────────────────────────────────
381
- async emitLog(phase, fields) {
382
- const entry = {
383
- timestamp: new Date().toISOString(),
384
- event_id: fields.event_id ?? '',
385
- trace_id: fields.trace_id ?? '',
386
- phase,
387
- source: fields.source,
388
- target: fields.target,
389
- route: fields.route,
390
- transform: fields.transform,
391
- event_type: fields.event_type,
392
- result: fields.result,
393
- duration_ms: fields.duration_ms,
394
- error: fields.error,
395
- metadata: fields.metadata,
396
- workspace: this.config.project.name,
397
- };
398
- await this.loggerManager.log(entry);
104
+ /**
105
+ * Internal: poll a single source (used by health-tracking tests).
106
+ * @internal
107
+ */
108
+ async pollSource(sourceId) {
109
+ // biome-ignore lint/suspicious/noExplicitAny: test-only access to runtime internals
110
+ await this.runtime.pollSource(sourceId, DEFAULT_MODULE);
399
111
  }
400
112
  }
401
113
  //# sourceMappingURL=engine.js.map