@agentxjs/core 1.9.1-dev

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 (77) hide show
  1. package/package.json +31 -0
  2. package/src/agent/AgentStateMachine.ts +151 -0
  3. package/src/agent/README.md +296 -0
  4. package/src/agent/__tests__/AgentStateMachine.test.ts +346 -0
  5. package/src/agent/__tests__/createAgent.test.ts +728 -0
  6. package/src/agent/__tests__/engine/internal/messageAssemblerProcessor.test.ts +567 -0
  7. package/src/agent/__tests__/engine/internal/stateEventProcessor.test.ts +315 -0
  8. package/src/agent/__tests__/engine/internal/turnTrackerProcessor.test.ts +340 -0
  9. package/src/agent/__tests__/engine/mealy/Mealy.test.ts +370 -0
  10. package/src/agent/__tests__/engine/mealy/Store.test.ts +123 -0
  11. package/src/agent/__tests__/engine/mealy/combinators.test.ts +322 -0
  12. package/src/agent/createAgent.ts +467 -0
  13. package/src/agent/engine/AgentProcessor.ts +106 -0
  14. package/src/agent/engine/MealyMachine.ts +184 -0
  15. package/src/agent/engine/internal/index.ts +35 -0
  16. package/src/agent/engine/internal/messageAssemblerProcessor.ts +550 -0
  17. package/src/agent/engine/internal/stateEventProcessor.ts +313 -0
  18. package/src/agent/engine/internal/turnTrackerProcessor.ts +239 -0
  19. package/src/agent/engine/mealy/Mealy.ts +308 -0
  20. package/src/agent/engine/mealy/Processor.ts +70 -0
  21. package/src/agent/engine/mealy/Sink.ts +56 -0
  22. package/src/agent/engine/mealy/Source.ts +51 -0
  23. package/src/agent/engine/mealy/Store.ts +98 -0
  24. package/src/agent/engine/mealy/combinators.ts +176 -0
  25. package/src/agent/engine/mealy/index.ts +45 -0
  26. package/src/agent/index.ts +106 -0
  27. package/src/agent/types/engine.ts +395 -0
  28. package/src/agent/types/event.ts +478 -0
  29. package/src/agent/types/index.ts +197 -0
  30. package/src/agent/types/message.ts +387 -0
  31. package/src/common/index.ts +8 -0
  32. package/src/common/logger/ConsoleLogger.ts +137 -0
  33. package/src/common/logger/LoggerFactoryImpl.ts +123 -0
  34. package/src/common/logger/index.ts +26 -0
  35. package/src/common/logger/types.ts +98 -0
  36. package/src/container/Container.ts +185 -0
  37. package/src/container/index.ts +44 -0
  38. package/src/container/types.ts +71 -0
  39. package/src/driver/index.ts +42 -0
  40. package/src/driver/types.ts +363 -0
  41. package/src/event/EventBus.ts +260 -0
  42. package/src/event/README.md +237 -0
  43. package/src/event/__tests__/EventBus.test.ts +251 -0
  44. package/src/event/index.ts +46 -0
  45. package/src/event/types/agent.ts +512 -0
  46. package/src/event/types/base.ts +241 -0
  47. package/src/event/types/bus.ts +429 -0
  48. package/src/event/types/command.ts +749 -0
  49. package/src/event/types/container.ts +471 -0
  50. package/src/event/types/driver.ts +452 -0
  51. package/src/event/types/index.ts +26 -0
  52. package/src/event/types/session.ts +314 -0
  53. package/src/image/Image.ts +203 -0
  54. package/src/image/index.ts +36 -0
  55. package/src/image/types.ts +77 -0
  56. package/src/index.ts +20 -0
  57. package/src/mq/OffsetGenerator.ts +48 -0
  58. package/src/mq/README.md +166 -0
  59. package/src/mq/__tests__/OffsetGenerator.test.ts +121 -0
  60. package/src/mq/index.ts +18 -0
  61. package/src/mq/types.ts +172 -0
  62. package/src/network/RpcClient.ts +455 -0
  63. package/src/network/index.ts +76 -0
  64. package/src/network/jsonrpc.ts +336 -0
  65. package/src/network/protocol.ts +90 -0
  66. package/src/network/types.ts +284 -0
  67. package/src/persistence/index.ts +27 -0
  68. package/src/persistence/types.ts +226 -0
  69. package/src/runtime/AgentXRuntime.ts +501 -0
  70. package/src/runtime/index.ts +56 -0
  71. package/src/runtime/types.ts +236 -0
  72. package/src/session/Session.ts +71 -0
  73. package/src/session/index.ts +25 -0
  74. package/src/session/types.ts +77 -0
  75. package/src/workspace/index.ts +27 -0
  76. package/src/workspace/types.ts +131 -0
  77. package/tsconfig.json +10 -0
@@ -0,0 +1,501 @@
1
+ /**
2
+ * AgentXRuntimeImpl - Runtime integration implementation
3
+ *
4
+ * Integrates all components to provide agent lifecycle management.
5
+ * Uses Provider dependencies to coordinate Session, Image, Container, etc.
6
+ *
7
+ * New Design:
8
+ * - Driver.receive() returns AsyncIterable<DriverStreamEvent>
9
+ * - Runtime processes events and emits to EventBus
10
+ * - No more EventBus-based communication with Driver
11
+ */
12
+
13
+ import { createLogger } from "commonxjs/logger";
14
+ import type {
15
+ AgentXProvider,
16
+ AgentXRuntime,
17
+ RuntimeAgent,
18
+ CreateAgentOptions,
19
+ AgentEventHandler,
20
+ Subscription,
21
+ AgentLifecycle,
22
+ } from "./types";
23
+ import type { UserContentPart, UserMessage } from "../agent/types";
24
+ import type { BusEvent } from "../event/types";
25
+ import type { Driver, DriverConfig, DriverStreamEvent } from "../driver/types";
26
+
27
+ const logger = createLogger("runtime/AgentXRuntime");
28
+
29
+ /**
30
+ * Internal agent state
31
+ */
32
+ interface AgentState {
33
+ agent: RuntimeAgent;
34
+ lifecycle: AgentLifecycle;
35
+ subscriptions: Set<() => void>;
36
+ driver: Driver;
37
+ /** Flag to track if a receive operation is in progress */
38
+ isReceiving: boolean;
39
+ }
40
+
41
+ /**
42
+ * AgentXRuntimeImpl - Runtime implementation
43
+ */
44
+ export class AgentXRuntimeImpl implements AgentXRuntime {
45
+ readonly provider: AgentXProvider;
46
+
47
+ private agents = new Map<string, AgentState>();
48
+ private globalSubscriptions = new Set<() => void>();
49
+ private isShutdown = false;
50
+
51
+ constructor(provider: AgentXProvider) {
52
+ this.provider = provider;
53
+ logger.info("AgentXRuntime initialized");
54
+ }
55
+
56
+ // ==================== Agent Lifecycle ====================
57
+
58
+ async createAgent(options: CreateAgentOptions): Promise<RuntimeAgent> {
59
+ if (this.isShutdown) {
60
+ throw new Error("Runtime is shutdown");
61
+ }
62
+
63
+ const { imageId } = options;
64
+
65
+ // Load image
66
+ const imageRecord = await this.provider.imageRepository.findImageById(imageId);
67
+ if (!imageRecord) {
68
+ throw new Error(`Image not found: ${imageId}`);
69
+ }
70
+
71
+ // Generate agent ID
72
+ const agentId = options.agentId ?? this.generateAgentId();
73
+
74
+ // Ensure container exists
75
+ const containerExists = await this.provider.containerRepository.containerExists(
76
+ imageRecord.containerId
77
+ );
78
+ if (!containerExists) {
79
+ throw new Error(`Container not found: ${imageRecord.containerId}`);
80
+ }
81
+
82
+ // Create workspace
83
+ const workspace = await this.provider.workspaceProvider.create({
84
+ containerId: imageRecord.containerId,
85
+ imageId,
86
+ });
87
+ await workspace.initialize();
88
+
89
+ // Create driver config
90
+ const driverConfig: DriverConfig = {
91
+ apiKey: process.env.ANTHROPIC_API_KEY ?? "",
92
+ baseUrl: process.env.ANTHROPIC_BASE_URL,
93
+ agentId,
94
+ systemPrompt: imageRecord.systemPrompt,
95
+ cwd: workspace.path,
96
+ 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 });
101
+ },
102
+ };
103
+
104
+ // Create driver using the new CreateDriver function
105
+ const driver = this.provider.createDriver(driverConfig);
106
+
107
+ // Initialize driver
108
+ await driver.initialize();
109
+
110
+ // Create runtime agent
111
+ const agent: RuntimeAgent = {
112
+ agentId,
113
+ imageId,
114
+ containerId: imageRecord.containerId,
115
+ sessionId: imageRecord.sessionId,
116
+ name: imageRecord.name,
117
+ lifecycle: "running",
118
+ createdAt: Date.now(),
119
+ };
120
+
121
+ // Store agent state with driver
122
+ const state: AgentState = {
123
+ agent,
124
+ lifecycle: "running",
125
+ subscriptions: new Set(),
126
+ driver,
127
+ isReceiving: false,
128
+ };
129
+ this.agents.set(agentId, state);
130
+
131
+ // Emit agent_created event
132
+ this.provider.eventBus.emit({
133
+ type: "agent_created",
134
+ timestamp: Date.now(),
135
+ source: "runtime",
136
+ category: "lifecycle",
137
+ intent: "notification",
138
+ data: {
139
+ agentId,
140
+ imageId,
141
+ containerId: imageRecord.containerId,
142
+ },
143
+ context: {
144
+ agentId,
145
+ imageId,
146
+ containerId: imageRecord.containerId,
147
+ sessionId: imageRecord.sessionId,
148
+ },
149
+ } as BusEvent);
150
+
151
+ logger.info("Agent created", {
152
+ agentId,
153
+ imageId,
154
+ containerId: imageRecord.containerId,
155
+ });
156
+
157
+ return agent;
158
+ }
159
+
160
+ getAgent(agentId: string): RuntimeAgent | undefined {
161
+ const state = this.agents.get(agentId);
162
+ return state?.agent;
163
+ }
164
+
165
+ getAgents(): RuntimeAgent[] {
166
+ return Array.from(this.agents.values()).map((s) => s.agent);
167
+ }
168
+
169
+ getAgentsByContainer(containerId: string): RuntimeAgent[] {
170
+ return Array.from(this.agents.values())
171
+ .filter((s) => s.agent.containerId === containerId)
172
+ .map((s) => s.agent);
173
+ }
174
+
175
+ async stopAgent(agentId: string): Promise<void> {
176
+ const state = this.agents.get(agentId);
177
+ if (!state) {
178
+ throw new Error(`Agent not found: ${agentId}`);
179
+ }
180
+
181
+ if (state.lifecycle === "destroyed") {
182
+ throw new Error(`Agent already destroyed: ${agentId}`);
183
+ }
184
+
185
+ state.lifecycle = "stopped";
186
+
187
+ // Emit agent_stopped event
188
+ this.provider.eventBus.emit({
189
+ type: "agent_stopped",
190
+ timestamp: Date.now(),
191
+ source: "runtime",
192
+ category: "lifecycle",
193
+ intent: "notification",
194
+ data: { agentId },
195
+ context: {
196
+ agentId,
197
+ imageId: state.agent.imageId,
198
+ containerId: state.agent.containerId,
199
+ sessionId: state.agent.sessionId,
200
+ },
201
+ } as BusEvent);
202
+
203
+ logger.info("Agent stopped", { agentId });
204
+ }
205
+
206
+ async resumeAgent(agentId: string): Promise<void> {
207
+ const state = this.agents.get(agentId);
208
+ if (!state) {
209
+ throw new Error(`Agent not found: ${agentId}`);
210
+ }
211
+
212
+ if (state.lifecycle === "destroyed") {
213
+ throw new Error(`Cannot resume destroyed agent: ${agentId}`);
214
+ }
215
+
216
+ state.lifecycle = "running";
217
+
218
+ // Emit agent_resumed event
219
+ this.provider.eventBus.emit({
220
+ type: "agent_resumed",
221
+ timestamp: Date.now(),
222
+ source: "runtime",
223
+ category: "lifecycle",
224
+ intent: "notification",
225
+ data: { agentId },
226
+ context: {
227
+ agentId,
228
+ imageId: state.agent.imageId,
229
+ containerId: state.agent.containerId,
230
+ sessionId: state.agent.sessionId,
231
+ },
232
+ } as BusEvent);
233
+
234
+ logger.info("Agent resumed", { agentId });
235
+ }
236
+
237
+ async destroyAgent(agentId: string): Promise<void> {
238
+ const state = this.agents.get(agentId);
239
+ if (!state) {
240
+ throw new Error(`Agent not found: ${agentId}`);
241
+ }
242
+
243
+ // Dispose driver (new interface, no disconnect needed)
244
+ await state.driver.dispose();
245
+
246
+ // Cleanup subscriptions
247
+ for (const unsub of state.subscriptions) {
248
+ unsub();
249
+ }
250
+ state.subscriptions.clear();
251
+
252
+ state.lifecycle = "destroyed";
253
+
254
+ // Emit agent_destroyed event
255
+ this.provider.eventBus.emit({
256
+ type: "agent_destroyed",
257
+ timestamp: Date.now(),
258
+ source: "runtime",
259
+ category: "lifecycle",
260
+ intent: "notification",
261
+ data: { agentId },
262
+ context: {
263
+ agentId,
264
+ imageId: state.agent.imageId,
265
+ containerId: state.agent.containerId,
266
+ sessionId: state.agent.sessionId,
267
+ },
268
+ } as BusEvent);
269
+
270
+ // Remove from map
271
+ this.agents.delete(agentId);
272
+
273
+ logger.info("Agent destroyed", { agentId });
274
+ }
275
+
276
+ // ==================== Message Handling ====================
277
+
278
+ async receive(
279
+ agentId: string,
280
+ content: string | UserContentPart[],
281
+ requestId?: string
282
+ ): Promise<void> {
283
+ const state = this.agents.get(agentId);
284
+ if (!state) {
285
+ throw new Error(`Agent not found: ${agentId}`);
286
+ }
287
+
288
+ if (state.lifecycle !== "running") {
289
+ throw new Error(`Cannot send message to ${state.lifecycle} agent: ${agentId}`);
290
+ }
291
+
292
+ if (state.isReceiving) {
293
+ throw new Error(`Agent ${agentId} is already processing a message`);
294
+ }
295
+
296
+ const actualRequestId = requestId ?? this.generateRequestId();
297
+
298
+ // Build user message
299
+ const userMessage: UserMessage = {
300
+ id: this.generateMessageId(),
301
+ role: "user",
302
+ subtype: "user",
303
+ content,
304
+ timestamp: Date.now(),
305
+ };
306
+
307
+ // Persist to session
308
+ await this.provider.sessionRepository.addMessage(state.agent.sessionId, userMessage);
309
+
310
+ // Emit user_message event (for external subscribers)
311
+ this.emitEvent(state, "user_message", userMessage, actualRequestId);
312
+
313
+ logger.debug("User message sent", {
314
+ agentId,
315
+ requestId: actualRequestId,
316
+ contentPreview:
317
+ typeof content === "string" ? content.substring(0, 50) : `[${content.length} parts]`,
318
+ });
319
+
320
+ // Mark as receiving
321
+ state.isReceiving = true;
322
+
323
+ try {
324
+ // Call driver.receive() and process the AsyncIterable
325
+ for await (const event of state.driver.receive(userMessage)) {
326
+ // Convert DriverStreamEvent to BusEvent and emit
327
+ this.handleDriverEvent(state, event, actualRequestId);
328
+ }
329
+ } catch (error) {
330
+ // Emit error event
331
+ this.emitEvent(
332
+ state,
333
+ "error_received",
334
+ {
335
+ message: error instanceof Error ? error.message : String(error),
336
+ errorCode: "runtime_error",
337
+ },
338
+ actualRequestId
339
+ );
340
+ throw error;
341
+ } finally {
342
+ state.isReceiving = false;
343
+ }
344
+ }
345
+
346
+ interrupt(agentId: string, requestId?: string): void {
347
+ const state = this.agents.get(agentId);
348
+ if (!state) {
349
+ throw new Error(`Agent not found: ${agentId}`);
350
+ }
351
+
352
+ // Call driver.interrupt() directly
353
+ state.driver.interrupt();
354
+
355
+ // Emit interrupt event (for external subscribers)
356
+ this.emitEvent(
357
+ state,
358
+ "interrupt",
359
+ { agentId },
360
+ requestId ?? this.generateRequestId()
361
+ );
362
+
363
+ logger.debug("Interrupt sent", { agentId, requestId });
364
+ }
365
+
366
+ // ==================== Event Subscription ====================
367
+
368
+ subscribe(agentId: string, handler: AgentEventHandler): Subscription {
369
+ const state = this.agents.get(agentId);
370
+ if (!state) {
371
+ throw new Error(`Agent not found: ${agentId}`);
372
+ }
373
+
374
+ const unsub = this.provider.eventBus.onAny((event) => {
375
+ const context = (event as BusEvent & { context?: { agentId?: string } }).context;
376
+ if (context?.agentId === agentId) {
377
+ handler(event);
378
+ }
379
+ });
380
+
381
+ state.subscriptions.add(unsub);
382
+
383
+ return {
384
+ unsubscribe: () => {
385
+ unsub();
386
+ state.subscriptions.delete(unsub);
387
+ },
388
+ };
389
+ }
390
+
391
+ subscribeAll(handler: AgentEventHandler): Subscription {
392
+ const unsub = this.provider.eventBus.onAny(handler);
393
+ this.globalSubscriptions.add(unsub);
394
+
395
+ return {
396
+ unsubscribe: () => {
397
+ unsub();
398
+ this.globalSubscriptions.delete(unsub);
399
+ },
400
+ };
401
+ }
402
+
403
+ // ==================== Cleanup ====================
404
+
405
+ async shutdown(): Promise<void> {
406
+ if (this.isShutdown) return;
407
+
408
+ logger.info("Shutting down AgentXRuntime...");
409
+
410
+ // Destroy all agents
411
+ const agentIds = Array.from(this.agents.keys());
412
+ for (const agentId of agentIds) {
413
+ await this.destroyAgent(agentId);
414
+ }
415
+
416
+ // Cleanup global subscriptions
417
+ for (const unsub of this.globalSubscriptions) {
418
+ unsub();
419
+ }
420
+ this.globalSubscriptions.clear();
421
+
422
+ this.isShutdown = true;
423
+ logger.info("AgentXRuntime shutdown complete");
424
+ }
425
+
426
+ // ==================== Private Helpers ====================
427
+
428
+ /**
429
+ * Handle a single DriverStreamEvent
430
+ */
431
+ private handleDriverEvent(
432
+ state: AgentState,
433
+ event: DriverStreamEvent,
434
+ requestId: string
435
+ ): void {
436
+ // Map DriverStreamEvent to BusEvent and emit
437
+ this.emitEvent(state, event.type, event.data, requestId);
438
+ }
439
+
440
+ /**
441
+ * Emit an event to the EventBus
442
+ */
443
+ private emitEvent(
444
+ state: AgentState,
445
+ type: string,
446
+ data: unknown,
447
+ requestId: string
448
+ ): void {
449
+ this.provider.eventBus.emit({
450
+ type,
451
+ timestamp: Date.now(),
452
+ source: "runtime",
453
+ category: this.categorizeEvent(type),
454
+ intent: "notification",
455
+ requestId,
456
+ data,
457
+ context: {
458
+ agentId: state.agent.agentId,
459
+ imageId: state.agent.imageId,
460
+ containerId: state.agent.containerId,
461
+ sessionId: state.agent.sessionId,
462
+ },
463
+ } as BusEvent);
464
+ }
465
+
466
+ /**
467
+ * Categorize event type
468
+ */
469
+ private categorizeEvent(type: string): string {
470
+ if (type.includes("message")) return "message";
471
+ if (type.includes("tool")) return "tool";
472
+ if (type.includes("error") || type.includes("interrupted")) return "error";
473
+ if (type.includes("delta")) return "stream";
474
+ return "stream";
475
+ }
476
+
477
+ private generateAgentId(): string {
478
+ const timestamp = Date.now().toString(36);
479
+ const random = Math.random().toString(36).substring(2, 8);
480
+ return `agent_${timestamp}_${random}`;
481
+ }
482
+
483
+ private generateRequestId(): string {
484
+ const timestamp = Date.now().toString(36);
485
+ const random = Math.random().toString(36).substring(2, 8);
486
+ return `req_${timestamp}_${random}`;
487
+ }
488
+
489
+ private generateMessageId(): string {
490
+ const timestamp = Date.now().toString(36);
491
+ const random = Math.random().toString(36).substring(2, 8);
492
+ return `msg_${timestamp}_${random}`;
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Create an AgentXRuntime instance
498
+ */
499
+ export function createAgentXRuntime(provider: AgentXProvider): AgentXRuntime {
500
+ return new AgentXRuntimeImpl(provider);
501
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Runtime Module
3
+ *
4
+ * AgentXProvider and AgentXRuntime interfaces.
5
+ * Platform packages provide concrete implementations.
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * import type {
10
+ * AgentXProvider,
11
+ * AgentXRuntime,
12
+ * RuntimeAgent,
13
+ * } from "@agentxjs/core/runtime";
14
+ *
15
+ * // Platform provides implementation
16
+ * const provider: AgentXProvider = {
17
+ * containerRepository,
18
+ * imageRepository,
19
+ * sessionRepository,
20
+ * workspaceProvider,
21
+ * driver,
22
+ * eventBus,
23
+ * };
24
+ *
25
+ * const runtime: AgentXRuntime = createRuntime({ provider });
26
+ *
27
+ * // Create agent from image
28
+ * const agent = await runtime.createAgent({ imageId: "img_xxx" });
29
+ *
30
+ * // Send message
31
+ * await runtime.receive(agent.agentId, "Hello!");
32
+ *
33
+ * // Subscribe to events
34
+ * const sub = runtime.subscribe(agent.agentId, (event) => {
35
+ * console.log(event.type, event.data);
36
+ * });
37
+ *
38
+ * // Cleanup
39
+ * sub.unsubscribe();
40
+ * await runtime.destroyAgent(agent.agentId);
41
+ * ```
42
+ */
43
+
44
+ export type {
45
+ AgentLifecycle,
46
+ RuntimeAgent,
47
+ AgentXProvider,
48
+ CreateAgentOptions,
49
+ AgentEventHandler,
50
+ Subscription,
51
+ AgentXRuntime,
52
+ AgentXRuntimeConfig,
53
+ CreateAgentXRuntime,
54
+ } from "./types";
55
+
56
+ export { AgentXRuntimeImpl, createAgentXRuntime } from "./AgentXRuntime";