@agentxjs/core 1.9.10-dev → 2.0.0

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.
Files changed (99) hide show
  1. package/README.md +342 -0
  2. package/dist/RpcClient-BcJ_zAGu.d.ts +304 -0
  3. package/dist/agent/engine/internal/index.d.ts +20 -15
  4. package/dist/agent/engine/internal/index.js +1 -2
  5. package/dist/agent/engine/mealy/index.js +0 -1
  6. package/dist/agent/index.d.ts +4 -4
  7. package/dist/agent/index.js +15 -15
  8. package/dist/agent/types/index.d.ts +4 -4
  9. package/dist/agent/types/index.js +1 -2
  10. package/dist/bash/index.d.ts +29 -0
  11. package/dist/bash/index.js +7 -0
  12. package/dist/{bus-uF1DM2ox.d.ts → bus-C9FLWIu8.d.ts} +3 -1
  13. package/dist/{chunk-K6WXQ2RW.js → chunk-23UUBQXR.js} +1 -2
  14. package/dist/chunk-23UUBQXR.js.map +1 -0
  15. package/dist/chunk-BHOD5PKR.js +55 -0
  16. package/dist/chunk-BHOD5PKR.js.map +1 -0
  17. package/dist/{chunk-I7GYR3MN.js → chunk-DEAR6N3O.js} +77 -91
  18. package/dist/chunk-DEAR6N3O.js.map +1 -0
  19. package/dist/chunk-FI7WQFGV.js +37 -0
  20. package/dist/chunk-FI7WQFGV.js.map +1 -0
  21. package/dist/{chunk-AT5P47YA.js → chunk-JTKCV7IS.js} +9 -9
  22. package/dist/chunk-JTKCV7IS.js.map +1 -0
  23. package/dist/{chunk-E5FPOAPO.js → chunk-LTVNPHST.js} +1 -1
  24. package/dist/chunk-LTVNPHST.js.map +1 -0
  25. package/dist/chunk-SKS7S2RY.js +1 -0
  26. package/dist/common/logger/index.js +0 -2
  27. package/dist/common/logger/index.js.map +1 -1
  28. package/dist/container/index.d.ts +3 -4
  29. package/dist/container/index.js +0 -2
  30. package/dist/container/index.js.map +1 -1
  31. package/dist/driver/index.d.ts +2 -310
  32. package/dist/event/index.d.ts +4 -4
  33. package/dist/event/index.js +1 -2
  34. package/dist/event/types/index.d.ts +4 -10
  35. package/dist/event/types/index.js +1 -2
  36. package/dist/{event-CDuTzs__.d.ts → event-DNWOBSBO.d.ts} +3 -4
  37. package/dist/image/index.d.ts +9 -5
  38. package/dist/image/index.js +5 -2
  39. package/dist/image/index.js.map +1 -1
  40. package/dist/index-CuS1i5V-.d.ts +609 -0
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.js +16 -16
  43. package/dist/{message-BMrMm1pq.d.ts → message-03TJzvIX.d.ts} +10 -33
  44. package/dist/mq/index.js +0 -2
  45. package/dist/mq/index.js.map +1 -1
  46. package/dist/network/index.d.ts +3 -291
  47. package/dist/network/index.js +3 -14
  48. package/dist/network/index.js.map +1 -1
  49. package/dist/persistence/index.d.ts +2 -155
  50. package/dist/platform/index.d.ts +76 -0
  51. package/dist/platform/index.js.map +1 -0
  52. package/dist/runtime/index.d.ts +26 -59
  53. package/dist/runtime/index.js +117 -33
  54. package/dist/runtime/index.js.map +1 -1
  55. package/dist/session/index.d.ts +4 -52
  56. package/dist/session/index.js +4 -51
  57. package/dist/session/index.js.map +1 -1
  58. package/dist/types-aE74Eo6G.d.ts +90 -0
  59. package/package.json +10 -5
  60. package/src/agent/__tests__/engine/internal/messageAssemblerProcessor.test.ts +291 -87
  61. package/src/agent/__tests__/engine/internal/turnTrackerProcessor.test.ts +56 -75
  62. package/src/agent/engine/MealyMachine.ts +1 -1
  63. package/src/agent/engine/internal/messageAssemblerProcessor.ts +99 -114
  64. package/src/agent/engine/internal/turnTrackerProcessor.ts +23 -27
  65. package/src/agent/types/event.ts +0 -4
  66. package/src/agent/types/index.ts +1 -3
  67. package/src/agent/types/message.ts +9 -43
  68. package/src/bash/index.ts +21 -0
  69. package/src/bash/tool.ts +57 -0
  70. package/src/bash/types.ts +108 -0
  71. package/src/driver/index.ts +1 -0
  72. package/src/driver/types.ts +122 -4
  73. package/src/event/__tests__/EventBus.test.ts +1 -1
  74. package/src/event/types/agent.ts +0 -11
  75. package/src/event/types/command.ts +3 -1
  76. package/src/image/Image.ts +11 -1
  77. package/src/image/types.ts +8 -2
  78. package/src/network/RpcClient.ts +21 -20
  79. package/src/network/index.ts +1 -1
  80. package/src/persistence/types.ts +5 -2
  81. package/src/platform/index.ts +21 -0
  82. package/src/platform/types.ts +84 -0
  83. package/src/runtime/AgentXRuntime.ts +184 -57
  84. package/src/runtime/__tests__/AgentXRuntime.test.ts +343 -0
  85. package/src/runtime/index.ts +7 -19
  86. package/src/runtime/types.ts +10 -62
  87. package/dist/chunk-7D4SUZUM.js +0 -38
  88. package/dist/chunk-AT5P47YA.js.map +0 -1
  89. package/dist/chunk-E5FPOAPO.js.map +0 -1
  90. package/dist/chunk-I7GYR3MN.js.map +0 -1
  91. package/dist/chunk-K6WXQ2RW.js.map +0 -1
  92. package/dist/workspace/index.d.ts +0 -111
  93. package/dist/wrapper-Y3UTVU2E.js +0 -3635
  94. package/dist/wrapper-Y3UTVU2E.js.map +0 -1
  95. package/src/workspace/index.ts +0 -27
  96. package/src/workspace/types.ts +0 -131
  97. /package/dist/{workspace → bash}/index.js.map +0 -0
  98. /package/dist/{chunk-7D4SUZUM.js.map → chunk-SKS7S2RY.js.map} +0 -0
  99. /package/dist/{workspace → platform}/index.js +0 -0
@@ -49,6 +49,10 @@ export class ImageImpl implements Image {
49
49
  return this.record.mcpServers;
50
50
  }
51
51
 
52
+ get customData(): Record<string, unknown> | undefined {
53
+ return this.record.customData;
54
+ }
55
+
52
56
  get createdAt(): number {
53
57
  return this.record.createdAt;
54
58
  }
@@ -76,6 +80,7 @@ export class ImageImpl implements Image {
76
80
  description: config.description,
77
81
  systemPrompt: config.systemPrompt,
78
82
  mcpServers: config.mcpServers,
83
+ customData: config.customData,
79
84
  createdAt: now,
80
85
  updatedAt: now,
81
86
  };
@@ -135,12 +140,17 @@ export class ImageImpl implements Image {
135
140
  /**
136
141
  * Update image metadata
137
142
  */
138
- async update(updates: { name?: string; description?: string }): Promise<Image> {
143
+ async update(updates: {
144
+ name?: string;
145
+ description?: string;
146
+ customData?: Record<string, unknown>;
147
+ }): Promise<Image> {
139
148
  const now = Date.now();
140
149
  const updatedRecord: ImageRecord = {
141
150
  ...this.record,
142
151
  name: updates.name ?? this.record.name,
143
152
  description: updates.description ?? this.record.description,
153
+ customData: updates.customData !== undefined ? updates.customData : this.record.customData,
144
154
  updatedAt: now,
145
155
  };
146
156
 
@@ -34,13 +34,18 @@ export interface Image {
34
34
  readonly description: string | undefined;
35
35
  readonly systemPrompt: string | undefined;
36
36
  readonly mcpServers: Record<string, McpServerConfig> | undefined;
37
+ readonly customData: Record<string, unknown> | undefined;
37
38
  readonly createdAt: number;
38
39
  readonly updatedAt: number;
39
40
 
40
41
  /**
41
- * Update image metadata (name, description)
42
+ * Update image metadata (name, description, customData)
42
43
  */
43
- update(updates: { name?: string; description?: string }): Promise<Image>;
44
+ update(updates: {
45
+ name?: string;
46
+ description?: string;
47
+ customData?: Record<string, unknown>;
48
+ }): Promise<Image>;
44
49
 
45
50
  /**
46
51
  * Delete this image and its session
@@ -74,4 +79,5 @@ export interface ImageCreateConfig {
74
79
  description?: string;
75
80
  systemPrompt?: string;
76
81
  mcpServers?: Record<string, McpServerConfig>;
82
+ customData?: Record<string, unknown>;
77
83
  }
@@ -39,19 +39,18 @@ import {
39
39
  } from "./jsonrpc";
40
40
  import type { SystemEvent } from "../event/types/base";
41
41
 
42
- /**
43
- * Check if running in browser environment
44
- */
45
- function isBrowser(): boolean {
46
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
- const globalWindow = typeof globalThis !== "undefined" ? (globalThis as any).window : undefined;
48
- return globalWindow?.document !== undefined;
49
- }
50
-
51
42
  // ============================================================================
52
43
  // Types
53
44
  // ============================================================================
54
45
 
46
+ /**
47
+ * Factory function for creating WebSocket instances.
48
+ * Platform layer provides the implementation:
49
+ * - Browser: native WebSocket (default)
50
+ * - Node.js: ws library (via @agentxjs/node-platform)
51
+ */
52
+ export type WebSocketFactory = (url: string) => WebSocket;
53
+
55
54
  /**
56
55
  * RpcClient configuration
57
56
  */
@@ -61,6 +60,12 @@ export interface RpcClientConfig {
61
60
  */
62
61
  url: string;
63
62
 
63
+ /**
64
+ * Factory for creating WebSocket instances.
65
+ * If not provided, falls back to the global WebSocket constructor.
66
+ */
67
+ createWebSocket?: WebSocketFactory;
68
+
64
69
  /**
65
70
  * Request timeout in milliseconds (default: 30000)
66
71
  */
@@ -77,7 +82,7 @@ export interface RpcClientConfig {
77
82
  reconnectDelay?: number;
78
83
 
79
84
  /**
80
- * Headers for authentication (Node.js only, sent in first message for browser)
85
+ * Headers for authentication (sent in first message after connection)
81
86
  */
82
87
  headers?:
83
88
  | Record<string, string>
@@ -172,14 +177,9 @@ export class RpcClient {
172
177
 
173
178
  const url = this.config.url;
174
179
 
175
- // Create WebSocket (browser or Node.js)
176
- let ws: WebSocket;
177
- if (isBrowser()) {
178
- ws = new WebSocket(url);
179
- } else {
180
- const { default: WS } = await import("ws");
181
- ws = new WS(url) as unknown as WebSocket;
182
- }
180
+ // Create WebSocket via injected factory or global WebSocket
181
+ const factory = this.config.createWebSocket ?? ((u: string) => new WebSocket(u));
182
+ const ws = factory(url);
183
183
 
184
184
  this.ws = ws;
185
185
 
@@ -191,8 +191,9 @@ export class RpcClient {
191
191
  console.log("[RpcClient] Connected to", url);
192
192
  }
193
193
 
194
- // Send auth if in browser (headers not supported in WebSocket API)
195
- if (isBrowser() && this.config.headers) {
194
+ // Send auth headers after connection (for environments where
195
+ // WebSocket constructor doesn't support headers, e.g. browser)
196
+ if (this.config.headers) {
196
197
  const headers =
197
198
  typeof this.config.headers === "function"
198
199
  ? await this.config.headers()
@@ -72,5 +72,5 @@ export {
72
72
  } from "./jsonrpc";
73
73
 
74
74
  // RPC Client
75
- export type { RpcClientConfig, RpcClientState } from "./RpcClient";
75
+ export type { RpcClientConfig, RpcClientState, WebSocketFactory } from "./RpcClient";
76
76
  export { RpcClient } from "./RpcClient";
@@ -50,8 +50,8 @@ export interface ContainerRecord {
50
50
  * Image metadata for storing provider-specific data
51
51
  */
52
52
  export interface ImageMetadata {
53
- /** Claude SDK session ID for conversation resume */
54
- claudeSdkSessionId?: string;
53
+ /** Driver session ID for conversation resume */
54
+ driverSessionId?: string;
55
55
  }
56
56
 
57
57
  /**
@@ -93,6 +93,9 @@ export interface ImageRecord {
93
93
  /** Provider-specific metadata */
94
94
  metadata?: ImageMetadata;
95
95
 
96
+ /** Application-specific custom data (favorites, sort order, tags, etc.) */
97
+ customData?: Record<string, unknown>;
98
+
96
99
  /** Creation timestamp (Unix milliseconds) */
97
100
  createdAt: number;
98
101
 
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Platform Module
3
+ *
4
+ * AgentXPlatform dependency injection container.
5
+ * Platform packages provide concrete implementations.
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * import type { AgentXPlatform } from "@agentxjs/core/platform";
10
+ *
11
+ * const platform: AgentXPlatform = {
12
+ * containerRepository,
13
+ * imageRepository,
14
+ * sessionRepository,
15
+ * eventBus,
16
+ * bashProvider, // optional
17
+ * };
18
+ * ```
19
+ */
20
+
21
+ export type { AgentXPlatform } from "./types";
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Platform Types
3
+ *
4
+ * AgentXPlatform - Dependency injection container for platform capabilities.
5
+ * Platform packages (node-platform, etc.) provide implementations.
6
+ *
7
+ * ```
8
+ * ┌─────────────────────────────────────────────────────────────┐
9
+ * │ AgentXPlatform │
10
+ * │ (Dependency Injection - Platform provides implementations) │
11
+ * │ │
12
+ * │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
13
+ * │ │ Repositories│ │ EventBus │ │ Providers │ │
14
+ * │ │ Container │ │ │ │ Bash (opt) │ │
15
+ * │ │ Image │ │ │ │ │ │
16
+ * │ │ Session │ │ │ │ │ │
17
+ * │ └─────────────┘ └─────────────┘ └─────────────┘ │
18
+ * └─────────────────────────────────────────────────────────────┘
19
+ * ```
20
+ */
21
+
22
+ import type { ContainerRepository } from "../container/types";
23
+ import type { ImageRepository } from "../image/types";
24
+ import type { SessionRepository } from "../session/types";
25
+ import type { EventBus } from "../event/types";
26
+ import type { BashProvider } from "../bash/types";
27
+ import type { WebSocketFactory } from "../network/RpcClient";
28
+
29
+ // ============================================================================
30
+ // AgentXPlatform - Dependency Injection
31
+ // ============================================================================
32
+
33
+ /**
34
+ * AgentXPlatform - Collects all dependencies for runtime
35
+ *
36
+ * Platform packages provide implementations of these interfaces.
37
+ * The platform is passed to AgentXRuntime for integration.
38
+ *
39
+ * Required capabilities:
40
+ * - containerRepository, imageRepository, sessionRepository — persistence
41
+ * - eventBus — pub/sub
42
+ *
43
+ * Optional capabilities:
44
+ * - bashProvider — command execution (not all platforms support this)
45
+ */
46
+ export interface AgentXPlatform {
47
+ /**
48
+ * Container repository for persistence
49
+ */
50
+ readonly containerRepository: ContainerRepository;
51
+
52
+ /**
53
+ * Image repository for persistence
54
+ */
55
+ readonly imageRepository: ImageRepository;
56
+
57
+ /**
58
+ * Session repository for persistence
59
+ */
60
+ readonly sessionRepository: SessionRepository;
61
+
62
+ /**
63
+ * Event bus for pub/sub
64
+ */
65
+ readonly eventBus: EventBus;
66
+
67
+ // === Optional Providers ===
68
+
69
+ /**
70
+ * Bash provider for command execution
71
+ *
72
+ * Optional — not all platforms support shell execution.
73
+ * Node.js platform provides child_process based implementation.
74
+ */
75
+ readonly bashProvider?: BashProvider;
76
+
77
+ /**
78
+ * WebSocket factory for creating client connections
79
+ *
80
+ * Optional — browser uses native WebSocket by default.
81
+ * Node.js platform provides ws-based implementation.
82
+ */
83
+ readonly webSocketFactory?: WebSocketFactory;
84
+ }
@@ -2,17 +2,18 @@
2
2
  * AgentXRuntimeImpl - Runtime integration implementation
3
3
  *
4
4
  * Integrates all components to provide agent lifecycle management.
5
- * Uses Provider dependencies to coordinate Session, Image, Container, etc.
5
+ * Uses Platform dependencies to coordinate Session, Image, Container, etc.
6
6
  *
7
- * New Design:
7
+ * Architecture:
8
8
  * - Driver.receive() returns AsyncIterable<DriverStreamEvent>
9
- * - Runtime processes events and emits to EventBus
10
- * - No more EventBus-based communication with Driver
9
+ * - Runtime emits raw stream events to EventBus
10
+ * - Runtime pushes events through AgentEngine (MealyMachine → Presenter)
11
+ * - Presenter emits message/state/turn events and persists messages
11
12
  */
12
13
 
13
14
  import { createLogger } from "commonxjs/logger";
14
15
  import type {
15
- AgentXProvider,
16
+ AgentXPlatform,
16
17
  AgentXRuntime,
17
18
  RuntimeAgent,
18
19
  CreateAgentOptions,
@@ -20,9 +21,27 @@ import type {
20
21
  Subscription,
21
22
  AgentLifecycle,
22
23
  } from "./types";
23
- import type { UserContentPart, UserMessage } from "../agent/types";
24
+ import type {
25
+ UserContentPart,
26
+ UserMessage,
27
+ AgentEngine,
28
+ StreamEvent,
29
+ AgentOutput,
30
+ AgentPresenter,
31
+ AgentSource,
32
+ Message,
33
+ } from "../agent/types";
24
34
  import type { BusEvent } from "../event/types";
25
- import type { Driver, DriverConfig, DriverStreamEvent } from "../driver/types";
35
+ import type {
36
+ CreateDriver,
37
+ Driver,
38
+ DriverConfig,
39
+ DriverStreamEvent,
40
+ ToolDefinition,
41
+ } from "../driver/types";
42
+ import { createSession } from "../session/Session";
43
+ import { createBashTool } from "../bash/tool";
44
+ import { createAgent as createAgentEngine } from "../agent/createAgent";
26
45
 
27
46
  const logger = createLogger("runtime/AgentXRuntime");
28
47
 
@@ -34,6 +53,7 @@ interface AgentState {
34
53
  lifecycle: AgentLifecycle;
35
54
  subscriptions: Set<() => void>;
36
55
  driver: Driver;
56
+ engine: AgentEngine;
37
57
  /** Flag to track if a receive operation is in progress */
38
58
  isReceiving: boolean;
39
59
  }
@@ -42,14 +62,16 @@ interface AgentState {
42
62
  * AgentXRuntimeImpl - Runtime implementation
43
63
  */
44
64
  export class AgentXRuntimeImpl implements AgentXRuntime {
45
- readonly provider: AgentXProvider;
65
+ readonly platform: AgentXPlatform;
66
+ private readonly createDriver: CreateDriver;
46
67
 
47
68
  private agents = new Map<string, AgentState>();
48
69
  private globalSubscriptions = new Set<() => void>();
49
70
  private isShutdown = false;
50
71
 
51
- constructor(provider: AgentXProvider) {
52
- this.provider = provider;
72
+ constructor(platform: AgentXPlatform, createDriver: CreateDriver) {
73
+ this.platform = platform;
74
+ this.createDriver = createDriver;
53
75
  logger.info("AgentXRuntime initialized");
54
76
  }
55
77
 
@@ -63,7 +85,7 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
63
85
  const { imageId } = options;
64
86
 
65
87
  // Load image
66
- const imageRecord = await this.provider.imageRepository.findImageById(imageId);
88
+ const imageRecord = await this.platform.imageRepository.findImageById(imageId);
67
89
  if (!imageRecord) {
68
90
  throw new Error(`Image not found: ${imageId}`);
69
91
  }
@@ -72,41 +94,102 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
72
94
  const agentId = options.agentId ?? this.generateAgentId();
73
95
 
74
96
  // Ensure container exists
75
- const containerExists = await this.provider.containerRepository.containerExists(
97
+ const containerExists = await this.platform.containerRepository.containerExists(
76
98
  imageRecord.containerId
77
99
  );
78
100
  if (!containerExists) {
79
101
  throw new Error(`Container not found: ${imageRecord.containerId}`);
80
102
  }
81
103
 
82
- // Create workspace
83
- const workspace = await this.provider.workspaceProvider.create({
84
- containerId: imageRecord.containerId,
104
+ // Create Session for driver (MonoDriver needs this to read history)
105
+ const session = createSession({
106
+ sessionId: imageRecord.sessionId,
85
107
  imageId,
108
+ containerId: imageRecord.containerId,
109
+ repository: this.platform.sessionRepository,
86
110
  });
87
- await workspace.initialize();
88
111
 
89
- // Create driver config
112
+ // Assemble platform-provided default tools
113
+ const defaultTools: ToolDefinition[] = [];
114
+ if (this.platform.bashProvider) {
115
+ defaultTools.push(createBashTool(this.platform.bashProvider));
116
+ }
117
+
118
+ // Create driver config (apiKey/baseUrl are provided by the createDriver closure)
90
119
  const driverConfig: DriverConfig = {
91
- apiKey: process.env.ANTHROPIC_API_KEY ?? "",
92
- baseUrl: process.env.ANTHROPIC_BASE_URL,
120
+ apiKey: "",
93
121
  agentId,
94
122
  systemPrompt: imageRecord.systemPrompt,
95
- cwd: workspace.path,
96
123
  mcpServers: imageRecord.mcpServers,
97
- resumeSessionId: imageRecord.metadata?.claudeSdkSessionId as string | undefined,
98
- onSessionIdCaptured: async (claudeSdkSessionId: string) => {
99
- // Persist SDK session ID for resume
100
- await this.provider.imageRepository.updateMetadata(imageId, { claudeSdkSessionId });
124
+ tools: defaultTools.length > 0 ? defaultTools : undefined,
125
+ session, // Inject Session for stateless drivers
126
+ resumeSessionId: imageRecord.metadata?.driverSessionId as string | undefined,
127
+ onSessionIdCaptured: async (driverSessionId: string) => {
128
+ // Persist driver session ID for resume
129
+ await this.platform.imageRepository.updateMetadata(imageId, { driverSessionId });
101
130
  },
102
131
  };
103
132
 
104
- // Create driver using the new CreateDriver function
105
- const driver = this.provider.createDriver(driverConfig);
133
+ // Create driver using the injected CreateDriver function
134
+ const driver = this.createDriver(driverConfig);
106
135
 
107
136
  // Initialize driver
108
137
  await driver.initialize();
109
138
 
139
+ // Create AgentEngine with custom Source and Presenter
140
+ // Source: no-op (Runtime pushes events directly via handleStreamEvent)
141
+ // Presenter: emits message/state/turn events to EventBus + persists messages
142
+ const noopSource: AgentSource = {
143
+ name: "RuntimeSource",
144
+ connect: () => {},
145
+ disconnect: () => {},
146
+ };
147
+
148
+ const sessionId = imageRecord.sessionId;
149
+ const sessionRepository = this.platform.sessionRepository;
150
+ const eventBus = this.platform.eventBus;
151
+
152
+ const runtimePresenter: AgentPresenter = {
153
+ name: "RuntimePresenter",
154
+ present: (_agentId: string, output: AgentOutput) => {
155
+ const category = categorizeAgentOutput(output.type);
156
+
157
+ // Skip stream events — already emitted by handleDriverEvent
158
+ if (category === "stream") return;
159
+
160
+ // Emit state/message/turn events to EventBus
161
+ eventBus.emit({
162
+ type: output.type,
163
+ timestamp: output.timestamp,
164
+ source: "agent",
165
+ category,
166
+ intent: "notification",
167
+ data: output.data,
168
+ context: {
169
+ agentId,
170
+ imageId,
171
+ containerId: imageRecord.containerId,
172
+ sessionId,
173
+ },
174
+ } as BusEvent);
175
+
176
+ // Persist message events to SessionRepository
177
+ if (category === "message" && output.type !== "user_message") {
178
+ const message = output.data as Message;
179
+ sessionRepository.addMessage(sessionId, message).catch((err) => {
180
+ logger.error("Failed to persist message", { type: output.type, error: err });
181
+ });
182
+ }
183
+ },
184
+ };
185
+
186
+ const engine = createAgentEngine({
187
+ agentId,
188
+ bus: this.platform.eventBus,
189
+ source: noopSource,
190
+ presenter: runtimePresenter,
191
+ });
192
+
110
193
  // Create runtime agent
111
194
  const agent: RuntimeAgent = {
112
195
  agentId,
@@ -118,18 +201,19 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
118
201
  createdAt: Date.now(),
119
202
  };
120
203
 
121
- // Store agent state with driver
204
+ // Store agent state with driver and engine
122
205
  const state: AgentState = {
123
206
  agent,
124
207
  lifecycle: "running",
125
208
  subscriptions: new Set(),
126
209
  driver,
210
+ engine,
127
211
  isReceiving: false,
128
212
  };
129
213
  this.agents.set(agentId, state);
130
214
 
131
215
  // Emit agent_created event
132
- this.provider.eventBus.emit({
216
+ this.platform.eventBus.emit({
133
217
  type: "agent_created",
134
218
  timestamp: Date.now(),
135
219
  source: "runtime",
@@ -185,7 +269,7 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
185
269
  state.lifecycle = "stopped";
186
270
 
187
271
  // Emit agent_stopped event
188
- this.provider.eventBus.emit({
272
+ this.platform.eventBus.emit({
189
273
  type: "agent_stopped",
190
274
  timestamp: Date.now(),
191
275
  source: "runtime",
@@ -216,7 +300,7 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
216
300
  state.lifecycle = "running";
217
301
 
218
302
  // Emit agent_resumed event
219
- this.provider.eventBus.emit({
303
+ this.platform.eventBus.emit({
220
304
  type: "agent_resumed",
221
305
  timestamp: Date.now(),
222
306
  source: "runtime",
@@ -240,8 +324,9 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
240
324
  throw new Error(`Agent not found: ${agentId}`);
241
325
  }
242
326
 
243
- // Dispose driver (new interface, no disconnect needed)
327
+ // Dispose driver and engine
244
328
  await state.driver.dispose();
329
+ await state.engine.destroy();
245
330
 
246
331
  // Cleanup subscriptions
247
332
  for (const unsub of state.subscriptions) {
@@ -252,7 +337,7 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
252
337
  state.lifecycle = "destroyed";
253
338
 
254
339
  // Emit agent_destroyed event
255
- this.provider.eventBus.emit({
340
+ this.platform.eventBus.emit({
256
341
  type: "agent_destroyed",
257
342
  timestamp: Date.now(),
258
343
  source: "runtime",
@@ -305,7 +390,7 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
305
390
  };
306
391
 
307
392
  // Persist to session
308
- await this.provider.sessionRepository.addMessage(state.agent.sessionId, userMessage);
393
+ await this.platform.sessionRepository.addMessage(state.agent.sessionId, userMessage);
309
394
 
310
395
  // Emit user_message event (for external subscribers)
311
396
  this.emitEvent(state, "user_message", userMessage, actualRequestId);
@@ -314,7 +399,11 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
314
399
  agentId,
315
400
  requestId: actualRequestId,
316
401
  contentPreview:
317
- typeof content === "string" ? content.substring(0, 50) : `[${content.length} parts]`,
402
+ typeof content === "string"
403
+ ? content.substring(0, 50)
404
+ : Array.isArray(content)
405
+ ? `[${content.length} parts]`
406
+ : `[${typeof content}]`,
318
407
  });
319
408
 
320
409
  // Mark as receiving
@@ -353,12 +442,7 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
353
442
  state.driver.interrupt();
354
443
 
355
444
  // Emit interrupt event (for external subscribers)
356
- this.emitEvent(
357
- state,
358
- "interrupt",
359
- { agentId },
360
- requestId ?? this.generateRequestId()
361
- );
445
+ this.emitEvent(state, "interrupt", { agentId }, requestId ?? this.generateRequestId());
362
446
 
363
447
  logger.debug("Interrupt sent", { agentId, requestId });
364
448
  }
@@ -371,7 +455,7 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
371
455
  throw new Error(`Agent not found: ${agentId}`);
372
456
  }
373
457
 
374
- const unsub = this.provider.eventBus.onAny((event) => {
458
+ const unsub = this.platform.eventBus.onAny((event) => {
375
459
  const context = (event as BusEvent & { context?: { agentId?: string } }).context;
376
460
  if (context?.agentId === agentId) {
377
461
  handler(event);
@@ -389,7 +473,7 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
389
473
  }
390
474
 
391
475
  subscribeAll(handler: AgentEventHandler): Subscription {
392
- const unsub = this.provider.eventBus.onAny(handler);
476
+ const unsub = this.platform.eventBus.onAny(handler);
393
477
  this.globalSubscriptions.add(unsub);
394
478
 
395
479
  return {
@@ -428,25 +512,21 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
428
512
  /**
429
513
  * Handle a single DriverStreamEvent
430
514
  */
431
- private handleDriverEvent(
432
- state: AgentState,
433
- event: DriverStreamEvent,
434
- requestId: string
435
- ): void {
436
- // Map DriverStreamEvent to BusEvent and emit
515
+ private handleDriverEvent(state: AgentState, event: DriverStreamEvent, requestId: string): void {
516
+ // 1. Emit raw stream event to EventBus (for Presentation and other subscribers)
437
517
  this.emitEvent(state, event.type, event.data, requestId);
518
+
519
+ // 2. Push to AgentEngine for MealyMachine processing
520
+ // Engine produces message/state/turn events via Presenter
521
+ const streamEvent = toStreamEvent(event);
522
+ state.engine.handleStreamEvent(streamEvent);
438
523
  }
439
524
 
440
525
  /**
441
526
  * Emit an event to the EventBus
442
527
  */
443
- private emitEvent(
444
- state: AgentState,
445
- type: string,
446
- data: unknown,
447
- requestId: string
448
- ): void {
449
- this.provider.eventBus.emit({
528
+ private emitEvent(state: AgentState, type: string, data: unknown, requestId: string): void {
529
+ this.platform.eventBus.emit({
450
530
  type,
451
531
  timestamp: Date.now(),
452
532
  source: "runtime",
@@ -493,9 +573,56 @@ export class AgentXRuntimeImpl implements AgentXRuntime {
493
573
  }
494
574
  }
495
575
 
576
+ // ============================================================================
577
+ // Helpers
578
+ // ============================================================================
579
+
580
+ /**
581
+ * Convert DriverStreamEvent to agent-layer StreamEvent.
582
+ * Data structures are identical; only "error" type needs renaming.
583
+ */
584
+ function toStreamEvent(event: DriverStreamEvent): StreamEvent {
585
+ const type = event.type === "error" ? "error_received" : event.type;
586
+ return { type, data: event.data, timestamp: Date.now() } as StreamEvent;
587
+ }
588
+
589
+ /**
590
+ * Categorize AgentOutput type for EventBus emission.
591
+ */
592
+ function categorizeAgentOutput(type: string): string {
593
+ // Stream layer — already emitted by handleDriverEvent
594
+ const streamTypes = [
595
+ "message_start",
596
+ "message_delta",
597
+ "message_stop",
598
+ "text_delta",
599
+ "tool_use_start",
600
+ "input_json_delta",
601
+ "tool_use_stop",
602
+ "tool_result",
603
+ "error_received",
604
+ ];
605
+ if (streamTypes.includes(type)) return "stream";
606
+
607
+ // Message layer
608
+ if (type.endsWith("_message")) return "message";
609
+
610
+ // Turn layer
611
+ if (type.startsWith("turn_")) return "turn";
612
+
613
+ // State layer (default)
614
+ return "state";
615
+ }
616
+
496
617
  /**
497
618
  * Create an AgentXRuntime instance
619
+ *
620
+ * @param platform - AgentXPlatform with repositories and event bus
621
+ * @param createDriver - Factory function for creating Driver instances per Agent
498
622
  */
499
- export function createAgentXRuntime(provider: AgentXProvider): AgentXRuntime {
500
- return new AgentXRuntimeImpl(provider);
623
+ export function createAgentXRuntime(
624
+ platform: AgentXPlatform,
625
+ createDriver: CreateDriver
626
+ ): AgentXRuntime {
627
+ return new AgentXRuntimeImpl(platform, createDriver);
501
628
  }