@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 +51 -17
- package/dist/engine.d.ts +26 -19
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +68 -356
- package/dist/engine.js.map +1 -1
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +22 -0
- package/dist/errors.js.map +1 -1
- package/dist/http.d.ts +19 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +107 -2
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +5 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +15 -5
- package/dist/logger.js.map +1 -1
- package/dist/module-instance.d.ts +76 -0
- package/dist/module-instance.d.ts.map +1 -0
- package/dist/module-instance.js +185 -0
- package/dist/module-instance.js.map +1 -0
- package/dist/registry.d.ts +23 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +42 -0
- package/dist/registry.js.map +1 -0
- package/dist/runtime.d.ts +81 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +519 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scheduler.d.ts +11 -2
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +44 -6
- package/dist/scheduler.js.map +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +45 -18
- package/dist/transform.js.map +1 -1
- package/package.json +2 -2
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.
|
|
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 {
|
|
16
|
+
import { Runtime, InMemoryBus } from '@orgloop/core';
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
const runtime = new Runtime({ bus: new InMemoryBus() });
|
|
19
|
+
await runtime.start();
|
|
18
20
|
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
await
|
|
49
|
+
await loop.start();
|
|
50
|
+
await loop.stop();
|
|
31
51
|
```
|
|
32
52
|
|
|
33
53
|
## API
|
|
34
54
|
|
|
35
|
-
###
|
|
55
|
+
### Runtime (multi-module)
|
|
36
56
|
|
|
37
|
-
- `
|
|
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
|
|
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 —
|
|
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
|
|
52
|
-
private readonly
|
|
53
|
-
private readonly
|
|
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
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA
|
|
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 —
|
|
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 {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
40
|
-
this.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
208
|
-
sources:
|
|
209
|
-
actors:
|
|
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:
|
|
212
|
-
...(this.
|
|
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
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|