@axiom-lattice/core 2.1.76 → 2.1.78

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/index.mjs CHANGED
@@ -1665,6 +1665,7 @@ var InMemoryProjectStore = class {
1665
1665
  workspaceId,
1666
1666
  name: data.name,
1667
1667
  description: data.description,
1668
+ config: data.config,
1668
1669
  createdAt: now,
1669
1670
  updatedAt: now
1670
1671
  };
@@ -1672,6 +1673,13 @@ var InMemoryProjectStore = class {
1672
1673
  this.projects.set(key, project);
1673
1674
  return project;
1674
1675
  }
1676
+ /**
1677
+ * Update an existing project
1678
+ *
1679
+ * @remarks
1680
+ * - The `config` field uses **replace** semantics: if provided, it completely
1681
+ * overwrites the existing config. To preserve the current config, omit this field.
1682
+ */
1675
1683
  async updateProject(tenantId, id, updates) {
1676
1684
  const key = this.getKey(tenantId, id);
1677
1685
  const existing = this.projects.get(key);
@@ -2463,14 +2471,35 @@ var InMemoryChannelInstallationStore = class {
2463
2471
  constructor() {
2464
2472
  this.installations = /* @__PURE__ */ new Map();
2465
2473
  }
2474
+ /**
2475
+ * Retrieves a channel installation by its unique ID.
2476
+ *
2477
+ * @param installationId - The installation identifier
2478
+ * @returns The {@link ChannelInstallation} or `null` if not found
2479
+ */
2466
2480
  async getInstallationById(installationId) {
2467
2481
  return this.installations.get(installationId) || null;
2468
2482
  }
2483
+ /**
2484
+ * Lists all channel installations for a tenant, optionally filtered by channel type.
2485
+ *
2486
+ * @param tenantId - Tenant identifier
2487
+ * @param channel - Optional channel type filter (`"lark"`, `"email"`, `"slack"`)
2488
+ * @returns Array of matching {@link ChannelInstallation} objects
2489
+ */
2469
2490
  async getInstallationsByTenant(tenantId, channel) {
2470
2491
  return Array.from(this.installations.values()).filter(
2471
2492
  (inst) => inst.tenantId === tenantId && (!channel || inst.channel === channel)
2472
2493
  );
2473
2494
  }
2495
+ /**
2496
+ * Creates a new channel installation for a tenant.
2497
+ *
2498
+ * @param tenantId - Tenant identifier
2499
+ * @param installationId - Unique installation ID (caller-defined)
2500
+ * @param data - {@link CreateChannelInstallationRequest} containing channel type, name, config, and fallback settings
2501
+ * @returns The created {@link ChannelInstallation}
2502
+ */
2474
2503
  async createInstallation(tenantId, installationId, data) {
2475
2504
  const now = /* @__PURE__ */ new Date();
2476
2505
  const installation = {
@@ -2488,6 +2517,14 @@ var InMemoryChannelInstallationStore = class {
2488
2517
  this.installations.set(installationId, installation);
2489
2518
  return installation;
2490
2519
  }
2520
+ /**
2521
+ * Updates an existing channel installation. Config fields are shallow-merged.
2522
+ *
2523
+ * @param tenantId - Tenant identifier (used for isolation check)
2524
+ * @param installationId - Installation ID to update
2525
+ * @param updates - {@link UpdateChannelInstallationRequest} with fields to patch
2526
+ * @returns The updated {@link ChannelInstallation} or `null` if not found or tenant mismatch
2527
+ */
2491
2528
  async updateInstallation(tenantId, installationId, updates) {
2492
2529
  const existing = this.installations.get(installationId);
2493
2530
  if (!existing || existing.tenantId !== tenantId) return null;
@@ -2503,11 +2540,21 @@ var InMemoryChannelInstallationStore = class {
2503
2540
  this.installations.set(installationId, updated);
2504
2541
  return updated;
2505
2542
  }
2543
+ /**
2544
+ * Deletes a channel installation.
2545
+ *
2546
+ * @param tenantId - Tenant identifier (used for isolation check)
2547
+ * @param installationId - Installation ID to delete
2548
+ * @returns `true` if deleted, `false` if not found or tenant mismatch
2549
+ */
2506
2550
  async deleteInstallation(tenantId, installationId) {
2507
2551
  const existing = this.installations.get(installationId);
2508
2552
  if (!existing || existing.tenantId !== tenantId) return false;
2509
2553
  return this.installations.delete(installationId);
2510
2554
  }
2555
+ /**
2556
+ * Clears all in-memory installations. Primarily used in tests.
2557
+ */
2511
2558
  clear() {
2512
2559
  this.installations.clear();
2513
2560
  }
@@ -2519,6 +2566,16 @@ var InMemoryBindingStore = class {
2519
2566
  constructor() {
2520
2567
  this.bindings = /* @__PURE__ */ new Map();
2521
2568
  }
2569
+ /**
2570
+ * Resolves a binding for the given sender across a specific channel installation.
2571
+ *
2572
+ * Called by `MessageRouter.dispatch()` during inbound message processing.
2573
+ * Matches on `channel + senderId + channelInstallationId + tenantId` and
2574
+ * requires `enabled === true`.
2575
+ *
2576
+ * @param params - Resolution parameters
2577
+ * @returns The matching {@link Binding} or `null` if none found.
2578
+ */
2522
2579
  async resolve(params) {
2523
2580
  for (const binding of this.bindings.values()) {
2524
2581
  if (binding.channel === params.channel && binding.senderId === params.senderId && binding.channelInstallationId === params.channelInstallationId && binding.tenantId === params.tenantId && binding.enabled) {
@@ -2527,6 +2584,12 @@ var InMemoryBindingStore = class {
2527
2584
  }
2528
2585
  return null;
2529
2586
  }
2587
+ /**
2588
+ * Creates a new sender-to-agent binding.
2589
+ *
2590
+ * @param input - {@link CreateBindingInput} defining channel, sender, target agent, and thread mode.
2591
+ * @returns The newly created {@link Binding}.
2592
+ */
2530
2593
  async create(input) {
2531
2594
  const now = /* @__PURE__ */ new Date();
2532
2595
  const binding = {
@@ -2549,6 +2612,14 @@ var InMemoryBindingStore = class {
2549
2612
  this.bindings.set(binding.id, binding);
2550
2613
  return binding;
2551
2614
  }
2615
+ /**
2616
+ * Updates an existing binding (e.g. changing the target agent or thread mode).
2617
+ *
2618
+ * @param id - Binding ID
2619
+ * @param patch - Partial {@link Binding} fields to update
2620
+ * @returns The updated {@link Binding}
2621
+ * @throws {Error} If the binding does not exist
2622
+ */
2552
2623
  async update(id, patch) {
2553
2624
  const existing = this.bindings.get(id);
2554
2625
  if (!existing) throw new Error(`Binding ${id} not found`);
@@ -2560,9 +2631,20 @@ var InMemoryBindingStore = class {
2560
2631
  this.bindings.set(id, updated);
2561
2632
  return updated;
2562
2633
  }
2634
+ /**
2635
+ * Deletes a binding by ID.
2636
+ *
2637
+ * @param id - Binding ID
2638
+ */
2563
2639
  async delete(id) {
2564
2640
  this.bindings.delete(id);
2565
2641
  }
2642
+ /**
2643
+ * Lists bindings filtered by tenant and optional channel/agent/installation.
2644
+ *
2645
+ * @param params - Filter and pagination parameters
2646
+ * @returns Array of matching {@link Binding} objects
2647
+ */
2566
2648
  async list(params) {
2567
2649
  let results = Array.from(this.bindings.values()).filter((b) => {
2568
2650
  if (b.tenantId !== params.tenantId) return false;
@@ -2575,6 +2657,12 @@ var InMemoryBindingStore = class {
2575
2657
  const limit = params.limit ?? 50;
2576
2658
  return results.slice(offset, offset + limit);
2577
2659
  }
2660
+ /**
2661
+ * Bulk-imports bindings.
2662
+ *
2663
+ * @param bindings - Array of {@link CreateBindingInput}
2664
+ * @returns Array of created {@link Binding} objects
2665
+ */
2578
2666
  async import(bindings) {
2579
2667
  const result = [];
2580
2668
  for (const input of bindings) {
@@ -2582,14 +2670,104 @@ var InMemoryBindingStore = class {
2582
2670
  }
2583
2671
  return result;
2584
2672
  }
2673
+ /**
2674
+ * Exports all bindings for a tenant.
2675
+ *
2676
+ * @param params - Filter by tenantId
2677
+ * @returns Array of {@link Binding} objects
2678
+ */
2585
2679
  async export(params) {
2586
2680
  return this.list({ tenantId: params.tenantId, limit: 1e4 });
2587
2681
  }
2682
+ /**
2683
+ * Clears all in-memory bindings. Primarily used in tests.
2684
+ */
2588
2685
  clear() {
2589
2686
  this.bindings.clear();
2590
2687
  }
2591
2688
  };
2592
2689
 
2690
+ // src/store_lattice/InMemoryA2AApiKeyStore.ts
2691
+ import { randomUUID as randomUUID2 } from "crypto";
2692
+ function generateApiKey() {
2693
+ return `a2a_${randomUUID2().replace(/-/g, "")}`;
2694
+ }
2695
+ var InMemoryA2AApiKeyStore = class {
2696
+ constructor() {
2697
+ this.keys = /* @__PURE__ */ new Map();
2698
+ }
2699
+ async findByKey(key) {
2700
+ for (const record of this.keys.values()) {
2701
+ if (record.key === key && record.enabled) return record;
2702
+ }
2703
+ return null;
2704
+ }
2705
+ async list(params) {
2706
+ let records = Array.from(this.keys.values());
2707
+ if (params.tenantId) {
2708
+ records = records.filter((r) => r.tenantId === params.tenantId);
2709
+ }
2710
+ const offset = params.offset || 0;
2711
+ const limit = params.limit;
2712
+ records = records.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
2713
+ return limit ? records.slice(offset, offset + limit) : records.slice(offset);
2714
+ }
2715
+ async create(input) {
2716
+ const now = /* @__PURE__ */ new Date();
2717
+ const record = {
2718
+ id: randomUUID2(),
2719
+ key: generateApiKey(),
2720
+ tenantId: input.tenantId,
2721
+ projectId: input.projectId,
2722
+ workspaceId: input.workspaceId,
2723
+ label: input.label,
2724
+ enabled: true,
2725
+ createdAt: now,
2726
+ updatedAt: now
2727
+ };
2728
+ this.keys.set(record.id, record);
2729
+ return record;
2730
+ }
2731
+ async disable(id) {
2732
+ const record = this.keys.get(id);
2733
+ if (!record) throw new Error(`A2A API key not found: ${id}`);
2734
+ const updated = { ...record, enabled: false, updatedAt: /* @__PURE__ */ new Date() };
2735
+ this.keys.set(id, updated);
2736
+ return updated;
2737
+ }
2738
+ async enable(id) {
2739
+ const record = this.keys.get(id);
2740
+ if (!record) throw new Error(`A2A API key not found: ${id}`);
2741
+ const updated = { ...record, enabled: true, updatedAt: /* @__PURE__ */ new Date() };
2742
+ this.keys.set(id, updated);
2743
+ return updated;
2744
+ }
2745
+ async rotate(id) {
2746
+ const record = this.keys.get(id);
2747
+ if (!record) throw new Error(`A2A API key not found: ${id}`);
2748
+ const updated = { ...record, key: generateApiKey(), updatedAt: /* @__PURE__ */ new Date() };
2749
+ this.keys.set(id, updated);
2750
+ return updated;
2751
+ }
2752
+ async delete(id) {
2753
+ this.keys.delete(id);
2754
+ }
2755
+ async loadIntoMap() {
2756
+ const map = /* @__PURE__ */ new Map();
2757
+ for (const record of this.keys.values()) {
2758
+ if (record.enabled) {
2759
+ map.set(record.key, {
2760
+ key: record.key,
2761
+ tenantId: record.tenantId,
2762
+ projectId: record.projectId,
2763
+ workspaceId: record.workspaceId
2764
+ });
2765
+ }
2766
+ }
2767
+ return map;
2768
+ }
2769
+ };
2770
+
2593
2771
  // src/store_lattice/StoreLatticeManager.ts
2594
2772
  var StoreLatticeManager = class _StoreLatticeManager extends BaseLatticeManager {
2595
2773
  /**
@@ -2764,6 +2942,12 @@ storeLatticeManager.registerLattice(
2764
2942
  "channelBinding",
2765
2943
  defaultChannelBindingStore
2766
2944
  );
2945
+ var defaultA2AApiKeyStore = new InMemoryA2AApiKeyStore();
2946
+ storeLatticeManager.registerLattice(
2947
+ "default",
2948
+ "a2aApiKey",
2949
+ defaultA2AApiKeyStore
2950
+ );
2767
2951
 
2768
2952
  // src/tool_lattice/manage_binding/index.ts
2769
2953
  function getInstallationStore() {
@@ -6860,6 +7044,7 @@ import {
6860
7044
  isDeepAgentConfig,
6861
7045
  isProcessingAgentConfig,
6862
7046
  isTeamAgentConfig,
7047
+ isA2ARemoteAgentConfig,
6863
7048
  getToolsFromConfig,
6864
7049
  getSubAgentsFromConfig
6865
7050
  } from "@axiom-lattice/protocols";
@@ -11906,6 +12091,14 @@ var ThreadStatus2 = /* @__PURE__ */ ((ThreadStatus3) => {
11906
12091
  return ThreadStatus3;
11907
12092
  })(ThreadStatus2 || {});
11908
12093
  var Agent = class {
12094
+ /**
12095
+ * Constructs an Agent instance.
12096
+ *
12097
+ * Prefer {@link AgentInstanceManager.getAgent} over direct construction — it
12098
+ * ensures a single instance per thread.
12099
+ *
12100
+ * @param params - {@link AgentThreadInterface}
12101
+ */
11909
12102
  constructor({
11910
12103
  assistant_id,
11911
12104
  thread_id,
@@ -12118,6 +12311,7 @@ var Agent = class {
12118
12311
  await this.queueStore?.removeMessage(p.id);
12119
12312
  const runStatus = await this.getRunStatus();
12120
12313
  const state = await this.getCurrentState();
12314
+ const customRunConfig = p.custom_run_config ?? queueMessageData.custom_run_config;
12121
12315
  if (runStatus === "interrupted" /* INTERRUPTED */) {
12122
12316
  this.publish("message:interrupted", {
12123
12317
  type: "message:interrupted",
@@ -12142,6 +12336,12 @@ var Agent = class {
12142
12336
  state
12143
12337
  });
12144
12338
  }
12339
+ this.publish("reply:ready", {
12340
+ type: "reply:ready",
12341
+ timestamp: /* @__PURE__ */ new Date(),
12342
+ state,
12343
+ customRunConfig
12344
+ });
12145
12345
  } catch (error) {
12146
12346
  console.error(`STEER/Command message ${p.id} execution failed:`, error);
12147
12347
  this.addChunk({
@@ -12220,6 +12420,12 @@ var Agent = class {
12220
12420
  });
12221
12421
  }
12222
12422
  }
12423
+ this.publish("reply:ready", {
12424
+ type: "reply:ready",
12425
+ timestamp: /* @__PURE__ */ new Date(),
12426
+ state,
12427
+ customRunConfig: remainingPendings[0]?.custom_run_config ?? firstQueueMessage?.custom_run_config
12428
+ });
12223
12429
  } catch (error) {
12224
12430
  console.error(`COLLECT mode execution failed:`, error);
12225
12431
  for (const p of remainingPendings) {
@@ -12268,6 +12474,7 @@ var Agent = class {
12268
12474
  await this.queueStore?.removeMessage(p.id);
12269
12475
  const runStatus = await this.getRunStatus();
12270
12476
  const state = await this.getCurrentState();
12477
+ const customRunConfig = p.custom_run_config ?? queueMessageData.custom_run_config;
12271
12478
  if (runStatus === "interrupted" /* INTERRUPTED */) {
12272
12479
  this.publish("message:interrupted", {
12273
12480
  type: "message:interrupted",
@@ -12292,6 +12499,12 @@ var Agent = class {
12292
12499
  state
12293
12500
  });
12294
12501
  }
12502
+ this.publish("reply:ready", {
12503
+ type: "reply:ready",
12504
+ timestamp: /* @__PURE__ */ new Date(),
12505
+ state,
12506
+ customRunConfig
12507
+ });
12295
12508
  } catch (error) {
12296
12509
  console.error(`FOLLOWUP mode message ${p.id} execution failed:`, error);
12297
12510
  this.addChunk({
@@ -12334,9 +12547,25 @@ var Agent = class {
12334
12547
  setQueueStore(store) {
12335
12548
  this.queueStore = store;
12336
12549
  }
12550
+ /**
12551
+ * Push a chunk into the streaming buffer for this thread.
12552
+ *
12553
+ * Consumers read chunks via {@link chunkStream}.
12554
+ *
12555
+ * @param content - The message chunk to buffer
12556
+ */
12337
12557
  addChunk(content) {
12338
12558
  return this.chunkBuffer.addChunk(this.thread_id, content);
12339
12559
  }
12560
+ /**
12561
+ * Returns an async iterator over new chunks since the given message ID.
12562
+ *
12563
+ * Used by SSE endpoints to stream agent output to clients in real time.
12564
+ *
12565
+ * @param message_id - The client message ID to start streaming from
12566
+ * @param stopTypes - Optional chunk types that terminate the stream
12567
+ * @returns An async iterable yielding {@link MessageChunk} objects
12568
+ */
12340
12569
  chunkStream(message_id, stopTypes) {
12341
12570
  const stream = this.chunkBuffer.getNewChunksSinceContentIterator(
12342
12571
  this.thread_id,
@@ -12425,13 +12654,21 @@ var Agent = class {
12425
12654
  };
12426
12655
  }
12427
12656
  /**
12428
- * Set queue configuration for thread
12657
+ * Override the thread's queue processing mode.
12658
+ *
12659
+ * @param config - Partial {@link ThreadQueueConfig} (e.g. `{ mode: QueueMode.FOLLOWUP }`)
12429
12660
  */
12430
12661
  async setQueueConfig(config) {
12431
12662
  this.queueMode = { ...this.queueMode, ...config };
12432
12663
  }
12433
12664
  /**
12434
- * Stop queue processor
12665
+ * Abort any ongoing queue processing without clearing the queue.
12666
+ *
12667
+ * Used internally by STEER mode to interrupt the current execution so the
12668
+ * steer message can be processed next. For a full abort that also clears
12669
+ * pending messages, use {@link abort}.
12670
+ *
12671
+ * @see {@link abort}
12435
12672
  */
12436
12673
  stopQueueProcessor() {
12437
12674
  if (this.abortController) {
@@ -12440,14 +12677,34 @@ var Agent = class {
12440
12677
  }
12441
12678
  }
12442
12679
  /**
12443
- * Add message to queue
12444
- * All messages go to queue, processor auto-starts if not running
12445
- * STEER/Command messages are inserted at head of queue for immediate processing
12446
- *
12447
- * Supports both legacy single message format and new messages[] format:
12448
- * - Legacy: input.message (single human message)
12449
- * - New: input.messages[] (array of mixed human/system messages)
12450
- * - When input.messages is provided, it takes precedence over input.message
12680
+ * Enqueue a message for this thread.
12681
+ *
12682
+ * Messages are always queued; the queue processor starts automatically if idle.
12683
+ * Returns immediately with the message ID — execution is asynchronous.
12684
+ *
12685
+ * **Queue modes** (via the optional `mode` param):
12686
+ * - `COLLECT` (default) Batch multiple pending messages into one agent call.
12687
+ * - `FOLLOWUP` — Process messages one at a time in order.
12688
+ * - `STEER` — High-priority, inserted at queue head, interrupts current processing.
12689
+ *
12690
+ * **Format**: Supports both `input.message` (legacy string) and `input.messages[]`
12691
+ * (array of `{ role, content }` objects). The array form takes precedence.
12692
+ *
12693
+ * The `custom_run_config` field is round-tripped through the queue and emitted
12694
+ * back in {@link ReplyReadyEvent}, enabling callers to attach routing metadata
12695
+ * (e.g. `_replyTarget` for channel reply).
12696
+ *
12697
+ * @param queueMessage - The message to enqueue
12698
+ * @param mode - Optional queue mode override (defaults to thread's current mode)
12699
+ * @returns `{ queued: true, executed: false, messageId }` — execution happens asynchronously
12700
+ *
12701
+ * @example
12702
+ * ```ts
12703
+ * await agent.addMessage({
12704
+ * input: { message: "Hello" },
12705
+ * custom_run_config: { _replyTarget: { adapterChannel: "lark", rawTarget: { chatId: "xxx" } } },
12706
+ * });
12707
+ * ```
12451
12708
  */
12452
12709
  async addMessage(queueMessage, mode) {
12453
12710
  const useMode = mode ?? this.queueMode.mode;
@@ -12541,8 +12798,13 @@ var Agent = class {
12541
12798
  return { queued: true, executed: false, messageId };
12542
12799
  }
12543
12800
  /**
12544
- * Start queue processor if not already running
12545
- * Public method to allow external triggering (e.g., from recovery)
12801
+ * Start the queue processor if it is not already running.
12802
+ *
12803
+ * Called automatically by {@link addMessage} and {@link resumeTask}.
12804
+ * Safe to call externally — it is a no-op if processing is already active.
12805
+ *
12806
+ * Emits `thread:busy` before starting, and starts the {@link waitingForQueueEnd}
12807
+ * loop with a fresh {@link AbortController}.
12546
12808
  */
12547
12809
  async startQueueProcessorIfNeeded() {
12548
12810
  const store = this.getQueueStore();
@@ -12590,6 +12852,12 @@ var Agent = class {
12590
12852
  type: "system"
12591
12853
  });
12592
12854
  }
12855
+ /**
12856
+ * Returns a LangGraph StateSnapshot for this thread.
12857
+ *
12858
+ * Includes `state.values.messages` (full conversation history) and
12859
+ * `state.tasks` / `state.next` (execution progress).
12860
+ */
12593
12861
  async getCurrentState() {
12594
12862
  const { runnable_agent } = await this.getLatticeClientAndRuntimeConfig();
12595
12863
  const state = await runnable_agent.getState({
@@ -12597,6 +12865,14 @@ var Agent = class {
12597
12865
  });
12598
12866
  return state;
12599
12867
  }
12868
+ /**
12869
+ * Returns the conversation history as normalized message objects.
12870
+ *
12871
+ * Filters to `human`, `ai`, and `tool` message types. Each entry has
12872
+ * `{ id, role, content }` plus any LangChain kwargs.
12873
+ *
12874
+ * @returns Array of message objects with `id`, `role`, and `content`
12875
+ */
12600
12876
  async getCurrentMessages() {
12601
12877
  const state = await this.getCurrentState();
12602
12878
  const messages = state.values.messages || [];
@@ -12619,6 +12895,14 @@ var Agent = class {
12619
12895
  const image = await drawableGraph.drawMermaid();
12620
12896
  return image;
12621
12897
  }
12898
+ /**
12899
+ * Determine the current thread execution status.
12900
+ *
12901
+ * Checks LangGraph's `state.tasks` for interrupts and `state.next` for
12902
+ * pending steps.
12903
+ *
12904
+ * @returns {@link ThreadStatus} — `IDLE`, `BUSY`, or `INTERRUPTED`
12905
+ */
12622
12906
  async getRunStatus() {
12623
12907
  const state = await this.getCurrentState();
12624
12908
  const isInterrupted = state.tasks?.some(
@@ -12633,9 +12917,14 @@ var Agent = class {
12633
12917
  return "idle" /* IDLE */;
12634
12918
  }
12635
12919
  /**
12636
- * Resume task processing after server restart
12637
- * Resets any stuck processing messages to pending and starts queue processing
12638
- * Note: Does not rely on LangGraph state as it may be stale after crash/restart
12920
+ * Resume processing after a server restart.
12921
+ *
12922
+ * Resets any stuck "processing" messages back to "pending" and restarts the
12923
+ * queue processor. Skips threads that are in `INTERRUPTED` state (the
12924
+ * interruption was intentional).
12925
+ *
12926
+ * Called during gateway startup to recover threads that were mid-execution
12927
+ * when the server went down.
12639
12928
  */
12640
12929
  async resumeTask() {
12641
12930
  try {
@@ -12657,9 +12946,14 @@ var Agent = class {
12657
12946
  await this.startQueueProcessorIfNeeded();
12658
12947
  }
12659
12948
  /**
12660
- * Abort the current agent execution
12661
- * This will cancel any ongoing invoke or stream operations
12662
- * and clear all queued messages (pending + processing)
12949
+ * Fully abort all activity on this thread.
12950
+ *
12951
+ * Aborts any in-flight agent execution (via {@link AbortController}) and
12952
+ * clears all pending and processing messages from the queue. Also marks
12953
+ * the chunk buffer thread as aborted so streaming consumers can detect it.
12954
+ *
12955
+ * Unlike {@link stopQueueProcessor}, this is a destructive abort — the
12956
+ * queue is drained.
12663
12957
  */
12664
12958
  async abort() {
12665
12959
  if (this.abortController) {
@@ -12677,22 +12971,44 @@ var Agent = class {
12677
12971
  return this.abortController?.signal.aborted ?? false;
12678
12972
  }
12679
12973
  /**
12680
- * Subscribe to lifecycle events for this agent/thread
12681
- * Events are automatically namespaced by tenantId and threadId
12974
+ * Subscribe to a lifecycle event for this specific thread.
12975
+ *
12976
+ * Event names are namespaced as `{eventName}:{tenantId}:{threadId}` so
12977
+ * listeners only receive events for this agent instance.
12978
+ *
12979
+ * @param eventName - One of {@link AgentLifecycleEventName}
12980
+ * @param callback - Handler receiving the event data payload
12981
+ *
12982
+ * @example
12983
+ * ```ts
12984
+ * agent.subscribe("message:completed", (evt) => {
12985
+ * console.log("AI response:", evt.state?.values?.messages);
12986
+ * });
12987
+ * ```
12682
12988
  */
12683
12989
  subscribe(eventName, callback) {
12684
12990
  const namespacedEvent = `${eventName}:${this.tenant_id}:${this.thread_id}`;
12685
12991
  event_bus_default.subscribe(namespacedEvent, callback);
12686
12992
  }
12687
12993
  /**
12688
- * Unsubscribe from lifecycle events
12994
+ * Remove a previously registered event listener.
12995
+ *
12996
+ * @param eventName - The event that was subscribed to
12997
+ * @param callback - The same function reference used in {@link subscribe}
12689
12998
  */
12690
12999
  unsubscribe(eventName, callback) {
12691
13000
  const namespacedEvent = `${eventName}:${this.tenant_id}:${this.thread_id}`;
12692
13001
  event_bus_default.unsubscribe(namespacedEvent, callback);
12693
13002
  }
12694
13003
  /**
12695
- * Subscribe to lifecycle events once (auto-unsubscribe after first event)
13004
+ * Subscribe to a lifecycle event once the listener is removed after the
13005
+ * first invocation.
13006
+ *
13007
+ * Ideal for one-shot async patterns (e.g. waiting for `reply:ready` before
13008
+ * sending a channel reply).
13009
+ *
13010
+ * @param eventName - One of {@link AgentLifecycleEventName}
13011
+ * @param callback - Handler receiving the event data payload
12696
13012
  */
12697
13013
  subscribeOnce(eventName, callback) {
12698
13014
  const namespacedEvent = `${eventName}:${this.tenant_id}:${this.thread_id}`;
@@ -12706,6 +13022,12 @@ var Agent = class {
12706
13022
  console.log(namespacedEvent);
12707
13023
  event_bus_default.publish(namespacedEvent, data);
12708
13024
  }
13025
+ /**
13026
+ * Track a sub-agent async task spawned by this agent.
13027
+ *
13028
+ * Tasks are monitored by the agent task consumer in `packages/gateway`
13029
+ * and their status can be polled via {@link getAsyncTasks}.
13030
+ */
12709
13031
  addAsyncTask(task) {
12710
13032
  this.asyncTasks.push(task);
12711
13033
  }
@@ -12715,6 +13037,12 @@ var Agent = class {
12715
13037
  getAsyncTask(taskId) {
12716
13038
  return this.asyncTasks.find((t) => t.taskId === taskId);
12717
13039
  }
13040
+ /**
13041
+ * Update the status of a tracked async task.
13042
+ *
13043
+ * Terminal states (`completed`, `failed`, `cancelled`) automatically
13044
+ * set `completedAt` to the current timestamp.
13045
+ */
12718
13046
  updateAsyncTaskStatus(taskId, status) {
12719
13047
  const task = this.getAsyncTask(taskId);
12720
13048
  if (!task) return;
@@ -13080,6 +13408,65 @@ function createSchedulerMiddleware(options = {}) {
13080
13408
  });
13081
13409
  }
13082
13410
 
13411
+ // src/agent_lattice/builders/CustomMiddlewareRegistry.ts
13412
+ var CustomMiddlewareRegistry = class {
13413
+ /**
13414
+ * Register a custom middleware factory under the given key.
13415
+ *
13416
+ * The key is referenced by `config.key` in the database middleware configuration.
13417
+ * When an agent is built, the framework looks up this key and calls the factory
13418
+ * with the remaining config fields.
13419
+ *
13420
+ * @param key - Unique identifier, referenced in database config as `config.key`
13421
+ * @param factory - Function that receives config (minus `key`) and returns an AgentMiddleware
13422
+ *
13423
+ * @example
13424
+ * ```ts
13425
+ * CustomMiddlewareRegistry.register("my-logger", (config) =>
13426
+ * createMiddleware({ name: "Logger", beforeAgent: async () => { ... } }),
13427
+ * );
13428
+ * ```
13429
+ */
13430
+ static register(key, factory) {
13431
+ this.factories.set(key, factory);
13432
+ }
13433
+ /**
13434
+ * Remove a previously registered factory.
13435
+ *
13436
+ * @param key - The factory key to unregister
13437
+ * @returns `true` if a factory was removed, `false` if the key was not found
13438
+ */
13439
+ static unregister(key) {
13440
+ return this.factories.delete(key);
13441
+ }
13442
+ /**
13443
+ * Look up a factory by key.
13444
+ *
13445
+ * @param key - The factory key
13446
+ * @returns The factory function, or `undefined` if not registered
13447
+ */
13448
+ static get(key) {
13449
+ return this.factories.get(key);
13450
+ }
13451
+ /**
13452
+ * Check whether a factory is registered under the given key.
13453
+ *
13454
+ * @param key - The factory key to check
13455
+ */
13456
+ static has(key) {
13457
+ return this.factories.has(key);
13458
+ }
13459
+ /**
13460
+ * Get all currently registered factory keys.
13461
+ *
13462
+ * @returns Array of registered key strings
13463
+ */
13464
+ static list() {
13465
+ return Array.from(this.factories.keys());
13466
+ }
13467
+ };
13468
+ CustomMiddlewareRegistry.factories = /* @__PURE__ */ new Map();
13469
+
13083
13470
  // src/agent_lattice/builders/commonMiddleware.ts
13084
13471
  async function createCommonMiddlewares(middlewareConfigs, filesystemBackend, fsIsExised) {
13085
13472
  const middlewares = [];
@@ -13156,6 +13543,21 @@ async function createCommonMiddlewares(middlewareConfigs, filesystemBackend, fsI
13156
13543
  case "scheduler":
13157
13544
  middlewares.push(createSchedulerMiddleware(config.config));
13158
13545
  break;
13546
+ case "custom":
13547
+ {
13548
+ const customConfig = config.config;
13549
+ const { key, ...rest } = customConfig;
13550
+ const factory = CustomMiddlewareRegistry.get(key);
13551
+ if (factory) {
13552
+ const middleware = factory(rest);
13553
+ middlewares.push(middleware instanceof Promise ? await middleware : middleware);
13554
+ } else {
13555
+ console.warn(
13556
+ `[custom middleware] No factory registered for key "${key}". Use CustomMiddlewareRegistry.register("${key}", factory) before building the agent.`
13557
+ );
13558
+ }
13559
+ }
13560
+ break;
13159
13561
  }
13160
13562
  }
13161
13563
  return middlewares;
@@ -17787,6 +18189,227 @@ var ProcessingAgentGraphBuilder = class {
17787
18189
  }
17788
18190
  };
17789
18191
 
18192
+ // src/agent_lattice/builders/RemoteAgentGraphBuilder.ts
18193
+ import { StateGraph as StateGraph2, MessagesAnnotation } from "@langchain/langgraph";
18194
+ import { AIMessage as AIMessage4 } from "@langchain/core/messages";
18195
+
18196
+ // src/services/a2a-client.ts
18197
+ import { v4 as v42 } from "uuid";
18198
+ var A2ARemoteError = class extends Error {
18199
+ constructor(message, statusCode, body) {
18200
+ super(message);
18201
+ this.statusCode = statusCode;
18202
+ this.body = body;
18203
+ this.name = "A2ARemoteError";
18204
+ }
18205
+ };
18206
+ var A2ARemoteClient = class {
18207
+ constructor(config) {
18208
+ this.config = config;
18209
+ this.jsonRpcUrl = null;
18210
+ this.card = null;
18211
+ }
18212
+ // ── Resolution ───────────────────────────────────────────────────────────
18213
+ /**
18214
+ * Fetch the agent card and resolve the JSON-RPC endpoint.
18215
+ */
18216
+ async resolve() {
18217
+ if (this.card) return this.card;
18218
+ const resp = await fetch(this.config.agentCardUrl, {
18219
+ signal: AbortSignal.timeout(this.config.timeout ?? 3e4)
18220
+ });
18221
+ if (!resp.ok) {
18222
+ throw new A2ARemoteError(
18223
+ `Failed to fetch agent card: HTTP ${resp.status}`,
18224
+ resp.status
18225
+ );
18226
+ }
18227
+ this.card = await resp.json();
18228
+ const card = this.card;
18229
+ const jsonRpc = card.additionalInterfaces?.find(
18230
+ (i) => i.transport === "JSONRPC"
18231
+ );
18232
+ this.jsonRpcUrl = jsonRpc?.url ?? card.url;
18233
+ return this.card;
18234
+ }
18235
+ // ── Execute ──────────────────────────────────────────────────────────────
18236
+ /**
18237
+ * Send a text prompt to the remote A2A agent and return the response.
18238
+ *
18239
+ * Supports SSE streaming (parses status-update events) and falls back
18240
+ * to polling the task status for non-streaming agents.
18241
+ */
18242
+ async sendMessage(text) {
18243
+ await this.resolve();
18244
+ const taskId = v42();
18245
+ const body = JSON.stringify({
18246
+ jsonrpc: "2.0",
18247
+ method: "tasks/send",
18248
+ params: {
18249
+ id: taskId,
18250
+ message: {
18251
+ role: "user",
18252
+ parts: [{ kind: "text", text }]
18253
+ }
18254
+ },
18255
+ id: taskId
18256
+ });
18257
+ const headers = {
18258
+ "Content-Type": "application/json",
18259
+ "Accept": "text/event-stream"
18260
+ };
18261
+ if (this.config.apiKey) {
18262
+ headers["Authorization"] = `Bearer ${this.config.apiKey}`;
18263
+ }
18264
+ const resp = await fetch(this.jsonRpcUrl, {
18265
+ method: "POST",
18266
+ headers,
18267
+ body,
18268
+ signal: AbortSignal.timeout(this.config.timeout ?? 3e5)
18269
+ });
18270
+ if (!resp.ok) {
18271
+ const text2 = await resp.text().catch(() => "");
18272
+ throw new A2ARemoteError(
18273
+ `A2A request failed: HTTP ${resp.status}`,
18274
+ resp.status,
18275
+ text2
18276
+ );
18277
+ }
18278
+ const contentType = resp.headers.get("content-type") ?? "";
18279
+ if (contentType.includes("text/event-stream")) {
18280
+ return this.parseSSE(resp);
18281
+ }
18282
+ const json = await resp.json();
18283
+ return this.extractArtifactText(json);
18284
+ }
18285
+ // ── SSE Parsing ──────────────────────────────────────────────────────────
18286
+ async parseSSE(resp) {
18287
+ if (!resp.body) {
18288
+ throw new A2ARemoteError("Empty response body");
18289
+ }
18290
+ const reader = resp.body.getReader();
18291
+ const decoder = new TextDecoder();
18292
+ let buffer2 = "";
18293
+ let accumulatedText = "";
18294
+ try {
18295
+ while (true) {
18296
+ const { done, value } = await reader.read();
18297
+ if (done) break;
18298
+ buffer2 += decoder.decode(value, { stream: true });
18299
+ const lines = buffer2.split("\n");
18300
+ buffer2 = lines.pop() ?? "";
18301
+ for (const line of lines) {
18302
+ if (line.startsWith("data: ")) {
18303
+ const data = line.slice(6).trim();
18304
+ if (!data) continue;
18305
+ try {
18306
+ const event = JSON.parse(data);
18307
+ this.handleSSEEvent(event, (t) => {
18308
+ accumulatedText += t;
18309
+ });
18310
+ } catch {
18311
+ }
18312
+ }
18313
+ }
18314
+ }
18315
+ if (buffer2.startsWith("data: ")) {
18316
+ const data = buffer2.slice(6).trim();
18317
+ if (data) {
18318
+ try {
18319
+ const event = JSON.parse(data);
18320
+ this.handleSSEEvent(event, (t) => {
18321
+ accumulatedText += t;
18322
+ });
18323
+ } catch {
18324
+ }
18325
+ }
18326
+ }
18327
+ } finally {
18328
+ reader.releaseLock();
18329
+ }
18330
+ return accumulatedText;
18331
+ }
18332
+ handleSSEEvent(event, onText) {
18333
+ const e = event;
18334
+ const messageParts = e.status?.message?.parts;
18335
+ if (messageParts) {
18336
+ for (const part of messageParts) {
18337
+ if (part.text) onText(part.text);
18338
+ }
18339
+ }
18340
+ }
18341
+ // ── Artifact Extraction ──────────────────────────────────────────────────
18342
+ extractArtifactText(task) {
18343
+ const artifacts = task.artifacts ?? [];
18344
+ return artifacts.flatMap((a) => a.parts ?? []).filter((p) => p.text).map((p) => p.text).join("\n");
18345
+ }
18346
+ };
18347
+
18348
+ // src/agent_lattice/builders/RemoteAgentGraphBuilder.ts
18349
+ import { AgentType as AgentType2 } from "@axiom-lattice/protocols";
18350
+ var RemoteAgentGraphBuilder = class {
18351
+ async build(agentLattice, params) {
18352
+ if (agentLattice.config.type !== AgentType2.A2A_REMOTE) {
18353
+ throw new Error(
18354
+ `RemoteAgentGraphBuilder received wrong agent type: ${agentLattice.config.type}`
18355
+ );
18356
+ }
18357
+ const config = agentLattice.config;
18358
+ const client = new A2ARemoteClient({
18359
+ agentCardUrl: config.agentCardUrl,
18360
+ apiKey: config.apiKey,
18361
+ timeout: config.timeout
18362
+ });
18363
+ await client.resolve();
18364
+ const remoteCallNode = async (state) => {
18365
+ const messages = state.messages ?? [];
18366
+ const text = extractLastHumanMessage(messages);
18367
+ if (!text) {
18368
+ return {
18369
+ messages: [
18370
+ new AIMessage4("No text input provided to remote agent.")
18371
+ ]
18372
+ };
18373
+ }
18374
+ try {
18375
+ const fullPrompt = params.prompt ? `${params.prompt}
18376
+
18377
+ User request:
18378
+ ${text}` : text;
18379
+ const response = await client.sendMessage(fullPrompt);
18380
+ return {
18381
+ messages: [new AIMessage4(response)]
18382
+ };
18383
+ } catch (error) {
18384
+ const msg = error.message ?? String(error);
18385
+ return {
18386
+ messages: [
18387
+ new AIMessage4(`Remote A2A agent error: ${msg}`)
18388
+ ]
18389
+ };
18390
+ }
18391
+ };
18392
+ const workflow = new StateGraph2(MessagesAnnotation).addNode("remote_call", remoteCallNode).addEdge("__start__", "remote_call").addEdge("remote_call", "__end__");
18393
+ return workflow.compile();
18394
+ }
18395
+ };
18396
+ function extractLastHumanMessage(messages) {
18397
+ for (let i = messages.length - 1; i >= 0; i--) {
18398
+ const msg = messages[i];
18399
+ if (msg._getType() === "human") {
18400
+ const content = msg.content;
18401
+ if (typeof content === "string") return content;
18402
+ if (Array.isArray(content)) {
18403
+ return content.filter(
18404
+ (c) => typeof c === "object" && c !== null && c.type === "text"
18405
+ ).map((c) => c.text).join("\n");
18406
+ }
18407
+ return String(content);
18408
+ }
18409
+ }
18410
+ return "";
18411
+ }
18412
+
17790
18413
  // src/agent_lattice/builders/AgentGraphBuilderFactory.ts
17791
18414
  var AgentGraphBuilderFactory = class _AgentGraphBuilderFactory {
17792
18415
  constructor() {
@@ -17810,6 +18433,7 @@ var AgentGraphBuilderFactory = class _AgentGraphBuilderFactory {
17810
18433
  this.builders.set(AgentType.DEEP_AGENT, new DeepAgentGraphBuilder());
17811
18434
  this.builders.set(AgentType.TEAM, new TeamAgentGraphBuilder());
17812
18435
  this.builders.set(AgentType.PROCESSING, new ProcessingAgentGraphBuilder());
18436
+ this.builders.set(AgentType.A2A_REMOTE, new RemoteAgentGraphBuilder());
17813
18437
  }
17814
18438
  /**
17815
18439
  * 注册自定义Builder
@@ -18486,8 +19110,8 @@ ${body}` : `${frontmatter}
18486
19110
 
18487
19111
  // src/agent_lattice/agentArchitectTools.ts
18488
19112
  import z55 from "zod";
18489
- import { v4 as v42 } from "uuid";
18490
- import { AgentType as AgentType2 } from "@axiom-lattice/protocols";
19113
+ import { v4 as v43 } from "uuid";
19114
+ import { AgentType as AgentType3 } from "@axiom-lattice/protocols";
18491
19115
  function getTenantId(exeConfig) {
18492
19116
  const runConfig = exeConfig?.configurable?.runConfig || {};
18493
19117
  return runConfig.tenantId || "default";
@@ -18595,7 +19219,7 @@ registerToolLattice(
18595
19219
  key: id,
18596
19220
  name: input.name,
18597
19221
  description: input.description || "",
18598
- type: input.type === "deep_agent" ? AgentType2.DEEP_AGENT : AgentType2.REACT,
19222
+ type: input.type === "deep_agent" ? AgentType3.DEEP_AGENT : AgentType3.REACT,
18599
19223
  prompt: input.prompt,
18600
19224
  ...input.tools && input.tools.length > 0 ? { tools: input.tools } : {},
18601
19225
  ...input.middleware && input.middleware.length > 0 ? { middleware: input.middleware } : {},
@@ -18684,7 +19308,7 @@ registerToolLattice(
18684
19308
  key: id,
18685
19309
  name: input.name,
18686
19310
  description: input.description || "",
18687
- type: AgentType2.PROCESSING,
19311
+ type: AgentType3.PROCESSING,
18688
19312
  prompt: input.prompt,
18689
19313
  ...input.tools && input.tools.length > 0 ? { tools: input.tools } : {},
18690
19314
  middleware,
@@ -18752,7 +19376,7 @@ registerToolLattice(
18752
19376
  return JSON.stringify({ error: `Agent '${input.id}' not found` });
18753
19377
  }
18754
19378
  const existingConfig = existing.graphDefinition || {};
18755
- if (existingConfig.type !== AgentType2.PROCESSING) {
19379
+ if (existingConfig.type !== AgentType3.PROCESSING) {
18756
19380
  return JSON.stringify({ error: `Agent '${input.id}' is not a PROCESSING agent (type: ${existingConfig.type})` });
18757
19381
  }
18758
19382
  const normalize = (s) => s.toLowerCase().replace(/[_\-\s]/g, "");
@@ -18960,7 +19584,7 @@ registerToolLattice(
18960
19584
  if (!existing) {
18961
19585
  return JSON.stringify({ error: `Agent '${id}' not found` });
18962
19586
  }
18963
- const threadId = v42();
19587
+ const threadId = v43();
18964
19588
  const agent = new Agent({
18965
19589
  tenant_id: tenantId,
18966
19590
  assistant_id: id,
@@ -18984,7 +19608,7 @@ registerToolLattice(
18984
19608
  );
18985
19609
 
18986
19610
  // src/agent_lattice/agentArchitectConfig.ts
18987
- import { AgentType as AgentType4 } from "@axiom-lattice/protocols";
19611
+ import { AgentType as AgentType5 } from "@axiom-lattice/protocols";
18988
19612
 
18989
19613
  // src/agent_lattice/agentArchitectPrompt.ts
18990
19614
  var AGENT_ARCHITECT_PROMPT = `# Agent Architect
@@ -19624,7 +20248,7 @@ Updates a PROCESSING agent's topology edges, name, prompt, or sub-agents. Unlike
19624
20248
  `;
19625
20249
 
19626
20250
  // src/agent_lattice/agentReviewerConfig.ts
19627
- import { AgentType as AgentType3 } from "@axiom-lattice/protocols";
20251
+ import { AgentType as AgentType4 } from "@axiom-lattice/protocols";
19628
20252
 
19629
20253
  // src/agent_lattice/agentReviewerPrompt.ts
19630
20254
  var AGENT_REVIEWER_PROMPT = `# Agent Reviewer
@@ -19687,7 +20311,7 @@ var agentReviewerConfig = {
19687
20311
  key: AGENT_REVIEWER_KEY,
19688
20312
  name: "Agent Reviewer",
19689
20313
  description: "Review and test AI agents. Use this agent to check agent configurations for correctness and to test agents by invoking them with realistic messages.",
19690
- type: AgentType3.REACT,
20314
+ type: AgentType4.REACT,
19691
20315
  prompt: AGENT_REVIEWER_PROMPT,
19692
20316
  tools: [
19693
20317
  "invoke_agent",
@@ -19713,7 +20337,7 @@ var agentArchitectConfig = {
19713
20337
  key: AGENT_ARCHITECT_KEY,
19714
20338
  name: "Agent Architect",
19715
20339
  description: "Design and manage AI agents through natural language conversation. Use this agent when you want to create a new agent, modify an existing agent, or manage your agent collection (list, view, update, delete).",
19716
- type: AgentType4.DEEP_AGENT,
20340
+ type: AgentType5.DEEP_AGENT,
19717
20341
  prompt: AGENT_ARCHITECT_PROMPT,
19718
20342
  tools: [
19719
20343
  "list_agents",
@@ -22532,6 +23156,7 @@ export {
22532
23156
  CompositeBackend,
22533
23157
  ConsoleLoggerClient,
22534
23158
  CustomMetricsClient,
23159
+ CustomMiddlewareRegistry,
22535
23160
  DaytonaInstance,
22536
23161
  DaytonaProvider,
22537
23162
  DefaultScheduleClient,
@@ -22542,6 +23167,7 @@ export {
22542
23167
  FileSystemSkillStore,
22543
23168
  FilesystemBackend,
22544
23169
  HumanMessage3 as HumanMessage,
23170
+ InMemoryA2AApiKeyStore,
22545
23171
  InMemoryAssistantStore,
22546
23172
  InMemoryBindingStore,
22547
23173
  InMemoryChannelInstallationStore,