@agentxjs/runtime 0.1.1
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/LICENSE +21 -0
- package/README.md +598 -0
- package/dist/index.cjs +3214 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +131 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +3176 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
createPersistence: () => createPersistence,
|
|
34
|
+
createRuntime: () => createRuntime
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/internal/SystemBusImpl.ts
|
|
39
|
+
var import_rxjs = require("rxjs");
|
|
40
|
+
var import_common = require("@agentxjs/common");
|
|
41
|
+
var logger = (0, import_common.createLogger)("runtime/SystemBusImpl");
|
|
42
|
+
var SystemBusImpl = class {
|
|
43
|
+
subject = new import_rxjs.Subject();
|
|
44
|
+
subscriptions = [];
|
|
45
|
+
nextId = 0;
|
|
46
|
+
isDestroyed = false;
|
|
47
|
+
// Cached restricted views
|
|
48
|
+
producerView = null;
|
|
49
|
+
consumerView = null;
|
|
50
|
+
constructor() {
|
|
51
|
+
this.subject.subscribe((event) => {
|
|
52
|
+
this.dispatch(event);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
emit(event) {
|
|
56
|
+
if (this.isDestroyed) return;
|
|
57
|
+
this.subject.next(event);
|
|
58
|
+
}
|
|
59
|
+
emitBatch(events) {
|
|
60
|
+
for (const event of events) {
|
|
61
|
+
this.emit(event);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
on(typeOrTypes, handler, options) {
|
|
65
|
+
if (this.isDestroyed) return () => {
|
|
66
|
+
};
|
|
67
|
+
const subscription = {
|
|
68
|
+
id: this.nextId++,
|
|
69
|
+
type: typeOrTypes,
|
|
70
|
+
handler,
|
|
71
|
+
filter: options?.filter,
|
|
72
|
+
priority: options?.priority ?? 0,
|
|
73
|
+
once: options?.once ?? false
|
|
74
|
+
};
|
|
75
|
+
this.subscriptions.push(subscription);
|
|
76
|
+
this.sortByPriority();
|
|
77
|
+
return () => this.removeSubscription(subscription.id);
|
|
78
|
+
}
|
|
79
|
+
onAny(handler, options) {
|
|
80
|
+
if (this.isDestroyed) return () => {
|
|
81
|
+
};
|
|
82
|
+
const subscription = {
|
|
83
|
+
id: this.nextId++,
|
|
84
|
+
type: "*",
|
|
85
|
+
handler,
|
|
86
|
+
filter: options?.filter,
|
|
87
|
+
priority: options?.priority ?? 0,
|
|
88
|
+
once: options?.once ?? false
|
|
89
|
+
};
|
|
90
|
+
this.subscriptions.push(subscription);
|
|
91
|
+
this.sortByPriority();
|
|
92
|
+
return () => this.removeSubscription(subscription.id);
|
|
93
|
+
}
|
|
94
|
+
once(type, handler) {
|
|
95
|
+
return this.on(type, handler, { once: true });
|
|
96
|
+
}
|
|
97
|
+
onCommand(type, handler) {
|
|
98
|
+
return this.on(type, handler);
|
|
99
|
+
}
|
|
100
|
+
emitCommand(type, data) {
|
|
101
|
+
this.emit({
|
|
102
|
+
type,
|
|
103
|
+
timestamp: Date.now(),
|
|
104
|
+
data,
|
|
105
|
+
source: "command",
|
|
106
|
+
category: type.endsWith("_response") ? "response" : "request",
|
|
107
|
+
intent: type.endsWith("_response") ? "result" : "request"
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
request(type, data, timeout = 3e4) {
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
const requestId = `req_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
113
|
+
const responseType = type.replace("_request", "_response");
|
|
114
|
+
const timer = setTimeout(() => {
|
|
115
|
+
unsubscribe();
|
|
116
|
+
reject(new Error(`Request timeout: ${type}`));
|
|
117
|
+
}, timeout);
|
|
118
|
+
const unsubscribe = this.onCommand(responseType, (event) => {
|
|
119
|
+
if (event.data.requestId === requestId) {
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
unsubscribe();
|
|
122
|
+
resolve(event);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
this.emitCommand(type, { ...data, requestId });
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
destroy() {
|
|
129
|
+
if (this.isDestroyed) return;
|
|
130
|
+
this.isDestroyed = true;
|
|
131
|
+
this.subscriptions = [];
|
|
132
|
+
this.subject.complete();
|
|
133
|
+
}
|
|
134
|
+
dispatch(event) {
|
|
135
|
+
const toRemove = [];
|
|
136
|
+
for (const sub of this.subscriptions) {
|
|
137
|
+
if (!this.matchesType(sub.type, event.type)) continue;
|
|
138
|
+
if (sub.filter && !sub.filter(event)) continue;
|
|
139
|
+
try {
|
|
140
|
+
sub.handler(event);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
logger.error("Event handler error", {
|
|
143
|
+
eventType: event.type,
|
|
144
|
+
subscriptionType: sub.type,
|
|
145
|
+
error: err instanceof Error ? err.message : String(err),
|
|
146
|
+
stack: err instanceof Error ? err.stack : void 0
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (sub.once) {
|
|
150
|
+
toRemove.push(sub.id);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const id of toRemove) {
|
|
154
|
+
this.removeSubscription(id);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
matchesType(subscriptionType, eventType) {
|
|
158
|
+
if (subscriptionType === "*") return true;
|
|
159
|
+
if (Array.isArray(subscriptionType)) return subscriptionType.includes(eventType);
|
|
160
|
+
return subscriptionType === eventType;
|
|
161
|
+
}
|
|
162
|
+
sortByPriority() {
|
|
163
|
+
this.subscriptions.sort((a, b) => b.priority - a.priority);
|
|
164
|
+
}
|
|
165
|
+
removeSubscription(id) {
|
|
166
|
+
this.subscriptions = this.subscriptions.filter((s) => s.id !== id);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get a read-only consumer view (only subscribe methods)
|
|
170
|
+
*/
|
|
171
|
+
asConsumer() {
|
|
172
|
+
if (!this.consumerView) {
|
|
173
|
+
this.consumerView = {
|
|
174
|
+
on: this.on.bind(this),
|
|
175
|
+
onAny: this.onAny.bind(this),
|
|
176
|
+
once: this.once.bind(this),
|
|
177
|
+
onCommand: this.onCommand.bind(this),
|
|
178
|
+
request: this.request.bind(this)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return this.consumerView;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get a write-only producer view (only emit methods)
|
|
185
|
+
*/
|
|
186
|
+
asProducer() {
|
|
187
|
+
if (!this.producerView) {
|
|
188
|
+
this.producerView = {
|
|
189
|
+
emit: this.emit.bind(this),
|
|
190
|
+
emitBatch: this.emitBatch.bind(this),
|
|
191
|
+
emitCommand: this.emitCommand.bind(this)
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return this.producerView;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// src/internal/BusDriver.ts
|
|
199
|
+
var import_common2 = require("@agentxjs/common");
|
|
200
|
+
var logger2 = (0, import_common2.createLogger)("runtime/BusDriver");
|
|
201
|
+
var BusDriver = class {
|
|
202
|
+
name = "BusDriver";
|
|
203
|
+
description = "Driver that listens to SystemBus for DriveableEvents";
|
|
204
|
+
config;
|
|
205
|
+
unsubscribe;
|
|
206
|
+
constructor(consumer, config) {
|
|
207
|
+
this.config = config;
|
|
208
|
+
logger2.debug("BusDriver created, subscribing to bus", {
|
|
209
|
+
agentId: config.agentId
|
|
210
|
+
});
|
|
211
|
+
this.unsubscribe = consumer.onAny(((event) => {
|
|
212
|
+
this.handleEvent(event);
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Handle incoming event from bus
|
|
217
|
+
*/
|
|
218
|
+
handleEvent(event) {
|
|
219
|
+
if (!this.isDriveableEventForThisAgent(event)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const driveableEvent = event;
|
|
223
|
+
logger2.debug("BusDriver received DriveableEvent", {
|
|
224
|
+
type: driveableEvent.type,
|
|
225
|
+
agentId: this.config.agentId,
|
|
226
|
+
requestId: driveableEvent.requestId
|
|
227
|
+
});
|
|
228
|
+
const streamEvent = this.toStreamEvent(driveableEvent);
|
|
229
|
+
this.config.onStreamEvent(streamEvent);
|
|
230
|
+
if (driveableEvent.type === "message_stop") {
|
|
231
|
+
this.config.onStreamComplete?.("message_stop");
|
|
232
|
+
} else if (driveableEvent.type === "interrupted") {
|
|
233
|
+
this.config.onStreamComplete?.("interrupted");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Check if event is a DriveableEvent for this agent
|
|
238
|
+
*
|
|
239
|
+
* Must check:
|
|
240
|
+
* 1. source === "environment" (from Claude)
|
|
241
|
+
* 2. context.agentId === this.config.agentId (for this agent)
|
|
242
|
+
*/
|
|
243
|
+
isDriveableEventForThisAgent(event) {
|
|
244
|
+
const driveableTypes = [
|
|
245
|
+
"message_start",
|
|
246
|
+
"message_delta",
|
|
247
|
+
"message_stop",
|
|
248
|
+
"text_content_block_start",
|
|
249
|
+
"text_delta",
|
|
250
|
+
"text_content_block_stop",
|
|
251
|
+
"tool_use_content_block_start",
|
|
252
|
+
"input_json_delta",
|
|
253
|
+
"tool_use_content_block_stop",
|
|
254
|
+
"tool_call",
|
|
255
|
+
"tool_result",
|
|
256
|
+
"interrupted",
|
|
257
|
+
"error_received"
|
|
258
|
+
];
|
|
259
|
+
if (event === null || typeof event !== "object" || !("type" in event) || typeof event.type !== "string") {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
const e = event;
|
|
263
|
+
if (e.source !== "environment") {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (!driveableTypes.includes(e.type)) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (e.context?.agentId !== this.config.agentId) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Convert DriveableEvent to StreamEvent
|
|
276
|
+
*/
|
|
277
|
+
toStreamEvent(event) {
|
|
278
|
+
const { type, timestamp, data } = event;
|
|
279
|
+
switch (type) {
|
|
280
|
+
case "message_start": {
|
|
281
|
+
const d = data;
|
|
282
|
+
return {
|
|
283
|
+
type: "message_start",
|
|
284
|
+
timestamp,
|
|
285
|
+
data: {
|
|
286
|
+
messageId: d.message?.id ?? "",
|
|
287
|
+
model: d.message?.model ?? ""
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
case "message_stop": {
|
|
292
|
+
const d = data;
|
|
293
|
+
return {
|
|
294
|
+
type: "message_stop",
|
|
295
|
+
timestamp,
|
|
296
|
+
data: {
|
|
297
|
+
stopReason: d.stopReason
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
case "text_delta": {
|
|
302
|
+
const d = data;
|
|
303
|
+
return {
|
|
304
|
+
type: "text_delta",
|
|
305
|
+
timestamp,
|
|
306
|
+
data: { text: d.text }
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
case "tool_use_content_block_start": {
|
|
310
|
+
const d = data;
|
|
311
|
+
return {
|
|
312
|
+
type: "tool_use_start",
|
|
313
|
+
timestamp,
|
|
314
|
+
data: {
|
|
315
|
+
toolCallId: d.toolCallId ?? d.id ?? "",
|
|
316
|
+
toolName: d.toolName ?? d.name ?? ""
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
case "input_json_delta": {
|
|
321
|
+
const d = data;
|
|
322
|
+
return {
|
|
323
|
+
type: "input_json_delta",
|
|
324
|
+
timestamp,
|
|
325
|
+
data: { partialJson: d.partialJson }
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
case "tool_use_content_block_stop": {
|
|
329
|
+
const d = data;
|
|
330
|
+
return {
|
|
331
|
+
type: "tool_use_stop",
|
|
332
|
+
timestamp,
|
|
333
|
+
data: {
|
|
334
|
+
toolCallId: d.toolCallId ?? d.id ?? "",
|
|
335
|
+
toolName: d.toolName ?? d.name ?? "",
|
|
336
|
+
input: d.input ?? {}
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
case "tool_result": {
|
|
341
|
+
const d = data;
|
|
342
|
+
return {
|
|
343
|
+
type: "tool_result",
|
|
344
|
+
timestamp,
|
|
345
|
+
data: {
|
|
346
|
+
toolCallId: d.toolCallId ?? d.toolUseId ?? "",
|
|
347
|
+
result: d.result,
|
|
348
|
+
isError: d.isError
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
case "interrupted": {
|
|
353
|
+
return {
|
|
354
|
+
type: "message_stop",
|
|
355
|
+
timestamp,
|
|
356
|
+
data: { stopReason: "end_turn" }
|
|
357
|
+
// Use valid StopReason
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
case "error_received": {
|
|
361
|
+
const d = data;
|
|
362
|
+
return {
|
|
363
|
+
type: "error_received",
|
|
364
|
+
timestamp,
|
|
365
|
+
data: {
|
|
366
|
+
message: d.message,
|
|
367
|
+
errorCode: d.errorCode
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
default:
|
|
372
|
+
return { type, timestamp, data };
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Dispose and stop listening
|
|
377
|
+
*/
|
|
378
|
+
dispose() {
|
|
379
|
+
logger2.debug("BusDriver disposing", { agentId: this.config.agentId });
|
|
380
|
+
this.unsubscribe();
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// src/internal/AgentInteractor.ts
|
|
385
|
+
var import_common3 = require("@agentxjs/common");
|
|
386
|
+
var logger3 = (0, import_common3.createLogger)("runtime/AgentInteractor");
|
|
387
|
+
var AgentInteractor = class {
|
|
388
|
+
producer;
|
|
389
|
+
session;
|
|
390
|
+
context;
|
|
391
|
+
constructor(producer, session, context) {
|
|
392
|
+
this.producer = producer;
|
|
393
|
+
this.session = session;
|
|
394
|
+
this.context = context;
|
|
395
|
+
logger3.debug("AgentInteractor created", { agentId: context.agentId });
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Receive user message
|
|
399
|
+
*
|
|
400
|
+
* @param content - Message content
|
|
401
|
+
* @param requestId - Request ID for correlation
|
|
402
|
+
*/
|
|
403
|
+
async receive(content, requestId) {
|
|
404
|
+
logger3.debug("AgentInteractor.receive", {
|
|
405
|
+
requestId,
|
|
406
|
+
agentId: this.context.agentId,
|
|
407
|
+
contentPreview: content.substring(0, 50)
|
|
408
|
+
});
|
|
409
|
+
const userMessage = {
|
|
410
|
+
id: `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
411
|
+
role: "user",
|
|
412
|
+
subtype: "user",
|
|
413
|
+
content,
|
|
414
|
+
timestamp: Date.now()
|
|
415
|
+
};
|
|
416
|
+
await this.session.addMessage(userMessage);
|
|
417
|
+
logger3.debug("UserMessage persisted", {
|
|
418
|
+
messageId: userMessage.id,
|
|
419
|
+
requestId
|
|
420
|
+
});
|
|
421
|
+
const eventContext = {
|
|
422
|
+
agentId: this.context.agentId,
|
|
423
|
+
imageId: this.context.imageId,
|
|
424
|
+
containerId: this.context.containerId,
|
|
425
|
+
sessionId: this.context.sessionId
|
|
426
|
+
};
|
|
427
|
+
this.producer.emit({
|
|
428
|
+
type: "user_message",
|
|
429
|
+
timestamp: Date.now(),
|
|
430
|
+
data: userMessage,
|
|
431
|
+
source: "agent",
|
|
432
|
+
category: "message",
|
|
433
|
+
intent: "request",
|
|
434
|
+
requestId,
|
|
435
|
+
context: eventContext,
|
|
436
|
+
broadcastable: false
|
|
437
|
+
// Internal event for ClaudeEffector
|
|
438
|
+
});
|
|
439
|
+
logger3.info("user_message event emitted to bus", {
|
|
440
|
+
messageId: userMessage.id,
|
|
441
|
+
requestId,
|
|
442
|
+
agentId: this.context.agentId,
|
|
443
|
+
eventType: "user_message"
|
|
444
|
+
});
|
|
445
|
+
return userMessage;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Interrupt current operation
|
|
449
|
+
*
|
|
450
|
+
* @param requestId - Optional request ID for correlation
|
|
451
|
+
*/
|
|
452
|
+
interrupt(requestId) {
|
|
453
|
+
logger3.debug("AgentInteractor.interrupt", {
|
|
454
|
+
requestId,
|
|
455
|
+
agentId: this.context.agentId
|
|
456
|
+
});
|
|
457
|
+
const eventContext = {
|
|
458
|
+
agentId: this.context.agentId,
|
|
459
|
+
imageId: this.context.imageId,
|
|
460
|
+
containerId: this.context.containerId,
|
|
461
|
+
sessionId: this.context.sessionId
|
|
462
|
+
};
|
|
463
|
+
this.producer.emit({
|
|
464
|
+
type: "interrupt",
|
|
465
|
+
timestamp: Date.now(),
|
|
466
|
+
data: { agentId: this.context.agentId },
|
|
467
|
+
source: "agent",
|
|
468
|
+
category: "action",
|
|
469
|
+
intent: "request",
|
|
470
|
+
requestId,
|
|
471
|
+
context: eventContext,
|
|
472
|
+
broadcastable: false
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// src/internal/RuntimeAgent.ts
|
|
478
|
+
var import_agent = require("@agentxjs/agent");
|
|
479
|
+
var import_common6 = require("@agentxjs/common");
|
|
480
|
+
|
|
481
|
+
// src/environment/ClaudeReceptor.ts
|
|
482
|
+
var import_common4 = require("@agentxjs/common");
|
|
483
|
+
var logger4 = (0, import_common4.createLogger)("ecosystem/ClaudeReceptor");
|
|
484
|
+
var ClaudeReceptor = class {
|
|
485
|
+
producer = null;
|
|
486
|
+
currentMeta = null;
|
|
487
|
+
/** Context for tracking content block state */
|
|
488
|
+
blockContext = {
|
|
489
|
+
currentBlockType: null,
|
|
490
|
+
currentBlockIndex: 0,
|
|
491
|
+
currentToolId: null,
|
|
492
|
+
currentToolName: null,
|
|
493
|
+
lastStopReason: null,
|
|
494
|
+
lastStopSequence: null
|
|
495
|
+
};
|
|
496
|
+
/**
|
|
497
|
+
* Connect to SystemBus producer to emit events
|
|
498
|
+
*/
|
|
499
|
+
connect(producer) {
|
|
500
|
+
this.producer = producer;
|
|
501
|
+
logger4.debug("ClaudeReceptor connected to SystemBusProducer");
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Feed SDK message to receptor with correlation metadata
|
|
505
|
+
* @param sdkMsg - SDK message from Claude
|
|
506
|
+
* @param meta - Request metadata for event correlation
|
|
507
|
+
*/
|
|
508
|
+
feed(sdkMsg, meta) {
|
|
509
|
+
this.currentMeta = meta;
|
|
510
|
+
this.processStreamEvent(sdkMsg);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Emit interrupted event
|
|
514
|
+
*/
|
|
515
|
+
emitInterrupted(reason, meta) {
|
|
516
|
+
const eventMeta = meta || this.currentMeta;
|
|
517
|
+
this.emitToBus({
|
|
518
|
+
type: "interrupted",
|
|
519
|
+
timestamp: Date.now(),
|
|
520
|
+
source: "environment",
|
|
521
|
+
category: "stream",
|
|
522
|
+
intent: "notification",
|
|
523
|
+
broadcastable: false,
|
|
524
|
+
requestId: eventMeta?.requestId,
|
|
525
|
+
context: eventMeta?.context,
|
|
526
|
+
data: { reason }
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Emit error_received event
|
|
531
|
+
*
|
|
532
|
+
* Used when an error is received from the environment (e.g., Claude API error).
|
|
533
|
+
* This drives the MealyMachine to produce error_occurred + error_message events.
|
|
534
|
+
*/
|
|
535
|
+
emitError(message, errorCode, meta) {
|
|
536
|
+
const eventMeta = meta || this.currentMeta;
|
|
537
|
+
this.emitToBus({
|
|
538
|
+
type: "error_received",
|
|
539
|
+
timestamp: Date.now(),
|
|
540
|
+
source: "environment",
|
|
541
|
+
category: "stream",
|
|
542
|
+
intent: "notification",
|
|
543
|
+
broadcastable: false,
|
|
544
|
+
requestId: eventMeta?.requestId,
|
|
545
|
+
context: eventMeta?.context,
|
|
546
|
+
data: { message, errorCode }
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Feed SDK user message (contains tool_result) to receptor
|
|
551
|
+
* @param sdkMsg - SDK user message from Claude
|
|
552
|
+
* @param meta - Request metadata for event correlation
|
|
553
|
+
*/
|
|
554
|
+
feedUserMessage(sdkMsg, meta) {
|
|
555
|
+
this.currentMeta = meta;
|
|
556
|
+
const { requestId, context } = meta;
|
|
557
|
+
if (!sdkMsg.message || !Array.isArray(sdkMsg.message.content)) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
for (const block of sdkMsg.message.content) {
|
|
561
|
+
if (block && typeof block === "object" && "type" in block && block.type === "tool_result") {
|
|
562
|
+
const toolResultBlock = block;
|
|
563
|
+
this.emitToBus({
|
|
564
|
+
type: "tool_result",
|
|
565
|
+
timestamp: Date.now(),
|
|
566
|
+
source: "environment",
|
|
567
|
+
category: "stream",
|
|
568
|
+
intent: "notification",
|
|
569
|
+
broadcastable: false,
|
|
570
|
+
requestId,
|
|
571
|
+
context,
|
|
572
|
+
data: {
|
|
573
|
+
toolUseId: toolResultBlock.tool_use_id,
|
|
574
|
+
result: toolResultBlock.content,
|
|
575
|
+
isError: toolResultBlock.is_error || false
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Process stream_event from SDK and emit corresponding DriveableEvent
|
|
583
|
+
*
|
|
584
|
+
* Uses currentMeta for requestId and context correlation.
|
|
585
|
+
*/
|
|
586
|
+
processStreamEvent(sdkMsg) {
|
|
587
|
+
const event = sdkMsg.event;
|
|
588
|
+
const { requestId, context } = this.currentMeta || {};
|
|
589
|
+
switch (event.type) {
|
|
590
|
+
case "message_start":
|
|
591
|
+
this.blockContext = {
|
|
592
|
+
currentBlockType: null,
|
|
593
|
+
currentBlockIndex: 0,
|
|
594
|
+
currentToolId: null,
|
|
595
|
+
currentToolName: null,
|
|
596
|
+
lastStopReason: null,
|
|
597
|
+
lastStopSequence: null
|
|
598
|
+
};
|
|
599
|
+
this.emitToBus({
|
|
600
|
+
type: "message_start",
|
|
601
|
+
timestamp: Date.now(),
|
|
602
|
+
source: "environment",
|
|
603
|
+
category: "stream",
|
|
604
|
+
intent: "notification",
|
|
605
|
+
broadcastable: false,
|
|
606
|
+
requestId,
|
|
607
|
+
context,
|
|
608
|
+
data: {
|
|
609
|
+
message: {
|
|
610
|
+
id: event.message.id,
|
|
611
|
+
model: event.message.model
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
break;
|
|
616
|
+
case "content_block_start": {
|
|
617
|
+
const contentBlock = event.content_block;
|
|
618
|
+
this.blockContext.currentBlockIndex = event.index;
|
|
619
|
+
logger4.debug("content_block_start received", { contentBlock, index: event.index });
|
|
620
|
+
if (contentBlock.type === "text") {
|
|
621
|
+
this.blockContext.currentBlockType = "text";
|
|
622
|
+
this.emitToBus({
|
|
623
|
+
type: "text_content_block_start",
|
|
624
|
+
timestamp: Date.now(),
|
|
625
|
+
source: "environment",
|
|
626
|
+
category: "stream",
|
|
627
|
+
intent: "notification",
|
|
628
|
+
broadcastable: false,
|
|
629
|
+
index: event.index,
|
|
630
|
+
requestId,
|
|
631
|
+
context,
|
|
632
|
+
data: {}
|
|
633
|
+
});
|
|
634
|
+
} else if (contentBlock.type === "tool_use") {
|
|
635
|
+
this.blockContext.currentBlockType = "tool_use";
|
|
636
|
+
this.blockContext.currentToolId = contentBlock.id || null;
|
|
637
|
+
this.blockContext.currentToolName = contentBlock.name || null;
|
|
638
|
+
this.emitToBus({
|
|
639
|
+
type: "tool_use_content_block_start",
|
|
640
|
+
timestamp: Date.now(),
|
|
641
|
+
source: "environment",
|
|
642
|
+
category: "stream",
|
|
643
|
+
intent: "notification",
|
|
644
|
+
broadcastable: false,
|
|
645
|
+
index: event.index,
|
|
646
|
+
requestId,
|
|
647
|
+
context,
|
|
648
|
+
data: {
|
|
649
|
+
id: contentBlock.id || "",
|
|
650
|
+
name: contentBlock.name || ""
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
case "content_block_delta": {
|
|
657
|
+
const delta = event.delta;
|
|
658
|
+
if (delta.type === "text_delta") {
|
|
659
|
+
this.emitToBus({
|
|
660
|
+
type: "text_delta",
|
|
661
|
+
timestamp: Date.now(),
|
|
662
|
+
source: "environment",
|
|
663
|
+
category: "stream",
|
|
664
|
+
intent: "notification",
|
|
665
|
+
broadcastable: false,
|
|
666
|
+
requestId,
|
|
667
|
+
context,
|
|
668
|
+
data: { text: delta.text || "" }
|
|
669
|
+
});
|
|
670
|
+
} else if (delta.type === "input_json_delta") {
|
|
671
|
+
this.emitToBus({
|
|
672
|
+
type: "input_json_delta",
|
|
673
|
+
timestamp: Date.now(),
|
|
674
|
+
source: "environment",
|
|
675
|
+
category: "stream",
|
|
676
|
+
intent: "notification",
|
|
677
|
+
broadcastable: false,
|
|
678
|
+
index: this.blockContext.currentBlockIndex,
|
|
679
|
+
requestId,
|
|
680
|
+
context,
|
|
681
|
+
data: { partialJson: delta.partial_json || "" }
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
case "content_block_stop":
|
|
687
|
+
if (this.blockContext.currentBlockType === "tool_use" && this.blockContext.currentToolId) {
|
|
688
|
+
this.emitToBus({
|
|
689
|
+
type: "tool_use_content_block_stop",
|
|
690
|
+
timestamp: Date.now(),
|
|
691
|
+
source: "environment",
|
|
692
|
+
category: "stream",
|
|
693
|
+
intent: "notification",
|
|
694
|
+
broadcastable: false,
|
|
695
|
+
index: this.blockContext.currentBlockIndex,
|
|
696
|
+
requestId,
|
|
697
|
+
context,
|
|
698
|
+
data: {}
|
|
699
|
+
});
|
|
700
|
+
} else {
|
|
701
|
+
this.emitToBus({
|
|
702
|
+
type: "text_content_block_stop",
|
|
703
|
+
timestamp: Date.now(),
|
|
704
|
+
source: "environment",
|
|
705
|
+
category: "stream",
|
|
706
|
+
intent: "notification",
|
|
707
|
+
broadcastable: false,
|
|
708
|
+
index: this.blockContext.currentBlockIndex,
|
|
709
|
+
requestId,
|
|
710
|
+
context,
|
|
711
|
+
data: {}
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
this.blockContext.currentBlockType = null;
|
|
715
|
+
this.blockContext.currentToolId = null;
|
|
716
|
+
this.blockContext.currentToolName = null;
|
|
717
|
+
break;
|
|
718
|
+
case "message_delta": {
|
|
719
|
+
const msgDelta = event.delta;
|
|
720
|
+
if (msgDelta.stop_reason) {
|
|
721
|
+
this.blockContext.lastStopReason = msgDelta.stop_reason;
|
|
722
|
+
this.blockContext.lastStopSequence = msgDelta.stop_sequence || null;
|
|
723
|
+
}
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
case "message_stop":
|
|
727
|
+
this.emitToBus({
|
|
728
|
+
type: "message_stop",
|
|
729
|
+
timestamp: Date.now(),
|
|
730
|
+
source: "environment",
|
|
731
|
+
category: "stream",
|
|
732
|
+
intent: "notification",
|
|
733
|
+
broadcastable: false,
|
|
734
|
+
requestId,
|
|
735
|
+
context,
|
|
736
|
+
data: {
|
|
737
|
+
stopReason: this.blockContext.lastStopReason || "end_turn",
|
|
738
|
+
stopSequence: this.blockContext.lastStopSequence || void 0
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
this.blockContext.lastStopReason = null;
|
|
742
|
+
this.blockContext.lastStopSequence = null;
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
emitToBus(event) {
|
|
747
|
+
if (this.producer) {
|
|
748
|
+
this.producer.emit(event);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
// src/environment/ClaudeEffector.ts
|
|
754
|
+
var import_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
|
|
755
|
+
var import_rxjs2 = require("rxjs");
|
|
756
|
+
var import_common5 = require("@agentxjs/common");
|
|
757
|
+
|
|
758
|
+
// src/environment/buildOptions.ts
|
|
759
|
+
function buildOptions(context, abortController) {
|
|
760
|
+
const options = {
|
|
761
|
+
abortController,
|
|
762
|
+
includePartialMessages: true
|
|
763
|
+
};
|
|
764
|
+
if (context.cwd) {
|
|
765
|
+
options.cwd = context.cwd;
|
|
766
|
+
}
|
|
767
|
+
const env = {
|
|
768
|
+
...process.env
|
|
769
|
+
};
|
|
770
|
+
if (context.baseUrl) {
|
|
771
|
+
env.ANTHROPIC_BASE_URL = context.baseUrl;
|
|
772
|
+
}
|
|
773
|
+
if (context.apiKey) {
|
|
774
|
+
env.ANTHROPIC_API_KEY = context.apiKey;
|
|
775
|
+
}
|
|
776
|
+
options.env = env;
|
|
777
|
+
options.executable = process.execPath;
|
|
778
|
+
if (context.model) options.model = context.model;
|
|
779
|
+
if (context.systemPrompt) options.systemPrompt = context.systemPrompt;
|
|
780
|
+
if (context.maxTurns) options.maxTurns = context.maxTurns;
|
|
781
|
+
if (context.maxThinkingTokens) options.maxThinkingTokens = context.maxThinkingTokens;
|
|
782
|
+
if (context.resume) options.resume = context.resume;
|
|
783
|
+
if (context.permissionMode) {
|
|
784
|
+
options.permissionMode = context.permissionMode;
|
|
785
|
+
} else {
|
|
786
|
+
options.permissionMode = "bypassPermissions";
|
|
787
|
+
}
|
|
788
|
+
return options;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/environment/helpers.ts
|
|
792
|
+
function buildPrompt(message) {
|
|
793
|
+
if (typeof message.content === "string") {
|
|
794
|
+
return message.content;
|
|
795
|
+
}
|
|
796
|
+
if (Array.isArray(message.content)) {
|
|
797
|
+
return message.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n");
|
|
798
|
+
}
|
|
799
|
+
return "";
|
|
800
|
+
}
|
|
801
|
+
function buildSDKUserMessage(message, sessionId) {
|
|
802
|
+
return {
|
|
803
|
+
type: "user",
|
|
804
|
+
message: { role: "user", content: buildPrompt(message) },
|
|
805
|
+
parent_tool_use_id: null,
|
|
806
|
+
session_id: sessionId
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/environment/observableToAsyncIterable.ts
|
|
811
|
+
async function* observableToAsyncIterable(observable) {
|
|
812
|
+
const queue = [];
|
|
813
|
+
let resolve = null;
|
|
814
|
+
let reject = null;
|
|
815
|
+
let done = false;
|
|
816
|
+
let error = null;
|
|
817
|
+
const subscription = observable.subscribe({
|
|
818
|
+
next: (value) => {
|
|
819
|
+
if (resolve) {
|
|
820
|
+
resolve({ value, done: false });
|
|
821
|
+
resolve = null;
|
|
822
|
+
reject = null;
|
|
823
|
+
} else {
|
|
824
|
+
queue.push(value);
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
error: (err) => {
|
|
828
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
829
|
+
done = true;
|
|
830
|
+
if (reject) {
|
|
831
|
+
reject(error);
|
|
832
|
+
resolve = null;
|
|
833
|
+
reject = null;
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
complete: () => {
|
|
837
|
+
done = true;
|
|
838
|
+
if (resolve) {
|
|
839
|
+
resolve({ value: void 0, done: true });
|
|
840
|
+
resolve = null;
|
|
841
|
+
reject = null;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
try {
|
|
846
|
+
while (!done || queue.length > 0) {
|
|
847
|
+
if (error) {
|
|
848
|
+
throw error;
|
|
849
|
+
}
|
|
850
|
+
if (queue.length > 0) {
|
|
851
|
+
yield queue.shift();
|
|
852
|
+
} else if (!done) {
|
|
853
|
+
const result = await new Promise((res, rej) => {
|
|
854
|
+
resolve = (iterResult) => {
|
|
855
|
+
if (iterResult.done) {
|
|
856
|
+
done = true;
|
|
857
|
+
res({ done: true });
|
|
858
|
+
} else {
|
|
859
|
+
res({ value: iterResult.value, done: false });
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
reject = rej;
|
|
863
|
+
});
|
|
864
|
+
if (!result.done) {
|
|
865
|
+
yield result.value;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
} finally {
|
|
870
|
+
subscription.unsubscribe();
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/environment/ClaudeEffector.ts
|
|
875
|
+
var logger5 = (0, import_common5.createLogger)("ecosystem/ClaudeEffector");
|
|
876
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
877
|
+
var ClaudeEffector = class {
|
|
878
|
+
config;
|
|
879
|
+
receptor;
|
|
880
|
+
promptSubject = new import_rxjs2.Subject();
|
|
881
|
+
currentAbortController = null;
|
|
882
|
+
claudeQuery = null;
|
|
883
|
+
isInitialized = false;
|
|
884
|
+
wasInterrupted = false;
|
|
885
|
+
currentMeta = null;
|
|
886
|
+
constructor(config, receptor) {
|
|
887
|
+
this.config = config;
|
|
888
|
+
this.receptor = receptor;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Connect to SystemBus consumer to subscribe to events
|
|
892
|
+
*/
|
|
893
|
+
connect(consumer) {
|
|
894
|
+
logger5.debug("ClaudeEffector connected to SystemBusConsumer", {
|
|
895
|
+
agentId: this.config.agentId
|
|
896
|
+
});
|
|
897
|
+
consumer.on("user_message", async (event) => {
|
|
898
|
+
const typedEvent = event;
|
|
899
|
+
logger5.debug("user_message event received", {
|
|
900
|
+
eventAgentId: typedEvent.context?.agentId,
|
|
901
|
+
myAgentId: this.config.agentId,
|
|
902
|
+
matches: typedEvent.context?.agentId === this.config.agentId
|
|
903
|
+
});
|
|
904
|
+
if (typedEvent.context?.agentId !== this.config.agentId) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
const message = typedEvent.data;
|
|
908
|
+
const meta = {
|
|
909
|
+
requestId: typedEvent.requestId || "",
|
|
910
|
+
context: typedEvent.context || {}
|
|
911
|
+
};
|
|
912
|
+
await this.send(message, meta);
|
|
913
|
+
});
|
|
914
|
+
consumer.on("interrupt", (event) => {
|
|
915
|
+
const typedEvent = event;
|
|
916
|
+
if (typedEvent.context?.agentId !== this.config.agentId) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
const meta = {
|
|
920
|
+
requestId: typedEvent.requestId || "",
|
|
921
|
+
context: typedEvent.context || {}
|
|
922
|
+
};
|
|
923
|
+
this.interrupt(meta);
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Send a message to Claude SDK
|
|
928
|
+
*/
|
|
929
|
+
async send(message, meta) {
|
|
930
|
+
this.wasInterrupted = false;
|
|
931
|
+
this.currentAbortController = new AbortController();
|
|
932
|
+
this.currentMeta = meta;
|
|
933
|
+
const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;
|
|
934
|
+
const timeoutId = setTimeout(() => {
|
|
935
|
+
logger5.warn("Request timeout", { timeout });
|
|
936
|
+
this.currentAbortController?.abort(new Error(`Request timeout after ${timeout}ms`));
|
|
937
|
+
}, timeout);
|
|
938
|
+
try {
|
|
939
|
+
await this.initialize(this.currentAbortController);
|
|
940
|
+
const sessionId = this.config.sessionId || "default";
|
|
941
|
+
const sdkUserMessage = buildSDKUserMessage(message, sessionId);
|
|
942
|
+
logger5.debug("Sending message to Claude", {
|
|
943
|
+
content: typeof message.content === "string" ? message.content.substring(0, 80) : "[structured]",
|
|
944
|
+
timeout,
|
|
945
|
+
requestId: meta.requestId
|
|
946
|
+
});
|
|
947
|
+
this.promptSubject.next(sdkUserMessage);
|
|
948
|
+
} finally {
|
|
949
|
+
clearTimeout(timeoutId);
|
|
950
|
+
this.currentAbortController = null;
|
|
951
|
+
this.wasInterrupted = false;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Interrupt current operation
|
|
956
|
+
*/
|
|
957
|
+
interrupt(meta) {
|
|
958
|
+
if (this.claudeQuery) {
|
|
959
|
+
logger5.debug("Interrupting Claude query", { requestId: meta?.requestId });
|
|
960
|
+
this.wasInterrupted = true;
|
|
961
|
+
if (meta) {
|
|
962
|
+
this.currentMeta = meta;
|
|
963
|
+
}
|
|
964
|
+
this.claudeQuery.interrupt().catch((err) => {
|
|
965
|
+
logger5.debug("SDK interrupt() error (may be expected)", { error: err });
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Initialize the Claude SDK query (lazy initialization)
|
|
971
|
+
*/
|
|
972
|
+
async initialize(abortController) {
|
|
973
|
+
if (this.isInitialized) return;
|
|
974
|
+
logger5.info("Initializing ClaudeEffector");
|
|
975
|
+
const context = {
|
|
976
|
+
apiKey: this.config.apiKey,
|
|
977
|
+
baseUrl: this.config.baseUrl,
|
|
978
|
+
model: this.config.model,
|
|
979
|
+
systemPrompt: this.config.systemPrompt,
|
|
980
|
+
cwd: this.config.cwd,
|
|
981
|
+
resume: this.config.resumeSessionId
|
|
982
|
+
};
|
|
983
|
+
const sdkOptions = buildOptions(context, abortController);
|
|
984
|
+
const promptStream = observableToAsyncIterable(this.promptSubject);
|
|
985
|
+
this.claudeQuery = (0, import_claude_agent_sdk.query)({
|
|
986
|
+
prompt: promptStream,
|
|
987
|
+
options: sdkOptions
|
|
988
|
+
});
|
|
989
|
+
this.isInitialized = true;
|
|
990
|
+
this.startBackgroundListener();
|
|
991
|
+
logger5.info("ClaudeEffector initialized");
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Start background listener for SDK responses
|
|
995
|
+
*/
|
|
996
|
+
startBackgroundListener() {
|
|
997
|
+
(async () => {
|
|
998
|
+
try {
|
|
999
|
+
for await (const sdkMsg of this.claudeQuery) {
|
|
1000
|
+
logger5.debug("SDK message received", {
|
|
1001
|
+
type: sdkMsg.type,
|
|
1002
|
+
subtype: sdkMsg.subtype,
|
|
1003
|
+
sessionId: sdkMsg.session_id,
|
|
1004
|
+
hasCurrentMeta: !!this.currentMeta
|
|
1005
|
+
});
|
|
1006
|
+
if (sdkMsg.type === "stream_event" && this.currentMeta) {
|
|
1007
|
+
this.receptor.feed(sdkMsg, this.currentMeta);
|
|
1008
|
+
}
|
|
1009
|
+
if (sdkMsg.type === "user" && this.currentMeta) {
|
|
1010
|
+
this.receptor.feedUserMessage(sdkMsg, this.currentMeta);
|
|
1011
|
+
}
|
|
1012
|
+
if (sdkMsg.session_id && this.config.onSessionIdCaptured) {
|
|
1013
|
+
this.config.onSessionIdCaptured(sdkMsg.session_id);
|
|
1014
|
+
}
|
|
1015
|
+
if (sdkMsg.type === "result") {
|
|
1016
|
+
const resultMsg = sdkMsg;
|
|
1017
|
+
logger5.info("SDK result received (full)", {
|
|
1018
|
+
fullResult: JSON.stringify(sdkMsg, null, 2)
|
|
1019
|
+
});
|
|
1020
|
+
logger5.info("SDK result received", {
|
|
1021
|
+
subtype: resultMsg.subtype,
|
|
1022
|
+
isError: resultMsg.is_error,
|
|
1023
|
+
errors: resultMsg.errors,
|
|
1024
|
+
wasInterrupted: this.wasInterrupted
|
|
1025
|
+
});
|
|
1026
|
+
if (resultMsg.subtype === "error_during_execution" && this.wasInterrupted) {
|
|
1027
|
+
this.receptor.emitInterrupted("user_interrupt", this.currentMeta || void 0);
|
|
1028
|
+
} else if (resultMsg.is_error && this.currentMeta) {
|
|
1029
|
+
const fullResult = sdkMsg;
|
|
1030
|
+
const errorMessage = fullResult.error?.message || fullResult.errors?.join(", ") || (typeof fullResult.result === "string" ? fullResult.result : null) || "An error occurred";
|
|
1031
|
+
const errorCode = fullResult.error?.type || resultMsg.subtype || "api_error";
|
|
1032
|
+
this.receptor.emitError(errorMessage, errorCode, this.currentMeta);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
if (this.isAbortError(error)) {
|
|
1038
|
+
logger5.debug("Background listener aborted (expected during interrupt)");
|
|
1039
|
+
this.resetState();
|
|
1040
|
+
} else {
|
|
1041
|
+
logger5.error("Background listener error", { error });
|
|
1042
|
+
if (this.currentMeta) {
|
|
1043
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
1044
|
+
this.receptor.emitError(errorMessage, "runtime_error", this.currentMeta);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
})();
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Check if an error is an abort error
|
|
1052
|
+
*/
|
|
1053
|
+
isAbortError(error) {
|
|
1054
|
+
if (error instanceof Error) {
|
|
1055
|
+
if (error.name === "AbortError") return true;
|
|
1056
|
+
if (error.message.includes("aborted")) return true;
|
|
1057
|
+
if (error.message.includes("abort")) return true;
|
|
1058
|
+
}
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Reset state after abort
|
|
1063
|
+
*/
|
|
1064
|
+
resetState() {
|
|
1065
|
+
this.isInitialized = false;
|
|
1066
|
+
this.claudeQuery = null;
|
|
1067
|
+
this.promptSubject = new import_rxjs2.Subject();
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Dispose and cleanup resources
|
|
1071
|
+
*/
|
|
1072
|
+
dispose() {
|
|
1073
|
+
logger5.debug("Disposing ClaudeEffector");
|
|
1074
|
+
if (this.currentAbortController) {
|
|
1075
|
+
this.currentAbortController.abort();
|
|
1076
|
+
}
|
|
1077
|
+
this.promptSubject.complete();
|
|
1078
|
+
this.resetState();
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
// src/environment/ClaudeEnvironment.ts
|
|
1083
|
+
var ClaudeEnvironment = class {
|
|
1084
|
+
name = "claude";
|
|
1085
|
+
receptor;
|
|
1086
|
+
effector;
|
|
1087
|
+
claudeEffector;
|
|
1088
|
+
constructor(config) {
|
|
1089
|
+
const claudeReceptor = new ClaudeReceptor();
|
|
1090
|
+
const claudeEffector = new ClaudeEffector(config, claudeReceptor);
|
|
1091
|
+
this.receptor = claudeReceptor;
|
|
1092
|
+
this.effector = claudeEffector;
|
|
1093
|
+
this.claudeEffector = claudeEffector;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Dispose environment resources
|
|
1097
|
+
*/
|
|
1098
|
+
dispose() {
|
|
1099
|
+
this.claudeEffector.dispose();
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
// src/internal/RuntimeAgent.ts
|
|
1104
|
+
var logger6 = (0, import_common6.createLogger)("runtime/RuntimeAgent");
|
|
1105
|
+
var BusPresenter = class {
|
|
1106
|
+
constructor(producer, session, agentId, imageId, containerId) {
|
|
1107
|
+
this.producer = producer;
|
|
1108
|
+
this.session = session;
|
|
1109
|
+
this.agentId = agentId;
|
|
1110
|
+
this.imageId = imageId;
|
|
1111
|
+
this.containerId = containerId;
|
|
1112
|
+
}
|
|
1113
|
+
name = "BusPresenter";
|
|
1114
|
+
description = "Forwards AgentOutput to SystemBus and collects messages";
|
|
1115
|
+
present(_agentId, output) {
|
|
1116
|
+
const category = this.getCategoryForOutput(output);
|
|
1117
|
+
if (output.type === "user_message") {
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
let data = output.data;
|
|
1121
|
+
if (category === "message") {
|
|
1122
|
+
data = this.convertToMessage(output);
|
|
1123
|
+
}
|
|
1124
|
+
const systemEvent = {
|
|
1125
|
+
type: output.type,
|
|
1126
|
+
timestamp: output.timestamp,
|
|
1127
|
+
data,
|
|
1128
|
+
source: "agent",
|
|
1129
|
+
category,
|
|
1130
|
+
intent: "notification",
|
|
1131
|
+
context: {
|
|
1132
|
+
containerId: this.containerId,
|
|
1133
|
+
imageId: this.imageId,
|
|
1134
|
+
agentId: this.agentId,
|
|
1135
|
+
sessionId: this.session.sessionId
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
this.producer.emit(systemEvent);
|
|
1139
|
+
if (category === "message") {
|
|
1140
|
+
this.session.addMessage(data).catch((err) => {
|
|
1141
|
+
logger6.error("Failed to persist message", { error: err, messageType: output.type });
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Convert AgentOutput to proper Message type for persistence
|
|
1147
|
+
*/
|
|
1148
|
+
convertToMessage(output) {
|
|
1149
|
+
const eventData = output.data;
|
|
1150
|
+
const messageId = eventData.messageId ?? eventData.id;
|
|
1151
|
+
const timestamp = eventData.timestamp || output.timestamp;
|
|
1152
|
+
switch (output.type) {
|
|
1153
|
+
case "assistant_message": {
|
|
1154
|
+
const content = eventData.content;
|
|
1155
|
+
return {
|
|
1156
|
+
id: messageId,
|
|
1157
|
+
role: "assistant",
|
|
1158
|
+
subtype: "assistant",
|
|
1159
|
+
content,
|
|
1160
|
+
timestamp
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
case "tool_call_message": {
|
|
1164
|
+
const toolCalls = eventData.toolCalls;
|
|
1165
|
+
const toolCall = toolCalls[0];
|
|
1166
|
+
return {
|
|
1167
|
+
id: messageId,
|
|
1168
|
+
role: "assistant",
|
|
1169
|
+
subtype: "tool-call",
|
|
1170
|
+
toolCall,
|
|
1171
|
+
timestamp
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
case "tool_result_message": {
|
|
1175
|
+
const results = eventData.results;
|
|
1176
|
+
const toolResult = results[0];
|
|
1177
|
+
return {
|
|
1178
|
+
id: messageId,
|
|
1179
|
+
role: "tool",
|
|
1180
|
+
subtype: "tool-result",
|
|
1181
|
+
toolCallId: toolResult.id,
|
|
1182
|
+
toolResult,
|
|
1183
|
+
timestamp
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
case "error_message": {
|
|
1187
|
+
const content = eventData.content;
|
|
1188
|
+
const errorCode = eventData.errorCode;
|
|
1189
|
+
return {
|
|
1190
|
+
id: messageId,
|
|
1191
|
+
role: "error",
|
|
1192
|
+
subtype: "error",
|
|
1193
|
+
content,
|
|
1194
|
+
errorCode,
|
|
1195
|
+
timestamp
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
default:
|
|
1199
|
+
logger6.warn("Unknown message type, passing through", { type: output.type });
|
|
1200
|
+
return eventData;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Determine event category from output type
|
|
1205
|
+
*/
|
|
1206
|
+
getCategoryForOutput(output) {
|
|
1207
|
+
const type = output.type;
|
|
1208
|
+
if (type === "message_start" || type === "message_delta" || type === "message_stop" || type === "text_delta" || type === "tool_use_start" || type === "input_json_delta" || type === "tool_use_stop" || type === "tool_result") {
|
|
1209
|
+
return "stream";
|
|
1210
|
+
}
|
|
1211
|
+
if (type === "user_message" || type === "assistant_message" || type === "tool_call_message" || type === "tool_result_message" || type === "error_message") {
|
|
1212
|
+
return "message";
|
|
1213
|
+
}
|
|
1214
|
+
if (type === "turn_request" || type === "turn_response") {
|
|
1215
|
+
return "turn";
|
|
1216
|
+
}
|
|
1217
|
+
return "state";
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
var RuntimeAgent = class {
|
|
1221
|
+
agentId;
|
|
1222
|
+
imageId;
|
|
1223
|
+
name;
|
|
1224
|
+
containerId;
|
|
1225
|
+
createdAt;
|
|
1226
|
+
_lifecycle = "running";
|
|
1227
|
+
interactor;
|
|
1228
|
+
driver;
|
|
1229
|
+
engine;
|
|
1230
|
+
producer;
|
|
1231
|
+
environment;
|
|
1232
|
+
imageRepository;
|
|
1233
|
+
session;
|
|
1234
|
+
config;
|
|
1235
|
+
constructor(config) {
|
|
1236
|
+
this.agentId = config.agentId;
|
|
1237
|
+
this.imageId = config.imageId;
|
|
1238
|
+
this.name = config.config.name ?? `agent-${config.agentId}`;
|
|
1239
|
+
this.containerId = config.containerId;
|
|
1240
|
+
this.createdAt = Date.now();
|
|
1241
|
+
this.producer = config.bus.asProducer();
|
|
1242
|
+
this.session = config.session;
|
|
1243
|
+
this.config = config.config;
|
|
1244
|
+
this.imageRepository = config.imageRepository;
|
|
1245
|
+
const resumeSessionId = config.image.metadata?.claudeSdkSessionId;
|
|
1246
|
+
this.environment = new ClaudeEnvironment({
|
|
1247
|
+
agentId: this.agentId,
|
|
1248
|
+
apiKey: config.llmConfig.apiKey,
|
|
1249
|
+
baseUrl: config.llmConfig.baseUrl,
|
|
1250
|
+
model: config.llmConfig.model,
|
|
1251
|
+
systemPrompt: config.config.systemPrompt,
|
|
1252
|
+
resumeSessionId,
|
|
1253
|
+
onSessionIdCaptured: (sdkSessionId) => {
|
|
1254
|
+
this.saveSessionId(sdkSessionId);
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
this.environment.receptor.connect(config.bus.asProducer());
|
|
1258
|
+
this.environment.effector.connect(config.bus.asConsumer());
|
|
1259
|
+
logger6.info("ClaudeEnvironment created for agent", {
|
|
1260
|
+
agentId: this.agentId,
|
|
1261
|
+
imageId: this.imageId,
|
|
1262
|
+
resumeSessionId: resumeSessionId ?? "none",
|
|
1263
|
+
isResume: !!resumeSessionId,
|
|
1264
|
+
imageMetadata: config.image.metadata
|
|
1265
|
+
});
|
|
1266
|
+
const presenter = new BusPresenter(
|
|
1267
|
+
this.producer,
|
|
1268
|
+
config.session,
|
|
1269
|
+
this.agentId,
|
|
1270
|
+
this.imageId,
|
|
1271
|
+
this.containerId
|
|
1272
|
+
);
|
|
1273
|
+
this.engine = (0, import_agent.createAgent)({
|
|
1274
|
+
driver: {
|
|
1275
|
+
name: "DummyDriver",
|
|
1276
|
+
description: "Placeholder driver for push-based event handling",
|
|
1277
|
+
receive: async function* () {
|
|
1278
|
+
},
|
|
1279
|
+
interrupt: () => {
|
|
1280
|
+
}
|
|
1281
|
+
},
|
|
1282
|
+
presenter
|
|
1283
|
+
});
|
|
1284
|
+
this.interactor = new AgentInteractor(this.producer, config.session, {
|
|
1285
|
+
agentId: this.agentId,
|
|
1286
|
+
imageId: this.imageId,
|
|
1287
|
+
containerId: this.containerId,
|
|
1288
|
+
sessionId: config.session.sessionId
|
|
1289
|
+
});
|
|
1290
|
+
this.driver = new BusDriver(config.bus.asConsumer(), {
|
|
1291
|
+
agentId: this.agentId,
|
|
1292
|
+
onStreamEvent: (event) => {
|
|
1293
|
+
logger6.debug("BusDriver \u2192 Engine.handleStreamEvent", { type: event.type });
|
|
1294
|
+
this.engine.handleStreamEvent(event);
|
|
1295
|
+
},
|
|
1296
|
+
onStreamComplete: (reason) => {
|
|
1297
|
+
logger6.debug("Stream completed", { reason, agentId: this.agentId });
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
logger6.debug("RuntimeAgent created", {
|
|
1301
|
+
agentId: this.agentId,
|
|
1302
|
+
imageId: this.imageId
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Save SDK session ID to image metadata for future resume
|
|
1307
|
+
*/
|
|
1308
|
+
saveSessionId(sdkSessionId) {
|
|
1309
|
+
logger6.info("Saving SDK session ID to image metadata", {
|
|
1310
|
+
agentId: this.agentId,
|
|
1311
|
+
imageId: this.imageId,
|
|
1312
|
+
sdkSessionId
|
|
1313
|
+
});
|
|
1314
|
+
this.imageRepository.updateMetadata(this.imageId, { claudeSdkSessionId: sdkSessionId }).catch((err) => {
|
|
1315
|
+
logger6.error("Failed to save SDK session ID", { error: err, imageId: this.imageId });
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
get lifecycle() {
|
|
1319
|
+
return this._lifecycle;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Receive a message from user
|
|
1323
|
+
*
|
|
1324
|
+
* @param content - Message content
|
|
1325
|
+
* @param requestId - Request ID for correlation
|
|
1326
|
+
*/
|
|
1327
|
+
async receive(content, requestId) {
|
|
1328
|
+
logger6.debug("RuntimeAgent.receive called", {
|
|
1329
|
+
agentId: this.agentId,
|
|
1330
|
+
contentPreview: content.substring(0, 50),
|
|
1331
|
+
requestId
|
|
1332
|
+
});
|
|
1333
|
+
if (this._lifecycle !== "running") {
|
|
1334
|
+
throw new Error(`Cannot send message to ${this._lifecycle} agent`);
|
|
1335
|
+
}
|
|
1336
|
+
await this.interactor.receive(content, requestId || `req_${Date.now()}`);
|
|
1337
|
+
logger6.debug("RuntimeAgent.receive completed", { agentId: this.agentId });
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Interrupt current operation
|
|
1341
|
+
*/
|
|
1342
|
+
interrupt(requestId) {
|
|
1343
|
+
logger6.debug("RuntimeAgent.interrupt called", { agentId: this.agentId, requestId });
|
|
1344
|
+
this.interactor.interrupt(requestId);
|
|
1345
|
+
this.producer.emit({
|
|
1346
|
+
type: "interrupted",
|
|
1347
|
+
timestamp: Date.now(),
|
|
1348
|
+
source: "agent",
|
|
1349
|
+
category: "lifecycle",
|
|
1350
|
+
intent: "notification",
|
|
1351
|
+
data: {
|
|
1352
|
+
agentId: this.agentId,
|
|
1353
|
+
containerId: this.containerId
|
|
1354
|
+
},
|
|
1355
|
+
context: {
|
|
1356
|
+
containerId: this.containerId,
|
|
1357
|
+
imageId: this.imageId,
|
|
1358
|
+
agentId: this.agentId,
|
|
1359
|
+
sessionId: this.session.sessionId
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
async stop() {
|
|
1364
|
+
if (this._lifecycle === "destroyed") {
|
|
1365
|
+
throw new Error("Cannot stop destroyed agent");
|
|
1366
|
+
}
|
|
1367
|
+
this._lifecycle = "stopped";
|
|
1368
|
+
}
|
|
1369
|
+
async resume() {
|
|
1370
|
+
if (this._lifecycle === "destroyed") {
|
|
1371
|
+
throw new Error("Cannot resume destroyed agent");
|
|
1372
|
+
}
|
|
1373
|
+
this._lifecycle = "running";
|
|
1374
|
+
this.producer.emit({
|
|
1375
|
+
type: "session_resumed",
|
|
1376
|
+
timestamp: Date.now(),
|
|
1377
|
+
source: "session",
|
|
1378
|
+
category: "lifecycle",
|
|
1379
|
+
intent: "notification",
|
|
1380
|
+
data: {
|
|
1381
|
+
sessionId: this.session.sessionId,
|
|
1382
|
+
agentId: this.agentId,
|
|
1383
|
+
containerId: this.containerId
|
|
1384
|
+
},
|
|
1385
|
+
context: {
|
|
1386
|
+
containerId: this.containerId,
|
|
1387
|
+
imageId: this.imageId,
|
|
1388
|
+
agentId: this.agentId,
|
|
1389
|
+
sessionId: this.session.sessionId
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
async destroy() {
|
|
1394
|
+
if (this._lifecycle !== "destroyed") {
|
|
1395
|
+
this.driver.dispose();
|
|
1396
|
+
this.environment.dispose();
|
|
1397
|
+
await this.engine.destroy();
|
|
1398
|
+
this._lifecycle = "destroyed";
|
|
1399
|
+
this.producer.emit({
|
|
1400
|
+
type: "session_destroyed",
|
|
1401
|
+
timestamp: Date.now(),
|
|
1402
|
+
source: "session",
|
|
1403
|
+
category: "lifecycle",
|
|
1404
|
+
intent: "notification",
|
|
1405
|
+
data: {
|
|
1406
|
+
sessionId: this.session.sessionId,
|
|
1407
|
+
agentId: this.agentId,
|
|
1408
|
+
containerId: this.containerId
|
|
1409
|
+
},
|
|
1410
|
+
context: {
|
|
1411
|
+
containerId: this.containerId,
|
|
1412
|
+
imageId: this.imageId,
|
|
1413
|
+
agentId: this.agentId,
|
|
1414
|
+
sessionId: this.session.sessionId
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1421
|
+
// src/internal/RuntimeSession.ts
|
|
1422
|
+
var RuntimeSession = class {
|
|
1423
|
+
sessionId;
|
|
1424
|
+
imageId;
|
|
1425
|
+
containerId;
|
|
1426
|
+
createdAt;
|
|
1427
|
+
repository;
|
|
1428
|
+
producer;
|
|
1429
|
+
constructor(config) {
|
|
1430
|
+
this.sessionId = config.sessionId;
|
|
1431
|
+
this.imageId = config.imageId;
|
|
1432
|
+
this.containerId = config.containerId;
|
|
1433
|
+
this.createdAt = Date.now();
|
|
1434
|
+
this.repository = config.repository;
|
|
1435
|
+
this.producer = config.producer;
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Initialize session in storage
|
|
1439
|
+
*/
|
|
1440
|
+
async initialize() {
|
|
1441
|
+
const record = {
|
|
1442
|
+
sessionId: this.sessionId,
|
|
1443
|
+
imageId: this.imageId,
|
|
1444
|
+
containerId: this.containerId,
|
|
1445
|
+
createdAt: this.createdAt,
|
|
1446
|
+
updatedAt: this.createdAt
|
|
1447
|
+
};
|
|
1448
|
+
await this.repository.saveSession(record);
|
|
1449
|
+
this.producer.emit({
|
|
1450
|
+
type: "session_created",
|
|
1451
|
+
timestamp: this.createdAt,
|
|
1452
|
+
source: "session",
|
|
1453
|
+
category: "lifecycle",
|
|
1454
|
+
intent: "notification",
|
|
1455
|
+
data: {
|
|
1456
|
+
sessionId: this.sessionId,
|
|
1457
|
+
imageId: this.imageId,
|
|
1458
|
+
containerId: this.containerId,
|
|
1459
|
+
createdAt: this.createdAt
|
|
1460
|
+
},
|
|
1461
|
+
context: {
|
|
1462
|
+
containerId: this.containerId,
|
|
1463
|
+
sessionId: this.sessionId
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
async addMessage(message) {
|
|
1468
|
+
await this.repository.addMessage(this.sessionId, message);
|
|
1469
|
+
this.producer.emit({
|
|
1470
|
+
type: "message_persisted",
|
|
1471
|
+
timestamp: Date.now(),
|
|
1472
|
+
source: "session",
|
|
1473
|
+
category: "persist",
|
|
1474
|
+
intent: "result",
|
|
1475
|
+
data: {
|
|
1476
|
+
sessionId: this.sessionId,
|
|
1477
|
+
messageId: message.id,
|
|
1478
|
+
savedAt: Date.now()
|
|
1479
|
+
},
|
|
1480
|
+
context: {
|
|
1481
|
+
containerId: this.containerId,
|
|
1482
|
+
sessionId: this.sessionId
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
async getMessages() {
|
|
1487
|
+
return this.repository.getMessages(this.sessionId);
|
|
1488
|
+
}
|
|
1489
|
+
async clear() {
|
|
1490
|
+
await this.repository.clearMessages(this.sessionId);
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1494
|
+
// src/internal/RuntimeSandbox.ts
|
|
1495
|
+
var import_promises = require("fs/promises");
|
|
1496
|
+
var import_node_path = require("path");
|
|
1497
|
+
var RuntimeWorkdir = class {
|
|
1498
|
+
id;
|
|
1499
|
+
name;
|
|
1500
|
+
path;
|
|
1501
|
+
constructor(agentId, path) {
|
|
1502
|
+
this.id = agentId;
|
|
1503
|
+
this.name = `workdir_${agentId}`;
|
|
1504
|
+
this.path = path;
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
var RuntimeSandbox = class {
|
|
1508
|
+
name;
|
|
1509
|
+
workdir;
|
|
1510
|
+
initialized = false;
|
|
1511
|
+
constructor(config) {
|
|
1512
|
+
this.name = `sandbox_${config.agentId}`;
|
|
1513
|
+
const workdirPath = (0, import_node_path.join)(
|
|
1514
|
+
config.basePath,
|
|
1515
|
+
"containers",
|
|
1516
|
+
config.containerId,
|
|
1517
|
+
"workdirs",
|
|
1518
|
+
config.agentId
|
|
1519
|
+
);
|
|
1520
|
+
this.workdir = new RuntimeWorkdir(config.agentId, workdirPath);
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Initialize sandbox - create directories
|
|
1524
|
+
*/
|
|
1525
|
+
async initialize() {
|
|
1526
|
+
if (this.initialized) return;
|
|
1527
|
+
await (0, import_promises.mkdir)(this.workdir.path, { recursive: true });
|
|
1528
|
+
this.initialized = true;
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Cleanup sandbox - remove directories (optional)
|
|
1532
|
+
*/
|
|
1533
|
+
async cleanup() {
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
|
|
1537
|
+
// src/internal/RuntimeImage.ts
|
|
1538
|
+
var import_common7 = require("@agentxjs/common");
|
|
1539
|
+
var logger7 = (0, import_common7.createLogger)("runtime/RuntimeImage");
|
|
1540
|
+
var RuntimeImage = class _RuntimeImage {
|
|
1541
|
+
constructor(record, context) {
|
|
1542
|
+
this.record = record;
|
|
1543
|
+
this.context = context;
|
|
1544
|
+
}
|
|
1545
|
+
// ==================== Getters ====================
|
|
1546
|
+
get imageId() {
|
|
1547
|
+
return this.record.imageId;
|
|
1548
|
+
}
|
|
1549
|
+
get containerId() {
|
|
1550
|
+
return this.record.containerId;
|
|
1551
|
+
}
|
|
1552
|
+
get sessionId() {
|
|
1553
|
+
return this.record.sessionId;
|
|
1554
|
+
}
|
|
1555
|
+
get name() {
|
|
1556
|
+
return this.record.name;
|
|
1557
|
+
}
|
|
1558
|
+
get description() {
|
|
1559
|
+
return this.record.description;
|
|
1560
|
+
}
|
|
1561
|
+
get systemPrompt() {
|
|
1562
|
+
return this.record.systemPrompt;
|
|
1563
|
+
}
|
|
1564
|
+
get createdAt() {
|
|
1565
|
+
return this.record.createdAt;
|
|
1566
|
+
}
|
|
1567
|
+
get updatedAt() {
|
|
1568
|
+
return this.record.updatedAt;
|
|
1569
|
+
}
|
|
1570
|
+
// ==================== Static Factory Methods ====================
|
|
1571
|
+
/**
|
|
1572
|
+
* Create a new image (conversation)
|
|
1573
|
+
*/
|
|
1574
|
+
static async create(config, context) {
|
|
1575
|
+
const now = Date.now();
|
|
1576
|
+
const imageId = _RuntimeImage.generateImageId();
|
|
1577
|
+
const sessionId = _RuntimeImage.generateSessionId();
|
|
1578
|
+
const record = {
|
|
1579
|
+
imageId,
|
|
1580
|
+
containerId: config.containerId,
|
|
1581
|
+
sessionId,
|
|
1582
|
+
name: config.name ?? "New Conversation",
|
|
1583
|
+
description: config.description,
|
|
1584
|
+
systemPrompt: config.systemPrompt,
|
|
1585
|
+
createdAt: now,
|
|
1586
|
+
updatedAt: now
|
|
1587
|
+
};
|
|
1588
|
+
await context.imageRepository.saveImage(record);
|
|
1589
|
+
await context.sessionRepository.saveSession({
|
|
1590
|
+
sessionId,
|
|
1591
|
+
imageId,
|
|
1592
|
+
containerId: config.containerId,
|
|
1593
|
+
createdAt: now,
|
|
1594
|
+
updatedAt: now
|
|
1595
|
+
});
|
|
1596
|
+
logger7.info("Image created", {
|
|
1597
|
+
imageId,
|
|
1598
|
+
sessionId,
|
|
1599
|
+
containerId: config.containerId,
|
|
1600
|
+
name: record.name
|
|
1601
|
+
});
|
|
1602
|
+
return new _RuntimeImage(record, context);
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Load an existing image from storage
|
|
1606
|
+
*/
|
|
1607
|
+
static async load(imageId, context) {
|
|
1608
|
+
const record = await context.imageRepository.findImageById(imageId);
|
|
1609
|
+
if (!record) {
|
|
1610
|
+
logger7.debug("Image not found", { imageId });
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
logger7.debug("Image loaded", { imageId, name: record.name });
|
|
1614
|
+
return new _RuntimeImage(record, context);
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* List all images in a container
|
|
1618
|
+
*/
|
|
1619
|
+
static async listByContainer(containerId, context) {
|
|
1620
|
+
return context.imageRepository.findImagesByContainerId(containerId);
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* List all images
|
|
1624
|
+
*/
|
|
1625
|
+
static async listAll(context) {
|
|
1626
|
+
return context.imageRepository.findAllImages();
|
|
1627
|
+
}
|
|
1628
|
+
// ==================== Instance Methods ====================
|
|
1629
|
+
/**
|
|
1630
|
+
* Get messages for this conversation
|
|
1631
|
+
*/
|
|
1632
|
+
async getMessages() {
|
|
1633
|
+
return this.context.sessionRepository.getMessages(this.sessionId);
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Update image metadata
|
|
1637
|
+
*/
|
|
1638
|
+
async update(updates) {
|
|
1639
|
+
const now = Date.now();
|
|
1640
|
+
const updatedRecord = {
|
|
1641
|
+
...this.record,
|
|
1642
|
+
name: updates.name ?? this.record.name,
|
|
1643
|
+
description: updates.description ?? this.record.description,
|
|
1644
|
+
updatedAt: now
|
|
1645
|
+
};
|
|
1646
|
+
await this.context.imageRepository.saveImage(updatedRecord);
|
|
1647
|
+
logger7.info("Image updated", { imageId: this.imageId, updates });
|
|
1648
|
+
return new _RuntimeImage(updatedRecord, this.context);
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Delete this image and its session
|
|
1652
|
+
*/
|
|
1653
|
+
async delete() {
|
|
1654
|
+
await this.context.sessionRepository.deleteSession(this.sessionId);
|
|
1655
|
+
await this.context.imageRepository.deleteImage(this.imageId);
|
|
1656
|
+
logger7.info("Image deleted", { imageId: this.imageId, sessionId: this.sessionId });
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Get the underlying record
|
|
1660
|
+
*/
|
|
1661
|
+
toRecord() {
|
|
1662
|
+
return { ...this.record };
|
|
1663
|
+
}
|
|
1664
|
+
// ==================== Private Helpers ====================
|
|
1665
|
+
static generateImageId() {
|
|
1666
|
+
const timestamp = Date.now().toString(36);
|
|
1667
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
1668
|
+
return `img_${timestamp}_${random}`;
|
|
1669
|
+
}
|
|
1670
|
+
static generateSessionId() {
|
|
1671
|
+
const timestamp = Date.now().toString(36);
|
|
1672
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
1673
|
+
return `sess_${timestamp}_${random}`;
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
|
|
1677
|
+
// src/internal/RuntimeContainer.ts
|
|
1678
|
+
var import_common8 = require("@agentxjs/common");
|
|
1679
|
+
var logger8 = (0, import_common8.createLogger)("runtime/RuntimeContainer");
|
|
1680
|
+
var RuntimeContainer = class _RuntimeContainer {
|
|
1681
|
+
containerId;
|
|
1682
|
+
createdAt;
|
|
1683
|
+
/** Map of agentId → RuntimeAgent */
|
|
1684
|
+
agents = /* @__PURE__ */ new Map();
|
|
1685
|
+
/** Map of imageId → agentId (for quick lookup) */
|
|
1686
|
+
imageToAgent = /* @__PURE__ */ new Map();
|
|
1687
|
+
context;
|
|
1688
|
+
constructor(containerId, createdAt, context) {
|
|
1689
|
+
this.containerId = containerId;
|
|
1690
|
+
this.createdAt = createdAt;
|
|
1691
|
+
this.context = context;
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* Create a new container and persist it
|
|
1695
|
+
*/
|
|
1696
|
+
static async create(containerId, context) {
|
|
1697
|
+
const now = Date.now();
|
|
1698
|
+
const record = {
|
|
1699
|
+
containerId,
|
|
1700
|
+
createdAt: now,
|
|
1701
|
+
updatedAt: now
|
|
1702
|
+
};
|
|
1703
|
+
await context.persistence.containers.saveContainer(record);
|
|
1704
|
+
const container = new _RuntimeContainer(containerId, now, context);
|
|
1705
|
+
context.bus.emit({
|
|
1706
|
+
type: "container_created",
|
|
1707
|
+
timestamp: now,
|
|
1708
|
+
source: "container",
|
|
1709
|
+
category: "lifecycle",
|
|
1710
|
+
intent: "notification",
|
|
1711
|
+
data: {
|
|
1712
|
+
containerId,
|
|
1713
|
+
createdAt: now
|
|
1714
|
+
},
|
|
1715
|
+
context: {
|
|
1716
|
+
containerId
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
logger8.info("Container created", { containerId });
|
|
1720
|
+
return container;
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Load an existing container from persistence
|
|
1724
|
+
*/
|
|
1725
|
+
static async load(containerId, context) {
|
|
1726
|
+
const record = await context.persistence.containers.findContainerById(containerId);
|
|
1727
|
+
if (!record) return null;
|
|
1728
|
+
logger8.info("Container loaded", { containerId });
|
|
1729
|
+
return new _RuntimeContainer(containerId, record.createdAt, context);
|
|
1730
|
+
}
|
|
1731
|
+
// ==================== Image → Agent Lifecycle ====================
|
|
1732
|
+
/**
|
|
1733
|
+
* Run an image - create or reuse an Agent for the given Image
|
|
1734
|
+
* @returns { agent, reused } - the agent and whether it was reused
|
|
1735
|
+
*/
|
|
1736
|
+
async runImage(image) {
|
|
1737
|
+
const existingAgentId = this.imageToAgent.get(image.imageId);
|
|
1738
|
+
if (existingAgentId) {
|
|
1739
|
+
const existingAgent = this.agents.get(existingAgentId);
|
|
1740
|
+
if (existingAgent) {
|
|
1741
|
+
logger8.info("Reusing existing agent for image", {
|
|
1742
|
+
containerId: this.containerId,
|
|
1743
|
+
imageId: image.imageId,
|
|
1744
|
+
agentId: existingAgentId
|
|
1745
|
+
});
|
|
1746
|
+
return { agent: existingAgent, reused: true };
|
|
1747
|
+
}
|
|
1748
|
+
this.imageToAgent.delete(image.imageId);
|
|
1749
|
+
}
|
|
1750
|
+
const agentId = this.generateAgentId();
|
|
1751
|
+
const sandbox = new RuntimeSandbox({
|
|
1752
|
+
agentId,
|
|
1753
|
+
containerId: this.containerId,
|
|
1754
|
+
basePath: this.context.basePath
|
|
1755
|
+
});
|
|
1756
|
+
await sandbox.initialize();
|
|
1757
|
+
const session = new RuntimeSession({
|
|
1758
|
+
sessionId: image.sessionId,
|
|
1759
|
+
imageId: image.imageId,
|
|
1760
|
+
containerId: this.containerId,
|
|
1761
|
+
repository: this.context.persistence.sessions,
|
|
1762
|
+
producer: this.context.bus.asProducer()
|
|
1763
|
+
});
|
|
1764
|
+
const agent = new RuntimeAgent({
|
|
1765
|
+
agentId,
|
|
1766
|
+
imageId: image.imageId,
|
|
1767
|
+
containerId: this.containerId,
|
|
1768
|
+
config: {
|
|
1769
|
+
name: image.name,
|
|
1770
|
+
description: image.description,
|
|
1771
|
+
systemPrompt: image.systemPrompt
|
|
1772
|
+
},
|
|
1773
|
+
bus: this.context.bus,
|
|
1774
|
+
sandbox,
|
|
1775
|
+
session,
|
|
1776
|
+
llmConfig: this.context.llmConfig,
|
|
1777
|
+
image,
|
|
1778
|
+
// Pass full image record for metadata access
|
|
1779
|
+
imageRepository: this.context.persistence.images
|
|
1780
|
+
});
|
|
1781
|
+
this.agents.set(agentId, agent);
|
|
1782
|
+
this.imageToAgent.set(image.imageId, agentId);
|
|
1783
|
+
this.context.bus.emit({
|
|
1784
|
+
type: "agent_registered",
|
|
1785
|
+
timestamp: Date.now(),
|
|
1786
|
+
source: "container",
|
|
1787
|
+
category: "lifecycle",
|
|
1788
|
+
intent: "notification",
|
|
1789
|
+
data: {
|
|
1790
|
+
containerId: this.containerId,
|
|
1791
|
+
agentId,
|
|
1792
|
+
definitionName: image.name,
|
|
1793
|
+
registeredAt: Date.now()
|
|
1794
|
+
},
|
|
1795
|
+
context: {
|
|
1796
|
+
containerId: this.containerId,
|
|
1797
|
+
agentId
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
logger8.info("Agent created for image", {
|
|
1801
|
+
containerId: this.containerId,
|
|
1802
|
+
imageId: image.imageId,
|
|
1803
|
+
agentId
|
|
1804
|
+
});
|
|
1805
|
+
return { agent, reused: false };
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Stop an image - destroy the Agent but keep the Image
|
|
1809
|
+
*/
|
|
1810
|
+
async stopImage(imageId) {
|
|
1811
|
+
const agentId = this.imageToAgent.get(imageId);
|
|
1812
|
+
if (!agentId) {
|
|
1813
|
+
logger8.debug("Image not running, nothing to stop", {
|
|
1814
|
+
imageId,
|
|
1815
|
+
containerId: this.containerId
|
|
1816
|
+
});
|
|
1817
|
+
return false;
|
|
1818
|
+
}
|
|
1819
|
+
logger8.info("Stopping image", { imageId, agentId, containerId: this.containerId });
|
|
1820
|
+
const success = await this.destroyAgent(agentId);
|
|
1821
|
+
if (success) {
|
|
1822
|
+
this.imageToAgent.delete(imageId);
|
|
1823
|
+
logger8.info("Image stopped", { imageId, agentId, containerId: this.containerId });
|
|
1824
|
+
}
|
|
1825
|
+
return success;
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Get agent ID for an image (if running)
|
|
1829
|
+
*/
|
|
1830
|
+
getAgentIdForImage(imageId) {
|
|
1831
|
+
return this.imageToAgent.get(imageId);
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Check if an image has a running agent
|
|
1835
|
+
*/
|
|
1836
|
+
isImageOnline(imageId) {
|
|
1837
|
+
const agentId = this.imageToAgent.get(imageId);
|
|
1838
|
+
return agentId !== void 0 && this.agents.has(agentId);
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Get imageId for an agent (reverse lookup)
|
|
1842
|
+
*/
|
|
1843
|
+
getImageIdForAgent(agentId) {
|
|
1844
|
+
for (const [imageId, mappedAgentId] of this.imageToAgent.entries()) {
|
|
1845
|
+
if (mappedAgentId === agentId) {
|
|
1846
|
+
return imageId;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return void 0;
|
|
1850
|
+
}
|
|
1851
|
+
getAgent(agentId) {
|
|
1852
|
+
return this.agents.get(agentId);
|
|
1853
|
+
}
|
|
1854
|
+
listAgents() {
|
|
1855
|
+
return Array.from(this.agents.values());
|
|
1856
|
+
}
|
|
1857
|
+
get agentCount() {
|
|
1858
|
+
return this.agents.size;
|
|
1859
|
+
}
|
|
1860
|
+
async destroyAgent(agentId) {
|
|
1861
|
+
const agent = this.agents.get(agentId);
|
|
1862
|
+
if (!agent) return false;
|
|
1863
|
+
await agent.destroy();
|
|
1864
|
+
this.agents.delete(agentId);
|
|
1865
|
+
this.context.bus.emit({
|
|
1866
|
+
type: "agent_unregistered",
|
|
1867
|
+
timestamp: Date.now(),
|
|
1868
|
+
source: "container",
|
|
1869
|
+
category: "lifecycle",
|
|
1870
|
+
intent: "notification",
|
|
1871
|
+
data: {
|
|
1872
|
+
containerId: this.containerId,
|
|
1873
|
+
agentId
|
|
1874
|
+
},
|
|
1875
|
+
context: {
|
|
1876
|
+
containerId: this.containerId,
|
|
1877
|
+
agentId
|
|
1878
|
+
}
|
|
1879
|
+
});
|
|
1880
|
+
logger8.info("Agent destroyed", { containerId: this.containerId, agentId });
|
|
1881
|
+
return true;
|
|
1882
|
+
}
|
|
1883
|
+
async destroyAllAgents() {
|
|
1884
|
+
const agentIds = Array.from(this.agents.keys());
|
|
1885
|
+
for (const agentId of agentIds) {
|
|
1886
|
+
await this.destroyAgent(agentId);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
// ==================== Container Lifecycle ====================
|
|
1890
|
+
async dispose() {
|
|
1891
|
+
const agentCount = this.agents.size;
|
|
1892
|
+
await this.destroyAllAgents();
|
|
1893
|
+
this.context.bus.emit({
|
|
1894
|
+
type: "container_destroyed",
|
|
1895
|
+
timestamp: Date.now(),
|
|
1896
|
+
source: "container",
|
|
1897
|
+
category: "lifecycle",
|
|
1898
|
+
intent: "notification",
|
|
1899
|
+
data: {
|
|
1900
|
+
containerId: this.containerId,
|
|
1901
|
+
agentCount
|
|
1902
|
+
},
|
|
1903
|
+
context: {
|
|
1904
|
+
containerId: this.containerId
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
this.context.onDisposed?.(this.containerId);
|
|
1908
|
+
logger8.info("Container disposed", { containerId: this.containerId, agentCount });
|
|
1909
|
+
}
|
|
1910
|
+
// ==================== Private Helpers ====================
|
|
1911
|
+
generateAgentId() {
|
|
1912
|
+
const timestamp = Date.now().toString(36);
|
|
1913
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
1914
|
+
return `agent_${timestamp}_${random}`;
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
|
|
1918
|
+
// src/internal/BaseEventHandler.ts
|
|
1919
|
+
var import_common9 = require("@agentxjs/common");
|
|
1920
|
+
var logger9 = (0, import_common9.createLogger)("runtime/BaseEventHandler");
|
|
1921
|
+
var BaseEventHandler = class {
|
|
1922
|
+
bus;
|
|
1923
|
+
unsubscribes = [];
|
|
1924
|
+
constructor(bus) {
|
|
1925
|
+
this.bus = bus;
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Safe execution wrapper for synchronous handlers
|
|
1929
|
+
*
|
|
1930
|
+
* Automatically catches errors and emits ErrorEvent.
|
|
1931
|
+
*/
|
|
1932
|
+
safeHandle(handler, context) {
|
|
1933
|
+
try {
|
|
1934
|
+
return handler();
|
|
1935
|
+
} catch (err) {
|
|
1936
|
+
this.handleError(err, context);
|
|
1937
|
+
return void 0;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Safe execution wrapper for asynchronous handlers
|
|
1942
|
+
*
|
|
1943
|
+
* Automatically catches errors and emits ErrorEvent.
|
|
1944
|
+
*/
|
|
1945
|
+
async safeHandleAsync(handler, context) {
|
|
1946
|
+
try {
|
|
1947
|
+
return await handler();
|
|
1948
|
+
} catch (err) {
|
|
1949
|
+
this.handleError(err, context);
|
|
1950
|
+
return void 0;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Handle error: log + emit ErrorEvent + optional callback
|
|
1955
|
+
*/
|
|
1956
|
+
handleError(err, context) {
|
|
1957
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1958
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
1959
|
+
logger9.error(`Error in ${context.operation || "handler"}`, {
|
|
1960
|
+
message,
|
|
1961
|
+
requestId: context.requestId,
|
|
1962
|
+
details: context.details
|
|
1963
|
+
});
|
|
1964
|
+
const errorEvent = {
|
|
1965
|
+
type: "system_error",
|
|
1966
|
+
timestamp: Date.now(),
|
|
1967
|
+
source: context.source || "command",
|
|
1968
|
+
category: "error",
|
|
1969
|
+
intent: "notification",
|
|
1970
|
+
data: {
|
|
1971
|
+
message,
|
|
1972
|
+
requestId: context.requestId,
|
|
1973
|
+
severity: context.severity || "error",
|
|
1974
|
+
details: {
|
|
1975
|
+
operation: context.operation,
|
|
1976
|
+
stack,
|
|
1977
|
+
...context.details
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1981
|
+
this.bus.emit(errorEvent);
|
|
1982
|
+
if (context.onError) {
|
|
1983
|
+
try {
|
|
1984
|
+
context.onError(err);
|
|
1985
|
+
} catch (callbackErr) {
|
|
1986
|
+
logger9.error("Error in onError callback", { error: callbackErr });
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Register subscription and track for cleanup
|
|
1992
|
+
*/
|
|
1993
|
+
subscribe(unsubscribe) {
|
|
1994
|
+
this.unsubscribes.push(unsubscribe);
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* Dispose handler and cleanup all subscriptions
|
|
1998
|
+
*/
|
|
1999
|
+
dispose() {
|
|
2000
|
+
for (const unsubscribe of this.unsubscribes) {
|
|
2001
|
+
unsubscribe();
|
|
2002
|
+
}
|
|
2003
|
+
this.unsubscribes = [];
|
|
2004
|
+
logger9.debug(`${this.constructor.name} disposed`);
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
|
|
2008
|
+
// src/internal/CommandHandler.ts
|
|
2009
|
+
var import_common10 = require("@agentxjs/common");
|
|
2010
|
+
var logger10 = (0, import_common10.createLogger)("runtime/CommandHandler");
|
|
2011
|
+
function createResponse(type, data) {
|
|
2012
|
+
return {
|
|
2013
|
+
type,
|
|
2014
|
+
timestamp: Date.now(),
|
|
2015
|
+
data,
|
|
2016
|
+
source: "command",
|
|
2017
|
+
category: "response",
|
|
2018
|
+
intent: "result"
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
function createSystemError(message, requestId, context, stack) {
|
|
2022
|
+
return {
|
|
2023
|
+
type: "system_error",
|
|
2024
|
+
timestamp: Date.now(),
|
|
2025
|
+
source: "command",
|
|
2026
|
+
category: "error",
|
|
2027
|
+
intent: "notification",
|
|
2028
|
+
data: {
|
|
2029
|
+
message,
|
|
2030
|
+
requestId,
|
|
2031
|
+
severity: "error",
|
|
2032
|
+
details: stack
|
|
2033
|
+
},
|
|
2034
|
+
context
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
var CommandHandler = class extends BaseEventHandler {
|
|
2038
|
+
ops;
|
|
2039
|
+
constructor(bus, operations) {
|
|
2040
|
+
super(bus);
|
|
2041
|
+
this.ops = operations;
|
|
2042
|
+
this.bindHandlers();
|
|
2043
|
+
logger10.debug("CommandHandler created");
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Log error and emit system_error event
|
|
2047
|
+
*/
|
|
2048
|
+
emitError(operation, err, requestId, context) {
|
|
2049
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
2050
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
2051
|
+
logger10.error(operation, {
|
|
2052
|
+
requestId,
|
|
2053
|
+
...context,
|
|
2054
|
+
error: errorMessage,
|
|
2055
|
+
stack
|
|
2056
|
+
});
|
|
2057
|
+
this.bus.emit(createSystemError(errorMessage, requestId, context, stack));
|
|
2058
|
+
}
|
|
2059
|
+
/**
|
|
2060
|
+
* Bind all command handlers to the bus
|
|
2061
|
+
*/
|
|
2062
|
+
bindHandlers() {
|
|
2063
|
+
this.subscribe(
|
|
2064
|
+
this.bus.onCommand("container_create_request", (event) => this.handleContainerCreate(event))
|
|
2065
|
+
);
|
|
2066
|
+
this.subscribe(
|
|
2067
|
+
this.bus.onCommand("container_get_request", (event) => this.handleContainerGet(event))
|
|
2068
|
+
);
|
|
2069
|
+
this.subscribe(
|
|
2070
|
+
this.bus.onCommand("container_list_request", (event) => this.handleContainerList(event))
|
|
2071
|
+
);
|
|
2072
|
+
this.subscribe(this.bus.onCommand("agent_get_request", (event) => this.handleAgentGet(event)));
|
|
2073
|
+
this.subscribe(
|
|
2074
|
+
this.bus.onCommand("agent_list_request", (event) => this.handleAgentList(event))
|
|
2075
|
+
);
|
|
2076
|
+
this.subscribe(
|
|
2077
|
+
this.bus.onCommand("agent_destroy_request", (event) => this.handleAgentDestroy(event))
|
|
2078
|
+
);
|
|
2079
|
+
this.subscribe(
|
|
2080
|
+
this.bus.onCommand("agent_destroy_all_request", (event) => this.handleAgentDestroyAll(event))
|
|
2081
|
+
);
|
|
2082
|
+
this.subscribe(
|
|
2083
|
+
this.bus.onCommand("message_send_request", (event) => this.handleMessageSend(event))
|
|
2084
|
+
);
|
|
2085
|
+
this.subscribe(
|
|
2086
|
+
this.bus.onCommand("agent_interrupt_request", (event) => this.handleAgentInterrupt(event))
|
|
2087
|
+
);
|
|
2088
|
+
this.subscribe(
|
|
2089
|
+
this.bus.onCommand("image_create_request", (event) => this.handleImageCreate(event))
|
|
2090
|
+
);
|
|
2091
|
+
this.subscribe(this.bus.onCommand("image_run_request", (event) => this.handleImageRun(event)));
|
|
2092
|
+
this.subscribe(
|
|
2093
|
+
this.bus.onCommand("image_stop_request", (event) => this.handleImageStop(event))
|
|
2094
|
+
);
|
|
2095
|
+
this.subscribe(
|
|
2096
|
+
this.bus.onCommand("image_update_request", (event) => this.handleImageUpdate(event))
|
|
2097
|
+
);
|
|
2098
|
+
this.subscribe(
|
|
2099
|
+
this.bus.onCommand("image_list_request", (event) => this.handleImageList(event))
|
|
2100
|
+
);
|
|
2101
|
+
this.subscribe(this.bus.onCommand("image_get_request", (event) => this.handleImageGet(event)));
|
|
2102
|
+
this.subscribe(
|
|
2103
|
+
this.bus.onCommand("image_delete_request", (event) => this.handleImageDelete(event))
|
|
2104
|
+
);
|
|
2105
|
+
this.subscribe(
|
|
2106
|
+
this.bus.onCommand("image_messages_request", (event) => this.handleImageMessages(event))
|
|
2107
|
+
);
|
|
2108
|
+
logger10.debug("Command handlers bound");
|
|
2109
|
+
}
|
|
2110
|
+
// ==================== Container Handlers ====================
|
|
2111
|
+
async handleContainerCreate(event) {
|
|
2112
|
+
const { requestId, containerId } = event.data;
|
|
2113
|
+
logger10.debug("Handling container_create_request", { requestId, containerId });
|
|
2114
|
+
try {
|
|
2115
|
+
await this.ops.createContainer(containerId);
|
|
2116
|
+
this.bus.emit(
|
|
2117
|
+
createResponse("container_create_response", {
|
|
2118
|
+
requestId,
|
|
2119
|
+
containerId
|
|
2120
|
+
})
|
|
2121
|
+
);
|
|
2122
|
+
} catch (err) {
|
|
2123
|
+
this.emitError("Failed to create container", err, requestId, { containerId });
|
|
2124
|
+
this.bus.emit(
|
|
2125
|
+
createResponse("container_create_response", {
|
|
2126
|
+
requestId,
|
|
2127
|
+
containerId,
|
|
2128
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2129
|
+
})
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
handleContainerGet(event) {
|
|
2134
|
+
const { requestId, containerId } = event.data;
|
|
2135
|
+
logger10.debug("Handling container_get_request", { requestId, containerId });
|
|
2136
|
+
const container = this.ops.getContainer(containerId);
|
|
2137
|
+
this.bus.emit(
|
|
2138
|
+
createResponse("container_get_response", {
|
|
2139
|
+
requestId,
|
|
2140
|
+
containerId: container?.containerId,
|
|
2141
|
+
exists: !!container
|
|
2142
|
+
})
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
handleContainerList(event) {
|
|
2146
|
+
const { requestId } = event.data;
|
|
2147
|
+
logger10.debug("Handling container_list_request", { requestId });
|
|
2148
|
+
const containers = this.ops.listContainers();
|
|
2149
|
+
this.bus.emit(
|
|
2150
|
+
createResponse("container_list_response", {
|
|
2151
|
+
requestId,
|
|
2152
|
+
containerIds: containers.map((c) => c.containerId)
|
|
2153
|
+
})
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
// ==================== Agent Handlers ====================
|
|
2157
|
+
handleAgentGet(event) {
|
|
2158
|
+
const { requestId, agentId } = event.data;
|
|
2159
|
+
logger10.debug("Handling agent_get_request", { requestId, agentId });
|
|
2160
|
+
const agent = this.ops.getAgent(agentId);
|
|
2161
|
+
this.bus.emit(
|
|
2162
|
+
createResponse("agent_get_response", {
|
|
2163
|
+
requestId,
|
|
2164
|
+
agentId: agent?.agentId,
|
|
2165
|
+
containerId: agent?.containerId,
|
|
2166
|
+
exists: !!agent
|
|
2167
|
+
})
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
handleAgentList(event) {
|
|
2171
|
+
const { requestId, containerId } = event.data;
|
|
2172
|
+
logger10.debug("Handling agent_list_request", { requestId, containerId });
|
|
2173
|
+
const agents = this.ops.listAgents(containerId);
|
|
2174
|
+
this.bus.emit(
|
|
2175
|
+
createResponse("agent_list_response", {
|
|
2176
|
+
requestId,
|
|
2177
|
+
agents: agents.map((a) => ({
|
|
2178
|
+
agentId: a.agentId,
|
|
2179
|
+
containerId: a.containerId,
|
|
2180
|
+
imageId: a.imageId
|
|
2181
|
+
}))
|
|
2182
|
+
})
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
async handleAgentDestroy(event) {
|
|
2186
|
+
const { requestId, agentId } = event.data;
|
|
2187
|
+
logger10.debug("Handling agent_destroy_request", { requestId, agentId });
|
|
2188
|
+
try {
|
|
2189
|
+
const success = await this.ops.destroyAgent(agentId);
|
|
2190
|
+
this.bus.emit(
|
|
2191
|
+
createResponse("agent_destroy_response", {
|
|
2192
|
+
requestId,
|
|
2193
|
+
agentId,
|
|
2194
|
+
success
|
|
2195
|
+
})
|
|
2196
|
+
);
|
|
2197
|
+
} catch (err) {
|
|
2198
|
+
this.emitError("Failed to destroy agent", err, requestId, { agentId });
|
|
2199
|
+
this.bus.emit(
|
|
2200
|
+
createResponse("agent_destroy_response", {
|
|
2201
|
+
requestId,
|
|
2202
|
+
agentId,
|
|
2203
|
+
success: false,
|
|
2204
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2205
|
+
})
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
async handleAgentDestroyAll(event) {
|
|
2210
|
+
const { requestId, containerId } = event.data;
|
|
2211
|
+
logger10.debug("Handling agent_destroy_all_request", { requestId, containerId });
|
|
2212
|
+
try {
|
|
2213
|
+
await this.ops.destroyAllAgents(containerId);
|
|
2214
|
+
this.bus.emit(
|
|
2215
|
+
createResponse("agent_destroy_all_response", {
|
|
2216
|
+
requestId,
|
|
2217
|
+
containerId
|
|
2218
|
+
})
|
|
2219
|
+
);
|
|
2220
|
+
} catch (err) {
|
|
2221
|
+
this.emitError("Failed to destroy all agents", err, requestId, { containerId });
|
|
2222
|
+
this.bus.emit(
|
|
2223
|
+
createResponse("agent_destroy_all_response", {
|
|
2224
|
+
requestId,
|
|
2225
|
+
containerId,
|
|
2226
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2227
|
+
})
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
async handleMessageSend(event) {
|
|
2232
|
+
const { requestId, imageId, agentId, content } = event.data;
|
|
2233
|
+
logger10.debug("Handling message_send_request", { requestId, imageId, agentId });
|
|
2234
|
+
try {
|
|
2235
|
+
const result = await this.ops.receiveMessage(imageId, agentId, content, requestId);
|
|
2236
|
+
this.bus.emit(
|
|
2237
|
+
createResponse("message_send_response", {
|
|
2238
|
+
requestId,
|
|
2239
|
+
imageId: result.imageId,
|
|
2240
|
+
agentId: result.agentId
|
|
2241
|
+
})
|
|
2242
|
+
);
|
|
2243
|
+
} catch (err) {
|
|
2244
|
+
this.emitError("Failed to send message", err, requestId, { imageId, agentId });
|
|
2245
|
+
this.bus.emit(
|
|
2246
|
+
createResponse("message_send_response", {
|
|
2247
|
+
requestId,
|
|
2248
|
+
imageId,
|
|
2249
|
+
agentId: agentId ?? "",
|
|
2250
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2251
|
+
})
|
|
2252
|
+
);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
handleAgentInterrupt(event) {
|
|
2256
|
+
const { requestId, imageId, agentId } = event.data;
|
|
2257
|
+
logger10.debug("Handling agent_interrupt_request", { requestId, imageId, agentId });
|
|
2258
|
+
try {
|
|
2259
|
+
const result = this.ops.interruptAgent(imageId, agentId, requestId);
|
|
2260
|
+
this.bus.emit(
|
|
2261
|
+
createResponse("agent_interrupt_response", {
|
|
2262
|
+
requestId,
|
|
2263
|
+
imageId: result.imageId,
|
|
2264
|
+
agentId: result.agentId
|
|
2265
|
+
})
|
|
2266
|
+
);
|
|
2267
|
+
} catch (err) {
|
|
2268
|
+
this.emitError("Failed to interrupt agent", err, requestId, { imageId, agentId });
|
|
2269
|
+
this.bus.emit(
|
|
2270
|
+
createResponse("agent_interrupt_response", {
|
|
2271
|
+
requestId,
|
|
2272
|
+
imageId,
|
|
2273
|
+
agentId,
|
|
2274
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2275
|
+
})
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
// ==================== Image Handlers ====================
|
|
2280
|
+
async handleImageCreate(event) {
|
|
2281
|
+
const { requestId, containerId, config } = event.data;
|
|
2282
|
+
logger10.debug("Handling image_create_request", { requestId, containerId });
|
|
2283
|
+
try {
|
|
2284
|
+
const record = await this.ops.createImage(containerId, config);
|
|
2285
|
+
this.bus.emit(
|
|
2286
|
+
createResponse("image_create_response", {
|
|
2287
|
+
requestId,
|
|
2288
|
+
record
|
|
2289
|
+
})
|
|
2290
|
+
);
|
|
2291
|
+
} catch (err) {
|
|
2292
|
+
this.emitError("Failed to create image", err, requestId, { containerId });
|
|
2293
|
+
this.bus.emit(
|
|
2294
|
+
createResponse("image_create_response", {
|
|
2295
|
+
requestId,
|
|
2296
|
+
record: null,
|
|
2297
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2298
|
+
})
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
async handleImageRun(event) {
|
|
2303
|
+
const { requestId, imageId } = event.data;
|
|
2304
|
+
logger10.debug("Handling image_run_request", { requestId, imageId });
|
|
2305
|
+
try {
|
|
2306
|
+
const result = await this.ops.runImage(imageId);
|
|
2307
|
+
this.bus.emit(
|
|
2308
|
+
createResponse("image_run_response", {
|
|
2309
|
+
requestId,
|
|
2310
|
+
imageId: result.imageId,
|
|
2311
|
+
agentId: result.agentId,
|
|
2312
|
+
reused: result.reused
|
|
2313
|
+
})
|
|
2314
|
+
);
|
|
2315
|
+
} catch (err) {
|
|
2316
|
+
this.emitError("Failed to run image", err, requestId, { imageId });
|
|
2317
|
+
this.bus.emit(
|
|
2318
|
+
createResponse("image_run_response", {
|
|
2319
|
+
requestId,
|
|
2320
|
+
imageId,
|
|
2321
|
+
agentId: "",
|
|
2322
|
+
reused: false,
|
|
2323
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2324
|
+
})
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
async handleImageStop(event) {
|
|
2329
|
+
const { requestId, imageId } = event.data;
|
|
2330
|
+
logger10.debug("Handling image_stop_request", { requestId, imageId });
|
|
2331
|
+
try {
|
|
2332
|
+
await this.ops.stopImage(imageId);
|
|
2333
|
+
this.bus.emit(
|
|
2334
|
+
createResponse("image_stop_response", {
|
|
2335
|
+
requestId,
|
|
2336
|
+
imageId
|
|
2337
|
+
})
|
|
2338
|
+
);
|
|
2339
|
+
} catch (err) {
|
|
2340
|
+
this.emitError("Failed to stop image", err, requestId, { imageId });
|
|
2341
|
+
this.bus.emit(
|
|
2342
|
+
createResponse("image_stop_response", {
|
|
2343
|
+
requestId,
|
|
2344
|
+
imageId,
|
|
2345
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2346
|
+
})
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
async handleImageUpdate(event) {
|
|
2351
|
+
const { requestId, imageId, updates } = event.data;
|
|
2352
|
+
logger10.debug("Handling image_update_request", { requestId, imageId });
|
|
2353
|
+
try {
|
|
2354
|
+
const record = await this.ops.updateImage(imageId, updates);
|
|
2355
|
+
this.bus.emit(
|
|
2356
|
+
createResponse("image_update_response", {
|
|
2357
|
+
requestId,
|
|
2358
|
+
record
|
|
2359
|
+
})
|
|
2360
|
+
);
|
|
2361
|
+
} catch (err) {
|
|
2362
|
+
this.emitError("Failed to update image", err, requestId, { imageId });
|
|
2363
|
+
this.bus.emit(
|
|
2364
|
+
createResponse("image_update_response", {
|
|
2365
|
+
requestId,
|
|
2366
|
+
record: null,
|
|
2367
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2368
|
+
})
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
async handleImageList(event) {
|
|
2373
|
+
const { requestId, containerId } = event.data;
|
|
2374
|
+
logger10.debug("Handling image_list_request", { requestId, containerId });
|
|
2375
|
+
try {
|
|
2376
|
+
const images = await this.ops.listImages(containerId);
|
|
2377
|
+
this.bus.emit(
|
|
2378
|
+
createResponse("image_list_response", {
|
|
2379
|
+
requestId,
|
|
2380
|
+
records: images
|
|
2381
|
+
})
|
|
2382
|
+
);
|
|
2383
|
+
} catch (err) {
|
|
2384
|
+
this.emitError("Failed to list images", err, requestId, { containerId });
|
|
2385
|
+
this.bus.emit(
|
|
2386
|
+
createResponse("image_list_response", {
|
|
2387
|
+
requestId,
|
|
2388
|
+
records: [],
|
|
2389
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2390
|
+
})
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
async handleImageGet(event) {
|
|
2395
|
+
const { requestId, imageId } = event.data;
|
|
2396
|
+
logger10.debug("Handling image_get_request", { requestId, imageId });
|
|
2397
|
+
try {
|
|
2398
|
+
const image = await this.ops.getImage(imageId);
|
|
2399
|
+
this.bus.emit(
|
|
2400
|
+
createResponse("image_get_response", {
|
|
2401
|
+
requestId,
|
|
2402
|
+
record: image
|
|
2403
|
+
})
|
|
2404
|
+
);
|
|
2405
|
+
} catch (err) {
|
|
2406
|
+
this.emitError("Failed to get image", err, requestId, { imageId });
|
|
2407
|
+
this.bus.emit(
|
|
2408
|
+
createResponse("image_get_response", {
|
|
2409
|
+
requestId,
|
|
2410
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2411
|
+
})
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
async handleImageDelete(event) {
|
|
2416
|
+
const { requestId, imageId } = event.data;
|
|
2417
|
+
logger10.debug("Handling image_delete_request", { requestId, imageId });
|
|
2418
|
+
try {
|
|
2419
|
+
await this.ops.deleteImage(imageId);
|
|
2420
|
+
this.bus.emit(
|
|
2421
|
+
createResponse("image_delete_response", {
|
|
2422
|
+
requestId,
|
|
2423
|
+
imageId
|
|
2424
|
+
})
|
|
2425
|
+
);
|
|
2426
|
+
} catch (err) {
|
|
2427
|
+
this.emitError("Failed to delete image", err, requestId, { imageId });
|
|
2428
|
+
this.bus.emit(
|
|
2429
|
+
createResponse("image_delete_response", {
|
|
2430
|
+
requestId,
|
|
2431
|
+
imageId,
|
|
2432
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2433
|
+
})
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
async handleImageMessages(event) {
|
|
2438
|
+
const { requestId, imageId } = event.data;
|
|
2439
|
+
logger10.info("Handling image_messages_request", { requestId, imageId });
|
|
2440
|
+
try {
|
|
2441
|
+
const messages = await this.ops.getImageMessages(imageId);
|
|
2442
|
+
logger10.info("Got messages for image", { imageId, count: messages.length });
|
|
2443
|
+
this.bus.emit(
|
|
2444
|
+
createResponse("image_messages_response", {
|
|
2445
|
+
requestId,
|
|
2446
|
+
imageId,
|
|
2447
|
+
messages
|
|
2448
|
+
})
|
|
2449
|
+
);
|
|
2450
|
+
logger10.info("Emitted image_messages_response", { requestId, imageId });
|
|
2451
|
+
} catch (err) {
|
|
2452
|
+
this.emitError("Failed to get image messages", err, requestId, { imageId });
|
|
2453
|
+
this.bus.emit(
|
|
2454
|
+
createResponse("image_messages_response", {
|
|
2455
|
+
requestId,
|
|
2456
|
+
imageId,
|
|
2457
|
+
messages: [],
|
|
2458
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2459
|
+
})
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
// Lifecycle is handled by BaseEventHandler.dispose()
|
|
2464
|
+
};
|
|
2465
|
+
|
|
2466
|
+
// src/RuntimeImpl.ts
|
|
2467
|
+
var import_common11 = require("@agentxjs/common");
|
|
2468
|
+
var import_node_os = require("os");
|
|
2469
|
+
var import_node_path2 = require("path");
|
|
2470
|
+
var logger11 = (0, import_common11.createLogger)("runtime/RuntimeImpl");
|
|
2471
|
+
var RuntimeImpl = class {
|
|
2472
|
+
persistence;
|
|
2473
|
+
llmProvider;
|
|
2474
|
+
bus;
|
|
2475
|
+
llmConfig;
|
|
2476
|
+
basePath;
|
|
2477
|
+
commandHandler;
|
|
2478
|
+
/** Container registry: containerId -> RuntimeContainer */
|
|
2479
|
+
containerRegistry = /* @__PURE__ */ new Map();
|
|
2480
|
+
constructor(config) {
|
|
2481
|
+
logger11.info("RuntimeImpl constructor start");
|
|
2482
|
+
this.persistence = config.persistence;
|
|
2483
|
+
this.llmProvider = config.llmProvider;
|
|
2484
|
+
this.basePath = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".agentx");
|
|
2485
|
+
logger11.info("Creating SystemBus");
|
|
2486
|
+
this.bus = new SystemBusImpl();
|
|
2487
|
+
this.llmConfig = this.llmProvider.provide();
|
|
2488
|
+
logger11.info("LLM config loaded", {
|
|
2489
|
+
hasApiKey: !!this.llmConfig.apiKey,
|
|
2490
|
+
model: this.llmConfig.model
|
|
2491
|
+
});
|
|
2492
|
+
logger11.info("Creating CommandHandler");
|
|
2493
|
+
this.commandHandler = new CommandHandler(this.bus, this.createRuntimeOperations());
|
|
2494
|
+
logger11.info("RuntimeImpl constructor done");
|
|
2495
|
+
}
|
|
2496
|
+
// ==================== SystemBus delegation ====================
|
|
2497
|
+
emit(event) {
|
|
2498
|
+
this.bus.emit(event);
|
|
2499
|
+
}
|
|
2500
|
+
emitBatch(events) {
|
|
2501
|
+
this.bus.emitBatch(events);
|
|
2502
|
+
}
|
|
2503
|
+
on(typeOrTypes, handler, options) {
|
|
2504
|
+
return this.bus.on(typeOrTypes, handler, options);
|
|
2505
|
+
}
|
|
2506
|
+
onAny(handler, options) {
|
|
2507
|
+
return this.bus.onAny(handler, options);
|
|
2508
|
+
}
|
|
2509
|
+
once(type, handler) {
|
|
2510
|
+
return this.bus.once(type, handler);
|
|
2511
|
+
}
|
|
2512
|
+
onCommand(type, handler) {
|
|
2513
|
+
return this.bus.onCommand(type, handler);
|
|
2514
|
+
}
|
|
2515
|
+
emitCommand(type, data) {
|
|
2516
|
+
this.bus.emitCommand(type, data);
|
|
2517
|
+
}
|
|
2518
|
+
request(type, data, timeout) {
|
|
2519
|
+
return this.bus.request(type, data, timeout);
|
|
2520
|
+
}
|
|
2521
|
+
asConsumer() {
|
|
2522
|
+
return this.bus.asConsumer();
|
|
2523
|
+
}
|
|
2524
|
+
asProducer() {
|
|
2525
|
+
return this.bus.asProducer();
|
|
2526
|
+
}
|
|
2527
|
+
destroy() {
|
|
2528
|
+
this.bus.destroy();
|
|
2529
|
+
}
|
|
2530
|
+
// ==================== Runtime Operations (for CommandHandler) ====================
|
|
2531
|
+
createRuntimeOperations() {
|
|
2532
|
+
return {
|
|
2533
|
+
// Container operations
|
|
2534
|
+
createContainer: async (containerId) => {
|
|
2535
|
+
const container = await this.getOrCreateContainer(containerId);
|
|
2536
|
+
return { containerId: container.containerId };
|
|
2537
|
+
},
|
|
2538
|
+
getContainer: (containerId) => {
|
|
2539
|
+
const container = this.containerRegistry.get(containerId);
|
|
2540
|
+
return container ? { containerId: container.containerId } : void 0;
|
|
2541
|
+
},
|
|
2542
|
+
listContainers: () => {
|
|
2543
|
+
return Array.from(this.containerRegistry.values()).map((c) => ({
|
|
2544
|
+
containerId: c.containerId
|
|
2545
|
+
}));
|
|
2546
|
+
},
|
|
2547
|
+
// Agent operations (by agentId)
|
|
2548
|
+
getAgent: (agentId) => {
|
|
2549
|
+
const agent = this.findAgent(agentId);
|
|
2550
|
+
if (!agent) return void 0;
|
|
2551
|
+
const imageId = this.findImageIdForAgent(agentId);
|
|
2552
|
+
return { agentId: agent.agentId, containerId: agent.containerId, imageId: imageId ?? "" };
|
|
2553
|
+
},
|
|
2554
|
+
listAgents: (containerId) => {
|
|
2555
|
+
const container = this.containerRegistry.get(containerId);
|
|
2556
|
+
if (!container) return [];
|
|
2557
|
+
return container.listAgents().map((a) => {
|
|
2558
|
+
const imageId = this.findImageIdForAgent(a.agentId);
|
|
2559
|
+
return { agentId: a.agentId, containerId: a.containerId, imageId: imageId ?? "" };
|
|
2560
|
+
});
|
|
2561
|
+
},
|
|
2562
|
+
destroyAgent: async (agentId) => {
|
|
2563
|
+
for (const container of this.containerRegistry.values()) {
|
|
2564
|
+
if (container.getAgent(agentId)) {
|
|
2565
|
+
return container.destroyAgent(agentId);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
return false;
|
|
2569
|
+
},
|
|
2570
|
+
destroyAllAgents: async (containerId) => {
|
|
2571
|
+
const container = this.containerRegistry.get(containerId);
|
|
2572
|
+
await container?.destroyAllAgents();
|
|
2573
|
+
},
|
|
2574
|
+
// Agent operations (by imageId - with auto-activation)
|
|
2575
|
+
receiveMessage: async (imageId, agentId, content, requestId) => {
|
|
2576
|
+
if (imageId) {
|
|
2577
|
+
logger11.debug("Receiving message by imageId", {
|
|
2578
|
+
imageId,
|
|
2579
|
+
contentLength: content.length,
|
|
2580
|
+
requestId
|
|
2581
|
+
});
|
|
2582
|
+
const record = await this.persistence.images.findImageById(imageId);
|
|
2583
|
+
if (!record) throw new Error(`Image not found: ${imageId}`);
|
|
2584
|
+
const container = await this.getOrCreateContainer(record.containerId);
|
|
2585
|
+
const { agent, reused } = await container.runImage(record);
|
|
2586
|
+
logger11.info("Message routed to agent", {
|
|
2587
|
+
imageId,
|
|
2588
|
+
agentId: agent.agentId,
|
|
2589
|
+
reused,
|
|
2590
|
+
requestId
|
|
2591
|
+
});
|
|
2592
|
+
await agent.receive(content, requestId);
|
|
2593
|
+
return { agentId: agent.agentId, imageId };
|
|
2594
|
+
}
|
|
2595
|
+
if (agentId) {
|
|
2596
|
+
logger11.debug("Receiving message by agentId (legacy)", {
|
|
2597
|
+
agentId,
|
|
2598
|
+
contentLength: content.length,
|
|
2599
|
+
requestId
|
|
2600
|
+
});
|
|
2601
|
+
const agent = this.findAgent(agentId);
|
|
2602
|
+
if (!agent) throw new Error(`Agent not found: ${agentId}`);
|
|
2603
|
+
await agent.receive(content, requestId);
|
|
2604
|
+
const foundImageId = this.findImageIdForAgent(agentId);
|
|
2605
|
+
return { agentId, imageId: foundImageId };
|
|
2606
|
+
}
|
|
2607
|
+
throw new Error("Either imageId or agentId must be provided");
|
|
2608
|
+
},
|
|
2609
|
+
interruptAgent: (imageId, agentId, requestId) => {
|
|
2610
|
+
if (imageId) {
|
|
2611
|
+
const foundAgentId = this.findAgentIdForImage(imageId);
|
|
2612
|
+
if (!foundAgentId) {
|
|
2613
|
+
logger11.debug("Image is offline, nothing to interrupt", { imageId });
|
|
2614
|
+
return { imageId, agentId: void 0 };
|
|
2615
|
+
}
|
|
2616
|
+
const agent = this.findAgent(foundAgentId);
|
|
2617
|
+
if (agent) {
|
|
2618
|
+
logger11.info("Interrupting agent by imageId", {
|
|
2619
|
+
imageId,
|
|
2620
|
+
agentId: foundAgentId,
|
|
2621
|
+
requestId
|
|
2622
|
+
});
|
|
2623
|
+
agent.interrupt(requestId);
|
|
2624
|
+
}
|
|
2625
|
+
return { imageId, agentId: foundAgentId };
|
|
2626
|
+
}
|
|
2627
|
+
if (agentId) {
|
|
2628
|
+
const agent = this.findAgent(agentId);
|
|
2629
|
+
if (!agent) throw new Error(`Agent not found: ${agentId}`);
|
|
2630
|
+
logger11.info("Interrupting agent by agentId (legacy)", { agentId, requestId });
|
|
2631
|
+
agent.interrupt(requestId);
|
|
2632
|
+
const foundImageId = this.findImageIdForAgent(agentId);
|
|
2633
|
+
return { agentId, imageId: foundImageId };
|
|
2634
|
+
}
|
|
2635
|
+
throw new Error("Either imageId or agentId must be provided");
|
|
2636
|
+
},
|
|
2637
|
+
// Image operations (new model)
|
|
2638
|
+
createImage: async (containerId, config) => {
|
|
2639
|
+
logger11.debug("Creating image", { containerId, name: config.name });
|
|
2640
|
+
await this.getOrCreateContainer(containerId);
|
|
2641
|
+
const image = await RuntimeImage.create(
|
|
2642
|
+
{ containerId, ...config },
|
|
2643
|
+
this.createImageContext()
|
|
2644
|
+
);
|
|
2645
|
+
logger11.info("Image created via RuntimeOps", { imageId: image.imageId, containerId });
|
|
2646
|
+
return this.toImageListItemResult(image.toRecord(), false);
|
|
2647
|
+
},
|
|
2648
|
+
runImage: async (imageId) => {
|
|
2649
|
+
logger11.debug("Running image", { imageId });
|
|
2650
|
+
const record = await this.persistence.images.findImageById(imageId);
|
|
2651
|
+
if (!record) throw new Error(`Image not found: ${imageId}`);
|
|
2652
|
+
const container = await this.getOrCreateContainer(record.containerId);
|
|
2653
|
+
const { agent, reused } = await container.runImage(record);
|
|
2654
|
+
logger11.info("Image running", { imageId, agentId: agent.agentId, reused });
|
|
2655
|
+
return { imageId, agentId: agent.agentId, reused };
|
|
2656
|
+
},
|
|
2657
|
+
stopImage: async (imageId) => {
|
|
2658
|
+
logger11.debug("Stopping image", { imageId });
|
|
2659
|
+
const record = await this.persistence.images.findImageById(imageId);
|
|
2660
|
+
if (!record) throw new Error(`Image not found: ${imageId}`);
|
|
2661
|
+
const container = this.containerRegistry.get(record.containerId);
|
|
2662
|
+
if (container) {
|
|
2663
|
+
await container.stopImage(imageId);
|
|
2664
|
+
logger11.info("Image stopped via RuntimeOps", { imageId });
|
|
2665
|
+
}
|
|
2666
|
+
},
|
|
2667
|
+
updateImage: async (imageId, updates) => {
|
|
2668
|
+
const image = await RuntimeImage.load(imageId, this.createImageContext());
|
|
2669
|
+
if (!image) throw new Error(`Image not found: ${imageId}`);
|
|
2670
|
+
const updatedImage = await image.update(updates);
|
|
2671
|
+
const online = this.isImageOnline(imageId);
|
|
2672
|
+
return this.toImageListItemResult(updatedImage.toRecord(), online);
|
|
2673
|
+
},
|
|
2674
|
+
listImages: async (containerId) => {
|
|
2675
|
+
const records = containerId ? await RuntimeImage.listByContainer(containerId, this.createImageContext()) : await RuntimeImage.listAll(this.createImageContext());
|
|
2676
|
+
return records.map((r) => {
|
|
2677
|
+
const online = this.isImageOnline(r.imageId);
|
|
2678
|
+
return this.toImageListItemResult(r, online);
|
|
2679
|
+
});
|
|
2680
|
+
},
|
|
2681
|
+
getImage: async (imageId) => {
|
|
2682
|
+
const record = await this.persistence.images.findImageById(imageId);
|
|
2683
|
+
if (!record) return null;
|
|
2684
|
+
const online = this.isImageOnline(imageId);
|
|
2685
|
+
return this.toImageListItemResult(record, online);
|
|
2686
|
+
},
|
|
2687
|
+
deleteImage: async (imageId) => {
|
|
2688
|
+
logger11.debug("Deleting image", { imageId });
|
|
2689
|
+
const agentId = this.findAgentIdForImage(imageId);
|
|
2690
|
+
if (agentId) {
|
|
2691
|
+
logger11.debug("Stopping running agent before delete", { imageId, agentId });
|
|
2692
|
+
for (const container of this.containerRegistry.values()) {
|
|
2693
|
+
if (container.getAgent(agentId)) {
|
|
2694
|
+
await container.destroyAgent(agentId);
|
|
2695
|
+
break;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
const image = await RuntimeImage.load(imageId, this.createImageContext());
|
|
2700
|
+
if (image) {
|
|
2701
|
+
await image.delete();
|
|
2702
|
+
logger11.info("Image deleted via RuntimeOps", { imageId });
|
|
2703
|
+
}
|
|
2704
|
+
},
|
|
2705
|
+
getImageMessages: async (imageId) => {
|
|
2706
|
+
logger11.debug("Getting messages for image", { imageId });
|
|
2707
|
+
const image = await RuntimeImage.load(imageId, this.createImageContext());
|
|
2708
|
+
if (!image) {
|
|
2709
|
+
throw new Error(`Image not found: ${imageId}`);
|
|
2710
|
+
}
|
|
2711
|
+
const messages = await image.getMessages();
|
|
2712
|
+
logger11.debug("Got messages from storage", { imageId, count: messages.length });
|
|
2713
|
+
return messages.map((m) => {
|
|
2714
|
+
let content;
|
|
2715
|
+
let role = m.role;
|
|
2716
|
+
let errorCode;
|
|
2717
|
+
if (m.subtype === "user" || m.subtype === "assistant") {
|
|
2718
|
+
content = m.content;
|
|
2719
|
+
} else if (m.subtype === "tool-call") {
|
|
2720
|
+
content = m.toolCall;
|
|
2721
|
+
role = "tool_call";
|
|
2722
|
+
} else if (m.subtype === "tool-result") {
|
|
2723
|
+
content = m.toolResult;
|
|
2724
|
+
role = "tool_result";
|
|
2725
|
+
} else if (m.subtype === "error") {
|
|
2726
|
+
content = m.content;
|
|
2727
|
+
role = "error";
|
|
2728
|
+
errorCode = m.errorCode;
|
|
2729
|
+
}
|
|
2730
|
+
return {
|
|
2731
|
+
id: m.id,
|
|
2732
|
+
role,
|
|
2733
|
+
content,
|
|
2734
|
+
timestamp: m.timestamp,
|
|
2735
|
+
errorCode
|
|
2736
|
+
};
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
// ==================== Internal Helpers ====================
|
|
2742
|
+
async getOrCreateContainer(containerId) {
|
|
2743
|
+
const existing = this.containerRegistry.get(containerId);
|
|
2744
|
+
if (existing) return existing;
|
|
2745
|
+
const loaded = await RuntimeContainer.load(containerId, this.createContainerContext());
|
|
2746
|
+
if (loaded) {
|
|
2747
|
+
this.containerRegistry.set(containerId, loaded);
|
|
2748
|
+
return loaded;
|
|
2749
|
+
}
|
|
2750
|
+
const container = await RuntimeContainer.create(containerId, this.createContainerContext());
|
|
2751
|
+
this.containerRegistry.set(containerId, container);
|
|
2752
|
+
return container;
|
|
2753
|
+
}
|
|
2754
|
+
findAgent(agentId) {
|
|
2755
|
+
for (const container of this.containerRegistry.values()) {
|
|
2756
|
+
const agent = container.getAgent(agentId);
|
|
2757
|
+
if (agent) return agent;
|
|
2758
|
+
}
|
|
2759
|
+
return void 0;
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Find imageId for a given agentId (reverse lookup)
|
|
2763
|
+
*/
|
|
2764
|
+
findImageIdForAgent(agentId) {
|
|
2765
|
+
for (const container of this.containerRegistry.values()) {
|
|
2766
|
+
const imageId = container.getImageIdForAgent(agentId);
|
|
2767
|
+
if (imageId) return imageId;
|
|
2768
|
+
}
|
|
2769
|
+
return void 0;
|
|
2770
|
+
}
|
|
2771
|
+
/**
|
|
2772
|
+
* Find agentId for a given imageId
|
|
2773
|
+
*/
|
|
2774
|
+
findAgentIdForImage(imageId) {
|
|
2775
|
+
for (const container of this.containerRegistry.values()) {
|
|
2776
|
+
const agentId = container.getAgentIdForImage(imageId);
|
|
2777
|
+
if (agentId) return agentId;
|
|
2778
|
+
}
|
|
2779
|
+
return void 0;
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
* Check if an image has a running agent
|
|
2783
|
+
*/
|
|
2784
|
+
isImageOnline(imageId) {
|
|
2785
|
+
for (const container of this.containerRegistry.values()) {
|
|
2786
|
+
if (container.isImageOnline(imageId)) {
|
|
2787
|
+
return true;
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
return false;
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Convert ImageRecord to ImageListItemResult
|
|
2794
|
+
*/
|
|
2795
|
+
toImageListItemResult(record, online) {
|
|
2796
|
+
const agentId = online ? this.findAgentIdForImage(record.imageId) : void 0;
|
|
2797
|
+
return {
|
|
2798
|
+
imageId: record.imageId,
|
|
2799
|
+
containerId: record.containerId,
|
|
2800
|
+
sessionId: record.sessionId,
|
|
2801
|
+
name: record.name,
|
|
2802
|
+
description: record.description,
|
|
2803
|
+
systemPrompt: record.systemPrompt,
|
|
2804
|
+
createdAt: record.createdAt,
|
|
2805
|
+
updatedAt: record.updatedAt,
|
|
2806
|
+
online,
|
|
2807
|
+
agentId
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
createContainerContext() {
|
|
2811
|
+
return {
|
|
2812
|
+
persistence: this.persistence,
|
|
2813
|
+
bus: this.bus,
|
|
2814
|
+
llmConfig: this.llmConfig,
|
|
2815
|
+
basePath: this.basePath,
|
|
2816
|
+
onDisposed: (containerId) => {
|
|
2817
|
+
this.containerRegistry.delete(containerId);
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
createImageContext() {
|
|
2822
|
+
return {
|
|
2823
|
+
imageRepository: this.persistence.images,
|
|
2824
|
+
sessionRepository: this.persistence.sessions
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
// ==================== Lifecycle ====================
|
|
2828
|
+
async dispose() {
|
|
2829
|
+
logger11.info("Disposing RuntimeImpl");
|
|
2830
|
+
this.commandHandler.dispose();
|
|
2831
|
+
for (const container of this.containerRegistry.values()) {
|
|
2832
|
+
await container.dispose();
|
|
2833
|
+
}
|
|
2834
|
+
this.bus.destroy();
|
|
2835
|
+
this.containerRegistry.clear();
|
|
2836
|
+
logger11.info("RuntimeImpl disposed");
|
|
2837
|
+
}
|
|
2838
|
+
};
|
|
2839
|
+
|
|
2840
|
+
// src/createRuntime.ts
|
|
2841
|
+
function createRuntime(config) {
|
|
2842
|
+
return new RuntimeImpl(config);
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
// src/internal/persistence/PersistenceImpl.ts
|
|
2846
|
+
var import_unstorage = require("unstorage");
|
|
2847
|
+
var import_common15 = require("@agentxjs/common");
|
|
2848
|
+
|
|
2849
|
+
// src/internal/persistence/repository/StorageImageRepository.ts
|
|
2850
|
+
var import_common12 = require("@agentxjs/common");
|
|
2851
|
+
var logger12 = (0, import_common12.createLogger)("persistence/ImageRepository");
|
|
2852
|
+
var PREFIX = "images";
|
|
2853
|
+
var INDEX_BY_NAME = "idx:images:name";
|
|
2854
|
+
var INDEX_BY_CONTAINER = "idx:images:container";
|
|
2855
|
+
var StorageImageRepository = class {
|
|
2856
|
+
constructor(storage) {
|
|
2857
|
+
this.storage = storage;
|
|
2858
|
+
}
|
|
2859
|
+
key(imageId) {
|
|
2860
|
+
return `${PREFIX}:${imageId}`;
|
|
2861
|
+
}
|
|
2862
|
+
nameIndexKey(name, imageId) {
|
|
2863
|
+
return `${INDEX_BY_NAME}:${name}:${imageId}`;
|
|
2864
|
+
}
|
|
2865
|
+
containerIndexKey(containerId, imageId) {
|
|
2866
|
+
return `${INDEX_BY_CONTAINER}:${containerId}:${imageId}`;
|
|
2867
|
+
}
|
|
2868
|
+
async saveImage(record) {
|
|
2869
|
+
await this.storage.setItem(this.key(record.imageId), record);
|
|
2870
|
+
await this.storage.setItem(this.nameIndexKey(record.name, record.imageId), record.imageId);
|
|
2871
|
+
await this.storage.setItem(
|
|
2872
|
+
this.containerIndexKey(record.containerId, record.imageId),
|
|
2873
|
+
record.imageId
|
|
2874
|
+
);
|
|
2875
|
+
logger12.debug("Image saved", { imageId: record.imageId });
|
|
2876
|
+
}
|
|
2877
|
+
async findImageById(imageId) {
|
|
2878
|
+
const record = await this.storage.getItem(this.key(imageId));
|
|
2879
|
+
return record ?? null;
|
|
2880
|
+
}
|
|
2881
|
+
async findAllImages() {
|
|
2882
|
+
const keys = await this.storage.getKeys(PREFIX);
|
|
2883
|
+
const records = [];
|
|
2884
|
+
for (const key of keys) {
|
|
2885
|
+
if (key.startsWith("idx:")) continue;
|
|
2886
|
+
const record = await this.storage.getItem(key);
|
|
2887
|
+
if (record) {
|
|
2888
|
+
records.push(record);
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
2892
|
+
}
|
|
2893
|
+
async findImagesByName(name) {
|
|
2894
|
+
const indexPrefix = `${INDEX_BY_NAME}:${name}`;
|
|
2895
|
+
const keys = await this.storage.getKeys(indexPrefix);
|
|
2896
|
+
const records = [];
|
|
2897
|
+
for (const key of keys) {
|
|
2898
|
+
const imageId = await this.storage.getItem(key);
|
|
2899
|
+
if (imageId) {
|
|
2900
|
+
const record = await this.findImageById(imageId);
|
|
2901
|
+
if (record) {
|
|
2902
|
+
records.push(record);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
2907
|
+
}
|
|
2908
|
+
async findImagesByContainerId(containerId) {
|
|
2909
|
+
const indexPrefix = `${INDEX_BY_CONTAINER}:${containerId}`;
|
|
2910
|
+
const keys = await this.storage.getKeys(indexPrefix);
|
|
2911
|
+
const records = [];
|
|
2912
|
+
for (const key of keys) {
|
|
2913
|
+
const imageId = await this.storage.getItem(key);
|
|
2914
|
+
if (imageId) {
|
|
2915
|
+
const record = await this.findImageById(imageId);
|
|
2916
|
+
if (record) {
|
|
2917
|
+
records.push(record);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
2922
|
+
}
|
|
2923
|
+
async deleteImage(imageId) {
|
|
2924
|
+
const record = await this.findImageById(imageId);
|
|
2925
|
+
await this.storage.removeItem(this.key(imageId));
|
|
2926
|
+
if (record) {
|
|
2927
|
+
await this.storage.removeItem(this.nameIndexKey(record.name, imageId));
|
|
2928
|
+
await this.storage.removeItem(this.containerIndexKey(record.containerId, imageId));
|
|
2929
|
+
}
|
|
2930
|
+
logger12.debug("Image deleted", { imageId });
|
|
2931
|
+
}
|
|
2932
|
+
async imageExists(imageId) {
|
|
2933
|
+
return await this.storage.hasItem(this.key(imageId));
|
|
2934
|
+
}
|
|
2935
|
+
async updateMetadata(imageId, metadata) {
|
|
2936
|
+
const record = await this.findImageById(imageId);
|
|
2937
|
+
if (!record) {
|
|
2938
|
+
throw new Error(`Image not found: ${imageId}`);
|
|
2939
|
+
}
|
|
2940
|
+
const updatedRecord = {
|
|
2941
|
+
...record,
|
|
2942
|
+
metadata: {
|
|
2943
|
+
...record.metadata,
|
|
2944
|
+
...metadata
|
|
2945
|
+
},
|
|
2946
|
+
updatedAt: Date.now()
|
|
2947
|
+
};
|
|
2948
|
+
await this.storage.setItem(this.key(imageId), updatedRecord);
|
|
2949
|
+
logger12.debug("Image metadata updated", { imageId, metadata });
|
|
2950
|
+
}
|
|
2951
|
+
};
|
|
2952
|
+
|
|
2953
|
+
// src/internal/persistence/repository/StorageContainerRepository.ts
|
|
2954
|
+
var import_common13 = require("@agentxjs/common");
|
|
2955
|
+
var logger13 = (0, import_common13.createLogger)("persistence/ContainerRepository");
|
|
2956
|
+
var PREFIX2 = "containers";
|
|
2957
|
+
var StorageContainerRepository = class {
|
|
2958
|
+
constructor(storage) {
|
|
2959
|
+
this.storage = storage;
|
|
2960
|
+
}
|
|
2961
|
+
key(containerId) {
|
|
2962
|
+
return `${PREFIX2}:${containerId}`;
|
|
2963
|
+
}
|
|
2964
|
+
async saveContainer(record) {
|
|
2965
|
+
await this.storage.setItem(this.key(record.containerId), record);
|
|
2966
|
+
logger13.debug("Container saved", { containerId: record.containerId });
|
|
2967
|
+
}
|
|
2968
|
+
async findContainerById(containerId) {
|
|
2969
|
+
const record = await this.storage.getItem(this.key(containerId));
|
|
2970
|
+
return record ?? null;
|
|
2971
|
+
}
|
|
2972
|
+
async findAllContainers() {
|
|
2973
|
+
const keys = await this.storage.getKeys(PREFIX2);
|
|
2974
|
+
const records = [];
|
|
2975
|
+
for (const key of keys) {
|
|
2976
|
+
const record = await this.storage.getItem(key);
|
|
2977
|
+
if (record) {
|
|
2978
|
+
records.push(record);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
2982
|
+
}
|
|
2983
|
+
async deleteContainer(containerId) {
|
|
2984
|
+
await this.storage.removeItem(this.key(containerId));
|
|
2985
|
+
logger13.debug("Container deleted", { containerId });
|
|
2986
|
+
}
|
|
2987
|
+
async containerExists(containerId) {
|
|
2988
|
+
return await this.storage.hasItem(this.key(containerId));
|
|
2989
|
+
}
|
|
2990
|
+
};
|
|
2991
|
+
|
|
2992
|
+
// src/internal/persistence/repository/StorageSessionRepository.ts
|
|
2993
|
+
var import_common14 = require("@agentxjs/common");
|
|
2994
|
+
var logger14 = (0, import_common14.createLogger)("persistence/SessionRepository");
|
|
2995
|
+
var PREFIX3 = "sessions";
|
|
2996
|
+
var MESSAGES_PREFIX = "messages";
|
|
2997
|
+
var INDEX_BY_IMAGE = "idx:sessions:image";
|
|
2998
|
+
var INDEX_BY_CONTAINER2 = "idx:sessions:container";
|
|
2999
|
+
var StorageSessionRepository = class {
|
|
3000
|
+
constructor(storage) {
|
|
3001
|
+
this.storage = storage;
|
|
3002
|
+
}
|
|
3003
|
+
key(sessionId) {
|
|
3004
|
+
return `${PREFIX3}:${sessionId}`;
|
|
3005
|
+
}
|
|
3006
|
+
messagesKey(sessionId) {
|
|
3007
|
+
return `${MESSAGES_PREFIX}:${sessionId}`;
|
|
3008
|
+
}
|
|
3009
|
+
imageIndexKey(imageId, sessionId) {
|
|
3010
|
+
return `${INDEX_BY_IMAGE}:${imageId}:${sessionId}`;
|
|
3011
|
+
}
|
|
3012
|
+
containerIndexKey(containerId, sessionId) {
|
|
3013
|
+
return `${INDEX_BY_CONTAINER2}:${containerId}:${sessionId}`;
|
|
3014
|
+
}
|
|
3015
|
+
async saveSession(record) {
|
|
3016
|
+
await this.storage.setItem(this.key(record.sessionId), record);
|
|
3017
|
+
await this.storage.setItem(
|
|
3018
|
+
this.imageIndexKey(record.imageId, record.sessionId),
|
|
3019
|
+
record.sessionId
|
|
3020
|
+
);
|
|
3021
|
+
await this.storage.setItem(
|
|
3022
|
+
this.containerIndexKey(record.containerId, record.sessionId),
|
|
3023
|
+
record.sessionId
|
|
3024
|
+
);
|
|
3025
|
+
logger14.debug("Session saved", { sessionId: record.sessionId });
|
|
3026
|
+
}
|
|
3027
|
+
async findSessionById(sessionId) {
|
|
3028
|
+
const record = await this.storage.getItem(this.key(sessionId));
|
|
3029
|
+
return record ?? null;
|
|
3030
|
+
}
|
|
3031
|
+
async findSessionByImageId(imageId) {
|
|
3032
|
+
const indexPrefix = `${INDEX_BY_IMAGE}:${imageId}`;
|
|
3033
|
+
const keys = await this.storage.getKeys(indexPrefix);
|
|
3034
|
+
if (keys.length === 0) return null;
|
|
3035
|
+
const sessionId = await this.storage.getItem(keys[0]);
|
|
3036
|
+
if (!sessionId) return null;
|
|
3037
|
+
return this.findSessionById(sessionId);
|
|
3038
|
+
}
|
|
3039
|
+
async findSessionsByContainerId(containerId) {
|
|
3040
|
+
const indexPrefix = `${INDEX_BY_CONTAINER2}:${containerId}`;
|
|
3041
|
+
const keys = await this.storage.getKeys(indexPrefix);
|
|
3042
|
+
const records = [];
|
|
3043
|
+
for (const key of keys) {
|
|
3044
|
+
const sessionId = await this.storage.getItem(key);
|
|
3045
|
+
if (sessionId) {
|
|
3046
|
+
const record = await this.findSessionById(sessionId);
|
|
3047
|
+
if (record) {
|
|
3048
|
+
records.push(record);
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
3053
|
+
}
|
|
3054
|
+
async findAllSessions() {
|
|
3055
|
+
const keys = await this.storage.getKeys(PREFIX3);
|
|
3056
|
+
const records = [];
|
|
3057
|
+
for (const key of keys) {
|
|
3058
|
+
if (key.startsWith("idx:")) continue;
|
|
3059
|
+
const record = await this.storage.getItem(key);
|
|
3060
|
+
if (record) {
|
|
3061
|
+
records.push(record);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
3065
|
+
}
|
|
3066
|
+
async deleteSession(sessionId) {
|
|
3067
|
+
const record = await this.findSessionById(sessionId);
|
|
3068
|
+
await this.storage.removeItem(this.key(sessionId));
|
|
3069
|
+
await this.storage.removeItem(this.messagesKey(sessionId));
|
|
3070
|
+
if (record) {
|
|
3071
|
+
await this.storage.removeItem(this.imageIndexKey(record.imageId, sessionId));
|
|
3072
|
+
await this.storage.removeItem(this.containerIndexKey(record.containerId, sessionId));
|
|
3073
|
+
}
|
|
3074
|
+
logger14.debug("Session deleted", { sessionId });
|
|
3075
|
+
}
|
|
3076
|
+
async sessionExists(sessionId) {
|
|
3077
|
+
return await this.storage.hasItem(this.key(sessionId));
|
|
3078
|
+
}
|
|
3079
|
+
// ==================== Message Operations ====================
|
|
3080
|
+
async addMessage(sessionId, message) {
|
|
3081
|
+
const messages = await this.getMessages(sessionId);
|
|
3082
|
+
messages.push(message);
|
|
3083
|
+
await this.storage.setItem(this.messagesKey(sessionId), messages);
|
|
3084
|
+
logger14.debug("Message added to session", { sessionId, subtype: message.subtype });
|
|
3085
|
+
}
|
|
3086
|
+
async getMessages(sessionId) {
|
|
3087
|
+
const messages = await this.storage.getItem(this.messagesKey(sessionId));
|
|
3088
|
+
if (!messages || !Array.isArray(messages)) {
|
|
3089
|
+
if (messages) {
|
|
3090
|
+
logger14.warn("Messages data is not an array, resetting", {
|
|
3091
|
+
sessionId,
|
|
3092
|
+
type: typeof messages
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
return [];
|
|
3096
|
+
}
|
|
3097
|
+
return messages;
|
|
3098
|
+
}
|
|
3099
|
+
async clearMessages(sessionId) {
|
|
3100
|
+
await this.storage.removeItem(this.messagesKey(sessionId));
|
|
3101
|
+
logger14.debug("Messages cleared for session", { sessionId });
|
|
3102
|
+
}
|
|
3103
|
+
};
|
|
3104
|
+
|
|
3105
|
+
// src/internal/persistence/PersistenceImpl.ts
|
|
3106
|
+
var logger15 = (0, import_common15.createLogger)("persistence/Persistence");
|
|
3107
|
+
var PersistenceImpl = class _PersistenceImpl {
|
|
3108
|
+
images;
|
|
3109
|
+
containers;
|
|
3110
|
+
sessions;
|
|
3111
|
+
storage;
|
|
3112
|
+
/**
|
|
3113
|
+
* Private constructor - use createPersistence() factory function
|
|
3114
|
+
*/
|
|
3115
|
+
constructor(storage, driverName) {
|
|
3116
|
+
this.storage = storage;
|
|
3117
|
+
this.images = new StorageImageRepository(this.storage);
|
|
3118
|
+
this.containers = new StorageContainerRepository(this.storage);
|
|
3119
|
+
this.sessions = new StorageSessionRepository(this.storage);
|
|
3120
|
+
logger15.info("Persistence created", { driver: driverName });
|
|
3121
|
+
}
|
|
3122
|
+
/**
|
|
3123
|
+
* Create a PersistenceImpl instance (async factory)
|
|
3124
|
+
*/
|
|
3125
|
+
static async create(config = {}) {
|
|
3126
|
+
const driverName = config.driver ?? "memory";
|
|
3127
|
+
const storage = config.storage ?? await createStorageFromConfig(config);
|
|
3128
|
+
return new _PersistenceImpl(storage, driverName);
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* Get the underlying storage instance
|
|
3132
|
+
*/
|
|
3133
|
+
getStorage() {
|
|
3134
|
+
return this.storage;
|
|
3135
|
+
}
|
|
3136
|
+
/**
|
|
3137
|
+
* Dispose and cleanup resources
|
|
3138
|
+
*/
|
|
3139
|
+
async dispose() {
|
|
3140
|
+
await this.storage.dispose();
|
|
3141
|
+
logger15.info("Persistence disposed");
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
async function createStorageFromConfig(config) {
|
|
3145
|
+
const driver = config.driver ?? "memory";
|
|
3146
|
+
switch (driver) {
|
|
3147
|
+
case "memory":
|
|
3148
|
+
return (0, import_unstorage.createStorage)();
|
|
3149
|
+
case "fs": {
|
|
3150
|
+
const { default: fsDriver } = await import("unstorage/drivers/fs");
|
|
3151
|
+
return (0, import_unstorage.createStorage)({
|
|
3152
|
+
driver: fsDriver({ base: config.path ?? "./data" })
|
|
3153
|
+
});
|
|
3154
|
+
}
|
|
3155
|
+
case "redis": {
|
|
3156
|
+
const { default: redisDriver } = await import("unstorage/drivers/redis");
|
|
3157
|
+
return (0, import_unstorage.createStorage)({
|
|
3158
|
+
driver: redisDriver({ url: config.url ?? "redis://localhost:6379" })
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
3161
|
+
case "mongodb": {
|
|
3162
|
+
const { default: mongoDriver } = await import("unstorage/drivers/mongodb");
|
|
3163
|
+
return (0, import_unstorage.createStorage)({
|
|
3164
|
+
driver: mongoDriver({
|
|
3165
|
+
connectionString: config.url ?? "mongodb://localhost:27017",
|
|
3166
|
+
databaseName: "agentx",
|
|
3167
|
+
collectionName: "storage"
|
|
3168
|
+
})
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
case "sqlite": {
|
|
3172
|
+
const { default: db0Driver } = await import("unstorage/drivers/db0");
|
|
3173
|
+
const { createDatabase } = await import("db0");
|
|
3174
|
+
const { default: sqliteConnector } = await import("db0/connectors/better-sqlite3");
|
|
3175
|
+
const database = createDatabase(sqliteConnector({ path: config.path ?? "./data.db" }));
|
|
3176
|
+
return (0, import_unstorage.createStorage)({
|
|
3177
|
+
driver: db0Driver({ database })
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
case "mysql": {
|
|
3181
|
+
const { default: db0Driver } = await import("unstorage/drivers/db0");
|
|
3182
|
+
const { createDatabase } = await import("db0");
|
|
3183
|
+
const { default: mysqlConnector } = await import("db0/connectors/mysql2");
|
|
3184
|
+
const database = createDatabase(
|
|
3185
|
+
mysqlConnector({ uri: config.url ?? "mysql://localhost:3306/agentx" })
|
|
3186
|
+
);
|
|
3187
|
+
return (0, import_unstorage.createStorage)({
|
|
3188
|
+
driver: db0Driver({ database })
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
case "postgresql": {
|
|
3192
|
+
const { default: db0Driver } = await import("unstorage/drivers/db0");
|
|
3193
|
+
const { createDatabase } = await import("db0");
|
|
3194
|
+
const { default: pgConnector } = await import("db0/connectors/postgresql");
|
|
3195
|
+
const database = createDatabase(
|
|
3196
|
+
pgConnector({ connectionString: config.url ?? "postgres://localhost:5432/agentx" })
|
|
3197
|
+
);
|
|
3198
|
+
return (0, import_unstorage.createStorage)({
|
|
3199
|
+
driver: db0Driver({ database })
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
default:
|
|
3203
|
+
throw new Error(`Unknown storage driver: ${driver}`);
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
async function createPersistence(config) {
|
|
3207
|
+
return PersistenceImpl.create(config);
|
|
3208
|
+
}
|
|
3209
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3210
|
+
0 && (module.exports = {
|
|
3211
|
+
createPersistence,
|
|
3212
|
+
createRuntime
|
|
3213
|
+
});
|
|
3214
|
+
//# sourceMappingURL=index.cjs.map
|