@defai.digital/automatosx 12.1.1 → 12.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/index.js CHANGED
@@ -195,11 +195,7 @@ var init_logger = __esm({
195
195
  message: entry.message,
196
196
  context: entry.context
197
197
  });
198
- if (entry.level === "error") {
199
- console.error(json);
200
- } else {
201
- console.log(json);
202
- }
198
+ console.error(json);
203
199
  return;
204
200
  }
205
201
  const timestamp = entry.timestamp.toISOString();
@@ -5367,7 +5363,7 @@ var PRECOMPILED_CONFIG = {
5367
5363
  "enableFreeTierPrioritization": true,
5368
5364
  "enableWorkloadAwareRouting": true
5369
5365
  },
5370
- "version": "12.1.1"
5366
+ "version": "12.3.0"
5371
5367
  };
5372
5368
 
5373
5369
  // src/core/config/schemas.ts
@@ -14153,6 +14149,7 @@ var ConversationContextStore = class {
14153
14149
  storePath;
14154
14150
  maxEntries;
14155
14151
  ttlMs;
14152
+ destroyed = false;
14156
14153
  constructor(options) {
14157
14154
  this.storePath = options.storePath;
14158
14155
  this.maxEntries = options.maxEntries ?? 100;
@@ -14189,6 +14186,9 @@ var ConversationContextStore = class {
14189
14186
  * Initialize store (create directory if needed)
14190
14187
  */
14191
14188
  async initialize() {
14189
+ if (this.destroyed) {
14190
+ throw new Error("Context store is destroyed");
14191
+ }
14192
14192
  try {
14193
14193
  await mkdir(this.storePath, { recursive: true });
14194
14194
  logger.info("[ContextStore] Initialized", { storePath: this.storePath });
@@ -14201,6 +14201,9 @@ var ConversationContextStore = class {
14201
14201
  * Save conversation context
14202
14202
  */
14203
14203
  async save(context) {
14204
+ if (this.destroyed) {
14205
+ throw new Error("Context store is destroyed");
14206
+ }
14204
14207
  await this.initialize();
14205
14208
  const filePath = this.validateAndResolvePath(context.id);
14206
14209
  const data = JSON.stringify(context, null, 2);
@@ -14244,6 +14247,9 @@ var ConversationContextStore = class {
14244
14247
  * List all context entries (non-expired)
14245
14248
  */
14246
14249
  async list(options) {
14250
+ if (this.destroyed) {
14251
+ return [];
14252
+ }
14247
14253
  try {
14248
14254
  const files = await readdir(this.storePath);
14249
14255
  const contexts = [];
@@ -14346,6 +14352,9 @@ var ConversationContextStore = class {
14346
14352
  * Get storage statistics
14347
14353
  */
14348
14354
  async stats() {
14355
+ if (this.destroyed) {
14356
+ return { totalEntries: 0, bySource: {} };
14357
+ }
14349
14358
  const contexts = await this.list();
14350
14359
  const bySource = {};
14351
14360
  for (const context of contexts) {
@@ -14358,6 +14367,12 @@ var ConversationContextStore = class {
14358
14367
  newestEntry: contexts[0]?.timestamp
14359
14368
  };
14360
14369
  }
14370
+ /**
14371
+ * Destroy store (prevent further I/O)
14372
+ */
14373
+ destroy() {
14374
+ this.destroyed = true;
14375
+ }
14361
14376
  };
14362
14377
 
14363
14378
  // src/mcp/tools/run-agent.ts
@@ -17134,7 +17149,7 @@ async function executeViaMcpPool(provider, agent, task, pool) {
17134
17149
  pool.release(provider, client);
17135
17150
  }
17136
17151
  }
17137
- async function executeViaCli(agent, task, actualProvider, no_memory, deps, isFallback) {
17152
+ async function executeViaCli(agent, task, actualProvider, no_memory, deps, isFallback, signal) {
17138
17153
  const context = await deps.contextManager.createContext(agent, task, {
17139
17154
  provider: actualProvider,
17140
17155
  skipMemory: no_memory
@@ -17143,7 +17158,8 @@ async function executeViaCli(agent, task, actualProvider, no_memory, deps, isFal
17143
17158
  const startTime = Date.now();
17144
17159
  const result = await executor.execute(context, {
17145
17160
  showProgress: false,
17146
- verbose: false
17161
+ verbose: false,
17162
+ signal
17147
17163
  });
17148
17164
  const latencyMs = Date.now() - startTime;
17149
17165
  const executionMode = isFallback ? "cli_fallback" : "cli_spawn";
@@ -17215,8 +17231,11 @@ async function buildAgentContext(agentName, task, deps, callerProvider, bestProv
17215
17231
  };
17216
17232
  }
17217
17233
  function createRunAgentHandler(deps) {
17218
- return async (input) => {
17234
+ return async (input, context) => {
17219
17235
  const { agent, task, provider, no_memory, mode = "auto" } = input;
17236
+ if (context?.signal?.aborted) {
17237
+ throw new Error("Request was cancelled");
17238
+ }
17220
17239
  validateAgentName(agent);
17221
17240
  validateStringParameter(task, "task", {
17222
17241
  required: true,
@@ -17243,6 +17262,9 @@ function createRunAgentHandler(deps) {
17243
17262
  bestProvider = selectedProvider?.name || "claude-code";
17244
17263
  }
17245
17264
  const shouldReturnContext = mode === "context" || mode === "auto" && callerActual === bestProvider && callerProvider !== "unknown";
17265
+ if (context?.signal?.aborted) {
17266
+ throw new Error("Request was cancelled");
17267
+ }
17246
17268
  logger.info("[Smart Routing] Decision", {
17247
17269
  mode,
17248
17270
  callerProvider,
@@ -17277,6 +17299,7 @@ function createRunAgentHandler(deps) {
17277
17299
  const shouldTryMcp = !!deps.mcpPool;
17278
17300
  if (shouldTryMcp && deps.mcpPool) {
17279
17301
  try {
17302
+ if (context?.signal?.aborted) throw new Error("Request was cancelled");
17280
17303
  const result = await executeViaMcpPool(
17281
17304
  bestProvider,
17282
17305
  agent,
@@ -17306,7 +17329,10 @@ function createRunAgentHandler(deps) {
17306
17329
  }
17307
17330
  }
17308
17331
  try {
17309
- const result = await executeViaCli(agent, task, actualProvider, no_memory, deps, shouldTryMcp);
17332
+ if (context?.signal?.aborted) {
17333
+ throw new Error("Request was cancelled");
17334
+ }
17335
+ const result = await executeViaCli(agent, task, actualProvider, no_memory, deps, shouldTryMcp, context?.signal);
17310
17336
  logger.info("[MCP] run_agent completed (CLI spawn mode)", {
17311
17337
  agent,
17312
17338
  latencyMs: result.latencyMs,
@@ -18574,6 +18600,11 @@ function compressWithInfo(payload, options = {}) {
18574
18600
  init_esm_shims();
18575
18601
  init_factory();
18576
18602
  init_logger();
18603
+ var NATIVE_MODULE_ERROR_PATTERNS = [
18604
+ "NODE_MODULE_VERSION",
18605
+ "was compiled against a different Node.js version",
18606
+ "better_sqlite3.node"
18607
+ ];
18577
18608
  var DEFAULT_CONFIG3 = {
18578
18609
  dbPath: ".automatosx/tasks/tasks.db",
18579
18610
  maxPayloadBytes: 1024 * 1024,
@@ -18701,6 +18732,28 @@ var SQL = {
18701
18732
  SELECT id FROM task_store WHERE payload_hash = ? AND status = 'completed' LIMIT 1
18702
18733
  `
18703
18734
  };
18735
+ function generateTaskId() {
18736
+ const timestamp = Date.now().toString(36);
18737
+ const random = randomBytes(4).toString("hex");
18738
+ return `task_${timestamp}${random}`;
18739
+ }
18740
+ function hashPayload(json) {
18741
+ return createHash("sha256").update(json).digest("hex").substring(0, 16);
18742
+ }
18743
+ function estimateEngine(type) {
18744
+ switch (type) {
18745
+ case "web_search":
18746
+ return "gemini";
18747
+ case "code_review":
18748
+ case "code_generation":
18749
+ return "claude";
18750
+ case "analysis":
18751
+ return "claude";
18752
+ case "custom":
18753
+ default:
18754
+ return "auto";
18755
+ }
18756
+ }
18704
18757
  var TaskStore = class {
18705
18758
  db;
18706
18759
  config;
@@ -18761,15 +18814,15 @@ var TaskStore = class {
18761
18814
  isCompressed = false;
18762
18815
  compressionRatio = 1;
18763
18816
  }
18764
- const taskId = this.generateTaskId();
18765
- const payloadHash = this.hashPayload(payloadJson);
18817
+ const taskId = generateTaskId();
18818
+ const payloadHash = hashPayload(payloadJson);
18766
18819
  const now = Date.now();
18767
18820
  const ttlHours = Math.min(
18768
18821
  validated.ttlHours ?? this.config.defaultTtlHours,
18769
18822
  this.config.maxTtlHours
18770
18823
  );
18771
18824
  const expiresAt = now + ttlHours * 60 * 60 * 1e3;
18772
- const estimatedEngine = validated.engine === "auto" ? this.estimateEngine(validated.type) : validated.engine;
18825
+ const estimatedEngine = validated.engine === "auto" ? estimateEngine(validated.type) : validated.engine;
18773
18826
  try {
18774
18827
  this.stmtInsert.run({
18775
18828
  id: taskId,
@@ -19112,31 +19165,6 @@ var TaskStore = class {
19112
19165
  throw new TaskEngineError("TaskStore is closed", "STORE_ERROR");
19113
19166
  }
19114
19167
  }
19115
- generateTaskId() {
19116
- const timestamp = Date.now().toString(36);
19117
- const random = randomBytes(4).toString("hex");
19118
- return `task_${timestamp}${random}`;
19119
- }
19120
- hashPayload(json) {
19121
- return createHash("sha256").update(json).digest("hex").substring(0, 16);
19122
- }
19123
- estimateEngine(type) {
19124
- switch (type) {
19125
- case "web_search":
19126
- return "gemini";
19127
- // Gemini is good for web search
19128
- case "code_review":
19129
- case "code_generation":
19130
- return "claude";
19131
- // Claude is good for code
19132
- case "analysis":
19133
- return "claude";
19134
- // Claude is good for analysis
19135
- case "custom":
19136
- default:
19137
- return "auto";
19138
- }
19139
- }
19140
19168
  rowToTask(row) {
19141
19169
  let payload;
19142
19170
  try {
@@ -19203,8 +19231,248 @@ var TaskStore = class {
19203
19231
  };
19204
19232
  }
19205
19233
  };
19234
+ var InMemoryTaskStore = class {
19235
+ tasks = /* @__PURE__ */ new Map();
19236
+ config;
19237
+ closed = false;
19238
+ constructor(config = {}) {
19239
+ this.config = { ...DEFAULT_CONFIG3, ...config };
19240
+ logger.warn("Using in-memory task store fallback (SQLite unavailable)", {
19241
+ nodeVersion: process.version
19242
+ });
19243
+ }
19244
+ createTask(input) {
19245
+ this.ensureOpen();
19246
+ const validated = CreateTaskInputSchema.parse(input);
19247
+ const payloadJson = JSON.stringify(validated.payload);
19248
+ const payloadSize = Buffer.byteLength(payloadJson, "utf-8");
19249
+ if (payloadSize > this.config.maxPayloadBytes) {
19250
+ throw new TaskEngineError(
19251
+ `Payload size ${payloadSize} exceeds limit ${this.config.maxPayloadBytes}`,
19252
+ "PAYLOAD_TOO_LARGE",
19253
+ { payloadSize, limit: this.config.maxPayloadBytes }
19254
+ );
19255
+ }
19256
+ const id = generateTaskId();
19257
+ const now = Date.now();
19258
+ const ttlHours = Math.min(
19259
+ validated.ttlHours ?? this.config.defaultTtlHours,
19260
+ this.config.maxTtlHours
19261
+ );
19262
+ const expiresAt = now + ttlHours * 60 * 60 * 1e3;
19263
+ const estimatedEngine = validated.engine === "auto" ? estimateEngine(validated.type) : validated.engine;
19264
+ const task = {
19265
+ id,
19266
+ type: validated.type,
19267
+ status: "pending",
19268
+ engine: validated.engine === "auto" ? null : validated.engine,
19269
+ priority: validated.priority ?? 5,
19270
+ payload: validated.payload,
19271
+ payloadSize,
19272
+ result: null,
19273
+ context: {
19274
+ originClient: validated.context?.originClient ?? "unknown",
19275
+ callChain: validated.context?.callChain ?? [],
19276
+ depth: validated.context?.depth ?? 0
19277
+ },
19278
+ createdAt: now,
19279
+ startedAt: null,
19280
+ completedAt: null,
19281
+ expiresAt,
19282
+ metrics: null,
19283
+ error: null,
19284
+ retryCount: 0,
19285
+ payloadHash: hashPayload(payloadJson),
19286
+ compressionRatio: 1
19287
+ };
19288
+ this.tasks.set(id, task);
19289
+ return {
19290
+ id,
19291
+ status: "pending",
19292
+ estimatedEngine: estimatedEngine ?? null,
19293
+ expiresAt,
19294
+ payloadSize,
19295
+ compressionRatio: 1
19296
+ };
19297
+ }
19298
+ getTask(taskId) {
19299
+ this.ensureOpen();
19300
+ const task = this.tasks.get(taskId);
19301
+ return task ? structuredClone(task) : null;
19302
+ }
19303
+ updateTaskStatus(taskId, status, error) {
19304
+ this.ensureOpen();
19305
+ const task = this.tasks.get(taskId);
19306
+ if (!task) {
19307
+ throw new TaskEngineError(`Task not found: ${taskId}`, "TASK_NOT_FOUND");
19308
+ }
19309
+ const now = Date.now();
19310
+ task.status = status;
19311
+ task.startedAt = status === "running" ? now : task.startedAt;
19312
+ if (status === "completed" || status === "failed") {
19313
+ task.completedAt = now;
19314
+ }
19315
+ task.error = error ?? null;
19316
+ }
19317
+ updateTaskResult(taskId, result, metrics) {
19318
+ this.ensureOpen();
19319
+ const task = this.tasks.get(taskId);
19320
+ if (!task) {
19321
+ throw new TaskEngineError(`Task not found: ${taskId}`, "TASK_NOT_FOUND");
19322
+ }
19323
+ task.status = "completed";
19324
+ task.result = result;
19325
+ task.metrics = {
19326
+ durationMs: metrics.durationMs ?? 0,
19327
+ tokensPrompt: metrics.tokensPrompt ?? null,
19328
+ tokensCompletion: metrics.tokensCompletion ?? null
19329
+ };
19330
+ task.completedAt = Date.now();
19331
+ task.error = null;
19332
+ }
19333
+ updateTaskFailed(taskId, error, durationMs) {
19334
+ this.ensureOpen();
19335
+ const task = this.tasks.get(taskId);
19336
+ if (!task) {
19337
+ throw new TaskEngineError(`Task not found: ${taskId}`, "TASK_NOT_FOUND");
19338
+ }
19339
+ task.status = "failed";
19340
+ task.error = error;
19341
+ task.metrics = task.metrics ?? {
19342
+ durationMs: durationMs ?? 0,
19343
+ tokensPrompt: null,
19344
+ tokensCompletion: null
19345
+ };
19346
+ task.completedAt = Date.now();
19347
+ }
19348
+ incrementRetry(taskId) {
19349
+ this.ensureOpen();
19350
+ const task = this.tasks.get(taskId);
19351
+ if (task) {
19352
+ task.retryCount += 1;
19353
+ }
19354
+ }
19355
+ deleteTask(taskId) {
19356
+ this.ensureOpen();
19357
+ return this.tasks.delete(taskId);
19358
+ }
19359
+ listTasks(filter = {}) {
19360
+ this.ensureOpen();
19361
+ const validated = TaskFilterSchema.parse(filter);
19362
+ const filtered = Array.from(this.tasks.values()).filter((task) => {
19363
+ if (validated.status && task.status !== validated.status) return false;
19364
+ if (validated.engine && task.engine !== validated.engine) return false;
19365
+ if (validated.type && task.type !== validated.type) return false;
19366
+ if (validated.originClient && task.context.originClient !== validated.originClient) return false;
19367
+ return true;
19368
+ });
19369
+ filtered.sort((a, b) => {
19370
+ if (a.priority !== b.priority) return b.priority - a.priority;
19371
+ return a.createdAt - b.createdAt;
19372
+ });
19373
+ const start = validated.offset ?? 0;
19374
+ const end = start + (validated.limit ?? 20);
19375
+ return filtered.slice(start, end).map((task) => structuredClone(task));
19376
+ }
19377
+ countTasks(filter = {}) {
19378
+ this.ensureOpen();
19379
+ const validated = TaskFilterSchema.parse(filter);
19380
+ return Array.from(this.tasks.values()).filter((task) => {
19381
+ if (validated.status && task.status !== validated.status) return false;
19382
+ if (validated.engine && task.engine !== validated.engine) return false;
19383
+ if (validated.type && task.type !== validated.type) return false;
19384
+ if (validated.originClient && task.context.originClient !== validated.originClient) return false;
19385
+ return true;
19386
+ }).length;
19387
+ }
19388
+ findByPayloadHash(payloadHash) {
19389
+ this.ensureOpen();
19390
+ for (const task of this.tasks.values()) {
19391
+ if (task.status === "completed" && task.payloadHash === payloadHash) {
19392
+ return task.id;
19393
+ }
19394
+ }
19395
+ return null;
19396
+ }
19397
+ cleanupExpired() {
19398
+ this.ensureOpen();
19399
+ const now = Date.now();
19400
+ let removed = 0;
19401
+ for (const [id, task] of this.tasks) {
19402
+ if (task.expiresAt < now && task.status !== "running") {
19403
+ this.tasks.delete(id);
19404
+ removed++;
19405
+ }
19406
+ }
19407
+ return removed;
19408
+ }
19409
+ cleanupZombieRunning() {
19410
+ this.ensureOpen();
19411
+ const now = Date.now();
19412
+ let converted = 0;
19413
+ for (const task of this.tasks.values()) {
19414
+ if (task.status === "running" && task.expiresAt < now) {
19415
+ task.status = "failed";
19416
+ task.error = {
19417
+ code: "ZOMBIE_TASK",
19418
+ message: "Task was stuck in running state past expiry"
19419
+ };
19420
+ task.completedAt = now;
19421
+ converted++;
19422
+ }
19423
+ }
19424
+ return converted;
19425
+ }
19426
+ cleanupAll() {
19427
+ const zombies = this.cleanupZombieRunning();
19428
+ const expired = this.cleanupExpired();
19429
+ return { expired, zombies };
19430
+ }
19431
+ getStats() {
19432
+ this.ensureOpen();
19433
+ const byStatus = {
19434
+ pending: 0,
19435
+ running: 0,
19436
+ completed: 0,
19437
+ failed: 0,
19438
+ expired: 0
19439
+ };
19440
+ for (const task of this.tasks.values()) {
19441
+ byStatus[task.status]++;
19442
+ }
19443
+ return {
19444
+ totalTasks: this.tasks.size,
19445
+ byStatus,
19446
+ dbSizeBytes: 0
19447
+ };
19448
+ }
19449
+ close() {
19450
+ this.closed = true;
19451
+ }
19452
+ ensureOpen() {
19453
+ if (this.closed) {
19454
+ throw new TaskEngineError("TaskStore is closed", "STORE_ERROR");
19455
+ }
19456
+ }
19457
+ };
19458
+ function isNativeModuleError(error) {
19459
+ if (!(error instanceof Error)) return false;
19460
+ const message = error.message ?? "";
19461
+ return NATIVE_MODULE_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
19462
+ }
19206
19463
  function createTaskStore(config) {
19207
- return new TaskStore(config);
19464
+ try {
19465
+ return new TaskStore(config);
19466
+ } catch (error) {
19467
+ if (isNativeModuleError(error)) {
19468
+ logger.warn("Falling back to in-memory task store (native SQLite unavailable)", {
19469
+ nodeVersion: process.version,
19470
+ error: error instanceof Error ? error.message : String(error)
19471
+ });
19472
+ return new InMemoryTaskStore(config);
19473
+ }
19474
+ throw error;
19475
+ }
19208
19476
  }
19209
19477
 
19210
19478
  // src/core/task-engine/engine.ts
@@ -19295,6 +19563,9 @@ var TaskEngine = class extends EventEmitter {
19295
19563
  if (!task) {
19296
19564
  throw new TaskEngineError(`Task not found: ${taskId}`, "TASK_NOT_FOUND");
19297
19565
  }
19566
+ if (options.abortSignal?.aborted) {
19567
+ throw new TaskEngineError("Task execution aborted", "EXECUTION_TIMEOUT");
19568
+ }
19298
19569
  if (task.status === "running") {
19299
19570
  throw new TaskEngineError(
19300
19571
  `Task is already running: ${taskId}`,
@@ -19460,6 +19731,14 @@ var TaskEngine = class extends EventEmitter {
19460
19731
  async executeTask(task, targetEngine, context, options) {
19461
19732
  const taskId = task.id;
19462
19733
  const abortController = new AbortController();
19734
+ let externalCancelled = false;
19735
+ const externalAbort = options.abortSignal ? () => {
19736
+ externalCancelled = true;
19737
+ abortController.abort();
19738
+ } : null;
19739
+ if (options.abortSignal && externalAbort) {
19740
+ options.abortSignal.addEventListener("abort", externalAbort, { once: true });
19741
+ }
19463
19742
  const startTime = Date.now();
19464
19743
  this.runningTasks.set(taskId, abortController);
19465
19744
  this.store.updateTaskStatus(taskId, "running");
@@ -19500,7 +19779,7 @@ var TaskEngine = class extends EventEmitter {
19500
19779
  const durationMs = Date.now() - startTime;
19501
19780
  const errorObj = error instanceof Error ? error : new Error(String(error));
19502
19781
  if (abortController.signal.aborted) {
19503
- const taskError2 = { code: "TIMEOUT", message: `Task timed out after ${timeoutMs}ms` };
19782
+ const taskError2 = externalCancelled ? { code: "CANCELLED", message: "Task was cancelled by caller" } : { code: "TIMEOUT", message: `Task timed out after ${timeoutMs}ms` };
19504
19783
  this.store.updateTaskFailed(taskId, taskError2, durationMs);
19505
19784
  this.stats.failed++;
19506
19785
  this.emit("task:failed", taskId, new TaskEngineError(taskError2.message, "EXECUTION_TIMEOUT"));
@@ -19534,6 +19813,9 @@ var TaskEngine = class extends EventEmitter {
19534
19813
  } finally {
19535
19814
  clearTimeout(timeoutId);
19536
19815
  this.runningTasks.delete(taskId);
19816
+ if (options.abortSignal && externalAbort) {
19817
+ options.abortSignal.removeEventListener("abort", externalAbort);
19818
+ }
19537
19819
  }
19538
19820
  }
19539
19821
  async executeWithRetry(task, engine, context, signal) {
@@ -19838,8 +20120,11 @@ var createTaskSchema = {
19838
20120
  init_esm_shims();
19839
20121
  init_logger();
19840
20122
  function createRunTaskHandler(deps) {
19841
- return async (input) => {
20123
+ return async (input, context) => {
19842
20124
  const startTime = Date.now();
20125
+ if (context?.signal?.aborted) {
20126
+ throw new Error("Request was cancelled");
20127
+ }
19843
20128
  logger.info("[run_task] Executing task", {
19844
20129
  taskId: input.task_id,
19845
20130
  engineOverride: input.engine_override,
@@ -19852,7 +20137,8 @@ function createRunTaskHandler(deps) {
19852
20137
  const options = {
19853
20138
  engineOverride: input.engine_override,
19854
20139
  timeoutMs: input.timeout_ms,
19855
- skipCache: input.skip_cache
20140
+ skipCache: input.skip_cache,
20141
+ abortSignal: context?.signal
19856
20142
  };
19857
20143
  const result = await taskEngine.runTask(input.task_id, options);
19858
20144
  const output = {
@@ -22178,6 +22464,10 @@ var McpServer = class _McpServer {
22178
22464
  version;
22179
22465
  ajv;
22180
22466
  compiledValidators = /* @__PURE__ */ new Map();
22467
+ cancelledRequests = /* @__PURE__ */ new Set();
22468
+ // Track client-initiated cancellations
22469
+ requestControllers = /* @__PURE__ */ new Map();
22470
+ // Abort long-running handlers
22181
22471
  // v10.5.0: MCP Session for Smart Routing
22182
22472
  session = null;
22183
22473
  // v10.6.0: MCP Client Pool for cross-provider execution
@@ -22456,6 +22746,16 @@ Use this tool first to understand what AutomatosX offers.`,
22456
22746
  if (this.streamingNotifier) {
22457
22747
  this.streamingNotifier.stop();
22458
22748
  }
22749
+ if (this.router) {
22750
+ this.router.destroy();
22751
+ }
22752
+ this.requestControllers.clear();
22753
+ if (this.sessionManager) {
22754
+ await this.sessionManager.destroy();
22755
+ }
22756
+ if (this.contextStore) {
22757
+ this.contextStore.destroy();
22758
+ }
22459
22759
  if (this.eventBridge) {
22460
22760
  this.eventBridge.destroy();
22461
22761
  }
@@ -22573,6 +22873,12 @@ Use this tool first to understand what AutomatosX offers.`,
22573
22873
  return this.handleToolsList(request, responseId);
22574
22874
  case "tools/call":
22575
22875
  return await this.handleToolCall(request, responseId);
22876
+ case "resources/list":
22877
+ return await this.handleResourcesList(request, responseId);
22878
+ case "resources/read":
22879
+ return await this.handleResourceRead(request, responseId);
22880
+ case "$/cancelRequest":
22881
+ return this.handleCancelRequest(request, responseId);
22576
22882
  default:
22577
22883
  return this.createErrorResponse(responseId, -32601 /* MethodNotFound */, `Method not found: ${method}`);
22578
22884
  }
@@ -22628,6 +22934,48 @@ Use this tool first to understand what AutomatosX offers.`,
22628
22934
  const tools = _McpServer.getStaticToolSchemas();
22629
22935
  return { jsonrpc: "2.0", id, result: { tools } };
22630
22936
  }
22937
+ /**
22938
+ * Handle resources/list request (exposes agent profiles as MCP resources)
22939
+ */
22940
+ async handleResourcesList(_request, id) {
22941
+ await this.ensureInitialized();
22942
+ const agents = await this.profileLoader.listProfiles();
22943
+ const resources = agents.map((agent) => ({
22944
+ uri: `agent/${agent}`,
22945
+ name: `Agent: ${agent}`,
22946
+ description: `AutomatosX agent profile for ${agent}`,
22947
+ mimeType: "text/markdown"
22948
+ }));
22949
+ return { jsonrpc: "2.0", id, result: { resources } };
22950
+ }
22951
+ /**
22952
+ * Handle resources/read request
22953
+ */
22954
+ async handleResourceRead(request, id) {
22955
+ await this.ensureInitialized();
22956
+ const uri = request.params?.uri;
22957
+ if (!uri || !uri.startsWith("agent/")) {
22958
+ return this.createErrorResponse(id, -32602 /* InvalidParams */, "Invalid resource URI. Expected agent/{name}.");
22959
+ }
22960
+ const agentName = uri.replace("agent/", "");
22961
+ try {
22962
+ const profile = await this.profileLoader.loadProfile(agentName);
22963
+ const summary = [
22964
+ `# ${agentName}`,
22965
+ profile.role ? `**Role:** ${profile.role}` : "",
22966
+ profile.abilities?.length ? `**Abilities:** ${profile.abilities.join(", ")}` : "",
22967
+ "",
22968
+ profile.systemPrompt || "No system prompt defined."
22969
+ ].filter(Boolean).join("\n");
22970
+ const contents = [
22971
+ { type: "text", text: summary },
22972
+ { type: "application/json", json: profile }
22973
+ ];
22974
+ return { jsonrpc: "2.0", id, result: { uri, mimeType: "text/markdown", contents } };
22975
+ } catch (error) {
22976
+ return this.createErrorResponse(id, -32603 /* InternalError */, `Failed to read resource: ${error.message}`);
22977
+ }
22978
+ }
22631
22979
  /**
22632
22980
  * Validate tool input against its JSON schema.
22633
22981
  * Returns null if valid, or error message string if invalid.
@@ -22639,10 +22987,34 @@ Use this tool first to understand what AutomatosX offers.`,
22639
22987
  return this.ajv.errorsText(validate.errors);
22640
22988
  }
22641
22989
  /** Create MCP tool response wrapper */
22642
- createToolResponse(id, text, isError = false) {
22643
- const response = { content: [{ type: "text", text }], ...isError && { isError } };
22990
+ createToolResponse(id, result, isError = false) {
22991
+ let content;
22992
+ if (typeof result === "string") {
22993
+ content = [{ type: "text", text: result }];
22994
+ } else {
22995
+ content = [
22996
+ { type: "application/json", json: result },
22997
+ { type: "text", text: JSON.stringify(result, null, 2) }
22998
+ ];
22999
+ }
23000
+ const response = { content, ...isError && { isError } };
22644
23001
  return { jsonrpc: "2.0", id, result: response };
22645
23002
  }
23003
+ /**
23004
+ * Handle client cancellation ($/cancelRequest)
23005
+ */
23006
+ handleCancelRequest(request, id) {
23007
+ const params = request.params;
23008
+ const cancelId = params?.id ?? params?.requestId;
23009
+ if (cancelId === void 0 || cancelId === null) {
23010
+ return this.createErrorResponse(id, -32602 /* InvalidParams */, "cancelRequest requires an id to cancel");
23011
+ }
23012
+ this.cancelledRequests.add(cancelId);
23013
+ logger.info("[MCP Server] Cancellation requested", { cancelId });
23014
+ const controller = this.requestControllers.get(cancelId);
23015
+ controller?.abort();
23016
+ return { jsonrpc: "2.0", id, result: null };
23017
+ }
22646
23018
  /**
22647
23019
  * Ensure services are initialized (lazy initialization on first call)
22648
23020
  * OPTIMIZATION (v10.3.1): Moves 15-20s initialization from handshake to first request
@@ -22677,6 +23049,16 @@ Use this tool first to understand what AutomatosX offers.`,
22677
23049
  async handleToolCall(request, id) {
22678
23050
  const { name, arguments: args2 } = request.params;
22679
23051
  logger.info("[MCP Server] Tool call", { tool: name });
23052
+ const requestId = id ?? null;
23053
+ const abortController = requestId !== null ? new AbortController() : null;
23054
+ if (requestId !== null && abortController) {
23055
+ this.requestControllers.set(requestId, abortController);
23056
+ }
23057
+ if (requestId !== null && this.cancelledRequests.has(requestId)) {
23058
+ this.cancelledRequests.delete(requestId);
23059
+ this.requestControllers.delete(requestId);
23060
+ return this.createErrorResponse(requestId, -32800 /* RequestCancelled */, "Request was cancelled");
23061
+ }
22680
23062
  await this.ensureInitialized();
22681
23063
  const handler = this.tools.get(name);
22682
23064
  if (!handler) {
@@ -22687,11 +23069,27 @@ Use this tool first to understand what AutomatosX offers.`,
22687
23069
  return this.createErrorResponse(id, -32602 /* InvalidParams */, validationError);
22688
23070
  }
22689
23071
  try {
22690
- const result = await handler(args2 || {});
22691
- return this.createToolResponse(id, JSON.stringify(result, null, 2));
23072
+ const result = await handler(args2 || {}, { signal: abortController?.signal });
23073
+ if (requestId !== null && this.cancelledRequests.has(requestId)) {
23074
+ this.cancelledRequests.delete(requestId);
23075
+ this.requestControllers.delete(requestId);
23076
+ return this.createErrorResponse(requestId, -32800 /* RequestCancelled */, "Request was cancelled");
23077
+ }
23078
+ return this.createToolResponse(id, result);
22692
23079
  } catch (error) {
23080
+ const err = error;
23081
+ const cancelled = err?.name === "AbortError" || err?.message?.toLowerCase().includes("cancel");
23082
+ if (cancelled) {
23083
+ logger.info("[MCP Server] Tool execution cancelled", { tool: name, id: requestId ?? void 0 });
23084
+ return this.createErrorResponse(id, -32800 /* RequestCancelled */, "Request was cancelled");
23085
+ }
22693
23086
  logger.error("[MCP Server] Tool execution failed", { tool: name, error });
22694
- return this.createToolResponse(id, `Error: ${error.message}`, true);
23087
+ return this.createToolResponse(id, `Error: ${err?.message ?? String(error)}`, true);
23088
+ } finally {
23089
+ if (requestId !== null) {
23090
+ this.cancelledRequests.delete(requestId);
23091
+ this.requestControllers.delete(requestId);
23092
+ }
22695
23093
  }
22696
23094
  }
22697
23095
  /**
@@ -22701,26 +23099,34 @@ Use this tool first to understand what AutomatosX offers.`,
22701
23099
  return { jsonrpc: "2.0", id, error: { code, message } };
22702
23100
  }
22703
23101
  /**
22704
- * Write MCP-compliant response with Content-Length framing
23102
+ * Write MCP-compliant response using newline-delimited framing
23103
+ *
23104
+ * v12.2.0: Changed from Content-Length to newline-delimited framing
23105
+ * per official MCP specification: https://spec.modelcontextprotocol.io/specification/basic/transports/
23106
+ *
23107
+ * Format: JSON message followed by newline character
23108
+ * Reference: @modelcontextprotocol/sdk serializeMessage() uses: JSON.stringify(message) + '\n'
22705
23109
  */
22706
23110
  writeResponse(response) {
22707
23111
  const json = JSON.stringify(response);
22708
- const contentLength = Buffer.byteLength(json, "utf-8");
22709
- const message = `Content-Length: ${contentLength}\r
22710
- \r
22711
- ${json}`;
22712
- process.stdout.write(message);
22713
- logger.debug("[MCP Server] Response sent", { id: response.id, contentLength });
23112
+ process.stdout.write(json + "\n");
23113
+ logger.debug("[MCP Server] Response sent", { id: response.id, length: json.length });
22714
23114
  }
22715
23115
  /**
22716
- * Start stdio server with Content-Length framing
23116
+ * Start stdio server with newline-delimited framing
23117
+ *
23118
+ * v12.2.0: Changed from Content-Length to newline-delimited framing
23119
+ * per official MCP specification: https://spec.modelcontextprotocol.io/specification/basic/transports/
23120
+ *
23121
+ * Format: Each JSON-RPC message is terminated by a newline character.
23122
+ * Messages MUST NOT contain embedded newlines.
23123
+ * Reference: @modelcontextprotocol/sdk ReadBuffer uses: buffer.indexOf('\n')
22717
23124
  *
22718
23125
  * BUG FIX (v9.0.1): Added iteration limit and buffer size checks to prevent infinite loops
22719
23126
  */
22720
23127
  async start() {
22721
23128
  logger.info("[MCP Server] Starting stdio JSON-RPC server...");
22722
23129
  let buffer = "";
22723
- let contentLength = null;
22724
23130
  process.stdin.on("data", (chunk) => {
22725
23131
  void this.stdinMutex.runExclusive(async () => {
22726
23132
  buffer += chunk.toString("utf-8");
@@ -22730,49 +23136,34 @@ ${json}`;
22730
23136
  maxSize: STDIO_MAX_BUFFER_SIZE
22731
23137
  });
22732
23138
  buffer = "";
22733
- contentLength = null;
22734
23139
  return;
22735
23140
  }
22736
23141
  let iterations = 0;
22737
23142
  while (iterations < STDIO_MAX_ITERATIONS) {
22738
23143
  iterations++;
22739
- if (contentLength === null) {
22740
- const delimiter2 = buffer.includes("\r\n\r\n") ? "\r\n\r\n" : buffer.includes("\n\n") ? "\n\n" : null;
22741
- if (!delimiter2) break;
22742
- const headerEndIndex = buffer.indexOf(delimiter2);
22743
- const headerBlock = buffer.slice(0, headerEndIndex);
22744
- for (const line of headerBlock.split(delimiter2 === "\r\n\r\n" ? "\r\n" : "\n")) {
22745
- const [key, value] = line.split(":", 2).map((s) => s.trim());
22746
- if (key && key.toLowerCase() === "content-length" && value) {
22747
- contentLength = parseInt(value, 10);
22748
- if (isNaN(contentLength) || contentLength <= 0 || contentLength > STDIO_MAX_MESSAGE_SIZE) {
22749
- logger.error("[MCP Server] Invalid Content-Length", { contentLength });
22750
- this.writeResponse({
22751
- jsonrpc: "2.0",
22752
- id: null,
22753
- error: {
22754
- code: -32600 /* InvalidRequest */,
22755
- message: `Invalid Content-Length: ${contentLength}`
22756
- }
22757
- });
22758
- buffer = buffer.slice(headerEndIndex + delimiter2.length);
22759
- contentLength = null;
22760
- continue;
22761
- }
23144
+ const newlineIndex = buffer.indexOf("\n");
23145
+ if (newlineIndex === -1) break;
23146
+ let jsonMessage = buffer.slice(0, newlineIndex);
23147
+ if (jsonMessage.endsWith("\r")) {
23148
+ jsonMessage = jsonMessage.slice(0, -1);
23149
+ }
23150
+ buffer = buffer.slice(newlineIndex + 1);
23151
+ if (jsonMessage.trim() === "") continue;
23152
+ if (jsonMessage.length > STDIO_MAX_MESSAGE_SIZE) {
23153
+ logger.error("[MCP Server] Message size exceeded maximum", {
23154
+ messageSize: jsonMessage.length,
23155
+ maxSize: STDIO_MAX_MESSAGE_SIZE
23156
+ });
23157
+ this.writeResponse({
23158
+ jsonrpc: "2.0",
23159
+ id: null,
23160
+ error: {
23161
+ code: -32600 /* InvalidRequest */,
23162
+ message: `Message too large: ${jsonMessage.length} bytes`
22762
23163
  }
22763
- }
22764
- if (contentLength === null) {
22765
- logger.error("[MCP Server] No Content-Length header found");
22766
- buffer = buffer.slice(headerEndIndex + delimiter2.length);
22767
- continue;
22768
- }
22769
- buffer = buffer.slice(headerEndIndex + delimiter2.length);
23164
+ });
23165
+ continue;
22770
23166
  }
22771
- if (Buffer.byteLength(buffer, "utf-8") < contentLength) break;
22772
- const messageBuffer = Buffer.from(buffer, "utf-8");
22773
- const jsonMessage = messageBuffer.slice(0, contentLength).toString("utf-8");
22774
- buffer = messageBuffer.slice(contentLength).toString("utf-8");
22775
- contentLength = null;
22776
23167
  try {
22777
23168
  const request = JSON.parse(jsonMessage);
22778
23169
  logger.debug("[MCP Server] Request received", { method: request.method, id: request.id });
@@ -22781,7 +23172,7 @@ ${json}`;
22781
23172
  this.writeResponse(response);
22782
23173
  }
22783
23174
  } catch (error) {
22784
- logger.error("[MCP Server] Failed to parse or handle request", { jsonMessage, error });
23175
+ logger.error("[MCP Server] Failed to parse or handle request", { error: error.message });
22785
23176
  this.writeResponse({ jsonrpc: "2.0", id: null, error: { code: -32700 /* ParseError */, message: "Parse error: Invalid JSON" } });
22786
23177
  }
22787
23178
  }
@@ -22800,7 +23191,7 @@ ${json}`;
22800
23191
  process.stdin.on("end", () => shutdown("Server stopped (stdin closed)"));
22801
23192
  process.on("SIGINT", () => shutdown("Received SIGINT, shutting down..."));
22802
23193
  process.on("SIGTERM", () => shutdown("Received SIGTERM, shutting down..."));
22803
- logger.info("[MCP Server] Server started successfully (Content-Length framing)");
23194
+ logger.info("[MCP Server] Server started successfully (newline-delimited framing)");
22804
23195
  }
22805
23196
  };
22806
23197