@axiom-lattice/gateway 4.7.0 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/{OrphanRecoveryRuntime-MQDEJX3M.mjs → OrphanRecoveryRuntime-BEFILJMU.mjs} +2 -2
  2. package/dist/{chunk-2POW27EI.mjs → chunk-7CG57LEF.mjs} +2 -2
  3. package/dist/{chunk-WKNZ7GBC.mjs → chunk-EXPERMZR.mjs} +1 -1
  4. package/dist/chunk-EXPERMZR.mjs.map +1 -0
  5. package/dist/{chunk-52SUWMOX.mjs → chunk-JUA3VJE7.mjs} +3 -2
  6. package/dist/chunk-JUA3VJE7.mjs.map +1 -0
  7. package/dist/{chunk-TJAUXMYJ.mjs → chunk-URXZPI2F.mjs} +8 -1
  8. package/dist/chunk-URXZPI2F.mjs.map +1 -0
  9. package/dist/{chunk-KDQKTU3N.mjs → chunk-ZBTG3T7K.mjs} +44 -8
  10. package/dist/chunk-ZBTG3T7K.mjs.map +1 -0
  11. package/dist/embed/{embed-page-AgentDetailSideApp-2C2ZR62Q.js → embed-page-AgentDetailSideApp-NTLAFO5C.js} +1 -1
  12. package/dist/embed/{embed-page-AgentGovernanceWorkbench-GG4ONSTS.js → embed-page-AgentGovernanceWorkbench-QPVBOMLH.js} +1 -1
  13. package/dist/embed/{embed-page-MessageGraph-WMYTVUUC.js → embed-page-MessageGraph-JAJGMYKH.js} +1 -1
  14. package/dist/embed/{embed-page-ReactChart-7XCMDMH6.js → embed-page-ReactChart-WUG6TEAZ.js} +1 -1
  15. package/dist/embed/{embed-page-SkillFlow-XMMHEHQL.js → embed-page-SkillFlow-TSBZ4V2P.js} +1 -1
  16. package/dist/embed/{embed-page-TasksSideApp-4ZJ4Q4CL.js → embed-page-TasksSideApp-TJI22YGD.js} +1 -1
  17. package/dist/embed/{embed-page-TeamWorkspace-H5OOEN3K.js → embed-page-TeamWorkspace-OJJLN5VI.js} +1 -1
  18. package/dist/embed/{embed-page-TopologyRuntimeView-QMXWERVC.js → embed-page-TopologyRuntimeView-ZFU4N6TV.js} +1 -1
  19. package/dist/embed/{embed-page-WorkflowAutomationView-L2OEGCA5.js → embed-page-WorkflowAutomationView-ALXV7VQQ.js} +1 -1
  20. package/dist/embed/{embed-page-chunk-CZE4IEB3.js → embed-page-chunk-DZQBVJ7Z.js} +1 -1
  21. package/dist/embed/{embed-page-chunk-N2ISCAS7.js → embed-page-chunk-IPIJ2SG6.js} +1 -1
  22. package/dist/embed/{embed-page-chunk-SUMYB6ZA.js → embed-page-chunk-N22EGQAJ.js} +1 -1
  23. package/dist/embed/{embed-page-chunk-OHDEKFTU.js → embed-page-chunk-O72XBKQH.js} +2 -2
  24. package/dist/embed/{embed-page-chunk-XIXSRYJY.js → embed-page-chunk-QMVIL4BJ.js} +190 -185
  25. package/dist/embed/{embed-page-chunk-2G54EIW2.js → embed-page-chunk-UT3WYBZH.js} +1 -1
  26. package/dist/embed/embed-page-chunk-YPNSOZYW.js +8 -0
  27. package/dist/embed/embed-page.js +1 -1
  28. package/dist/index.d.mts +146 -17
  29. package/dist/index.d.ts +146 -17
  30. package/dist/index.js +1190 -328
  31. package/dist/index.js.map +1 -1
  32. package/dist/index.mjs +649 -97
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/{open-STFMNRO5.mjs → open-UVKMONVE.mjs} +5 -4
  35. package/dist/open-UVKMONVE.mjs.map +1 -0
  36. package/dist/{open-files-NPNQQ6TY.mjs → open-files-X3HXVEEM.mjs} +4 -4
  37. package/dist/{open-session-IHDGAXL3.mjs → open-session-2Y7XJJW2.mjs} +3 -3
  38. package/dist/{resources-DXPXAXEZ.mjs → resources-2L3KSUUU.mjs} +2 -2
  39. package/dist/sandboxOpenCredentialProvider-USIU7Z7H.mjs +275 -0
  40. package/dist/sandboxOpenCredentialProvider-USIU7Z7H.mjs.map +1 -0
  41. package/dist/{sandboxProvider-I3P66VXT.mjs → sandboxProvider-DUYWHYJN.mjs} +2 -2
  42. package/package.json +14 -8
  43. package/dist/chunk-52SUWMOX.mjs.map +0 -1
  44. package/dist/chunk-KDQKTU3N.mjs.map +0 -1
  45. package/dist/chunk-TJAUXMYJ.mjs.map +0 -1
  46. package/dist/chunk-WKNZ7GBC.mjs.map +0 -1
  47. package/dist/embed/embed-page-chunk-HEMNMC4X.js +0 -7
  48. package/dist/open-STFMNRO5.mjs.map +0 -1
  49. /package/dist/{OrphanRecoveryRuntime-MQDEJX3M.mjs.map → OrphanRecoveryRuntime-BEFILJMU.mjs.map} +0 -0
  50. /package/dist/{chunk-2POW27EI.mjs.map → chunk-7CG57LEF.mjs.map} +0 -0
  51. /package/dist/{open-files-NPNQQ6TY.mjs.map → open-files-X3HXVEEM.mjs.map} +0 -0
  52. /package/dist/{open-session-IHDGAXL3.mjs.map → open-session-2Y7XJJW2.mjs.map} +0 -0
  53. /package/dist/{resources-DXPXAXEZ.mjs.map → resources-2L3KSUUU.mjs.map} +0 -0
  54. /package/dist/{sandboxProvider-I3P66VXT.mjs.map → sandboxProvider-DUYWHYJN.mjs.map} +0 -0
package/dist/index.mjs CHANGED
@@ -9,13 +9,14 @@ import {
9
9
  } from "./chunk-73QZQQOT.mjs";
10
10
  import {
11
11
  buildPreviewTokenScript
12
- } from "./chunk-2POW27EI.mjs";
13
- import "./chunk-WKNZ7GBC.mjs";
12
+ } from "./chunk-7CG57LEF.mjs";
13
+ import "./chunk-EXPERMZR.mjs";
14
14
  import {
15
15
  CollectionService
16
16
  } from "./chunk-PWHUWFXL.mjs";
17
17
  import {
18
18
  createOrphanRecoveryRuntime,
19
+ createTaskRunLiveness,
19
20
  isProjectLifecycleTask,
20
21
  isRosterExemptProject,
21
22
  recoverTask,
@@ -23,7 +24,7 @@ import {
23
24
  resolveTaskOwnerMembership,
24
25
  resolveTopCaller,
25
26
  runOrphanRecovery
26
- } from "./chunk-KDQKTU3N.mjs";
27
+ } from "./chunk-ZBTG3T7K.mjs";
27
28
  import {
28
29
  createRecoveryCallerDeps
29
30
  } from "./chunk-NOXGKYUV.mjs";
@@ -858,9 +859,11 @@ async function getAssistantStats(request, reply) {
858
859
  // src/controllers/run.ts
859
860
  import { v4 } from "uuid";
860
861
  import {
861
- agentInstanceManager as agentInstanceManager2
862
+ agentInstanceManager as agentInstanceManager2,
863
+ getReplicaId,
864
+ getThreadRunOwner
862
865
  } from "@axiom-lattice/core";
863
- import { MessageChunkTypes } from "@axiom-lattice/protocols";
866
+ import { MessageChunkTypes, AGENT_AFFINITY_HEADER, AFFINITY_ECHO_HEADER, affinityEchoHeaders, REPLICA_HEADER, AFFINITY_MISS_CODE, buildAffinityMissChunk } from "@axiom-lattice/protocols";
864
867
 
865
868
  // src/utils/agentScope.ts
866
869
  function headerValue(value) {
@@ -960,6 +963,24 @@ async function writeSse(raw, value, closed) {
960
963
  }
961
964
 
962
965
  // src/controllers/run.ts
966
+ var replicaHeader = () => ({ [REPLICA_HEADER]: getReplicaId() });
967
+ var exposeHeaders = `${REPLICA_HEADER}, ${AFFINITY_ECHO_HEADER}`;
968
+ function applyAffinityEcho(request, reply) {
969
+ for (const [name, value] of Object.entries(affinityEchoHeaders(request.headers[AGENT_AFFINITY_HEADER]))) {
970
+ reply.header(name, value);
971
+ }
972
+ }
973
+ var AFFINITY_GRACE_MS = Number.isFinite(Number(process.env.AXIOM_AFFINITY_GRACE_MS)) ? Number(process.env.AXIOM_AFFINITY_GRACE_MS) : 500;
974
+ async function peerOwnsThread(input) {
975
+ if (!process.env.AXIOM_REPLICA_ID) return null;
976
+ if (!input.tenant_id || !input.assistant_id || !input.thread_id) return null;
977
+ const owner = await getThreadRunOwner({
978
+ tenantId: input.tenant_id,
979
+ assistantId: input.assistant_id,
980
+ threadId: input.thread_id
981
+ });
982
+ return owner && owner !== getReplicaId() ? owner : null;
983
+ }
963
984
  function getUserId2(request) {
964
985
  const authUser = request.user;
965
986
  if (authUser?.id) return authUser.id;
@@ -986,6 +1007,7 @@ var createRun = async (request, reply) => {
986
1007
  ...user_id && { user_id }
987
1008
  };
988
1009
  if (!assistant_id) {
1010
+ applyAffinityEcho(request, reply);
989
1011
  reply.status(400).send({
990
1012
  success: false,
991
1013
  error: "\u52A9\u624BID\u662F\u5FC5\u9700\u7684"
@@ -1007,6 +1029,8 @@ var createRun = async (request, reply) => {
1007
1029
  command,
1008
1030
  custom_run_config: mergedConfig
1009
1031
  }, mode);
1032
+ reply.header(REPLICA_HEADER, getReplicaId());
1033
+ applyAffinityEcho(request, reply);
1010
1034
  reply.status(202).send({
1011
1035
  success: true,
1012
1036
  messageId: result.messageId,
@@ -1020,7 +1044,10 @@ var createRun = async (request, reply) => {
1020
1044
  "Content-Type": "text/event-stream",
1021
1045
  "Cache-Control": "no-cache",
1022
1046
  Connection: "keep-alive",
1023
- "Access-Control-Allow-Origin": "*"
1047
+ "Access-Control-Allow-Origin": "*",
1048
+ "Access-Control-Expose-Headers": exposeHeaders,
1049
+ ...replicaHeader(),
1050
+ ...affinityEchoHeaders(request.headers[AGENT_AFFINITY_HEADER])
1024
1051
  });
1025
1052
  try {
1026
1053
  const messageInput = message_id ? { ...input, id: message_id } : input;
@@ -1031,6 +1058,18 @@ var createRun = async (request, reply) => {
1031
1058
  }, [MessageChunkTypes.MESSAGE_COMPLETED, MessageChunkTypes.INTERRUPT, MessageChunkTypes.MESSAGE_FAILED], {
1032
1059
  queueMode: mode
1033
1060
  });
1061
+ let peer = await peerOwnsThread({ tenant_id, assistant_id, thread_id });
1062
+ if (peer) {
1063
+ await new Promise((resolve3) => setTimeout(resolve3, AFFINITY_GRACE_MS));
1064
+ peer = await peerOwnsThread({ tenant_id, assistant_id, thread_id });
1065
+ }
1066
+ if (peer) {
1067
+ reply.raw.write(`data: ${JSON.stringify(buildAffinityMissChunk(peer, thread_id))}
1068
+
1069
+ `);
1070
+ reply.raw.end();
1071
+ return;
1072
+ }
1034
1073
  await pipeAsyncIterableToSse(reply.raw, result.stream, {
1035
1074
  onError: (error) => [{
1036
1075
  type: "error",
@@ -1062,12 +1101,15 @@ var createRun = async (request, reply) => {
1062
1101
  command,
1063
1102
  custom_run_config: mergedConfig
1064
1103
  });
1104
+ reply.header(REPLICA_HEADER, getReplicaId());
1105
+ applyAffinityEcho(request, reply);
1065
1106
  reply.status(200).send({
1066
1107
  success: true,
1067
1108
  ...result
1068
1109
  });
1069
1110
  }
1070
1111
  } catch (error) {
1112
+ applyAffinityEcho(request, reply);
1071
1113
  reply.status(500).send({
1072
1114
  success: false,
1073
1115
  error: `\u521B\u5EFA\u8FD0\u884C\u65F6\u53D1\u751F\u9519\u8BEF: ${error.message}`
@@ -1090,9 +1132,20 @@ var resumeStream = async (request, reply) => {
1090
1132
  "Content-Type": "text/event-stream",
1091
1133
  "Cache-Control": "no-cache",
1092
1134
  Connection: "keep-alive",
1093
- "Access-Control-Allow-Origin": "*"
1135
+ "Access-Control-Allow-Origin": "*",
1136
+ "Access-Control-Expose-Headers": exposeHeaders,
1137
+ ...replicaHeader(),
1138
+ ...affinityEchoHeaders(request.headers[AGENT_AFFINITY_HEADER])
1094
1139
  });
1095
1140
  try {
1141
+ const peer = await peerOwnsThread({ tenant_id, assistant_id, thread_id });
1142
+ if (peer) {
1143
+ reply.raw.write(`data: ${JSON.stringify(buildAffinityMissChunk(peer, thread_id))}
1144
+
1145
+ `);
1146
+ reply.raw.end();
1147
+ return;
1148
+ }
1096
1149
  const agent = agentInstanceManager2.getAgent({
1097
1150
  assistant_id,
1098
1151
  thread_id,
@@ -1143,12 +1196,22 @@ var abortRun = async (request, reply) => {
1143
1196
  workspace_id,
1144
1197
  project_id
1145
1198
  });
1199
+ const peer = await peerOwnsThread({ tenant_id, assistant_id: assistantId, thread_id: threadId });
1200
+ if (peer) {
1201
+ reply.header(REPLICA_HEADER, getReplicaId());
1202
+ applyAffinityEcho(request, reply);
1203
+ reply.status(409).send({ success: false, code: AFFINITY_MISS_CODE, owner: peer, threadId });
1204
+ return;
1205
+ }
1146
1206
  await agent.abort();
1207
+ reply.header(REPLICA_HEADER, getReplicaId());
1208
+ applyAffinityEcho(request, reply);
1147
1209
  reply.status(200).send({
1148
1210
  success: true,
1149
1211
  message: "Agent aborted"
1150
1212
  });
1151
1213
  } catch (error) {
1214
+ applyAffinityEcho(request, reply);
1152
1215
  reply.status(500).send({
1153
1216
  success: false,
1154
1217
  error: `Abort failed: ${error.message}`
@@ -1166,13 +1229,17 @@ var recoverRun = async (request, reply) => {
1166
1229
  workspace_id,
1167
1230
  project_id
1168
1231
  });
1169
- const { status } = await agent.resumeTask();
1232
+ const { status, leaseContended } = await agent.resumeTask();
1233
+ reply.header(REPLICA_HEADER, getReplicaId());
1234
+ applyAffinityEcho(request, reply);
1170
1235
  reply.status(200).send({
1171
1236
  success: true,
1172
1237
  threadId,
1173
- status
1238
+ status,
1239
+ ...leaseContended ? { code: AFFINITY_MISS_CODE } : {}
1174
1240
  });
1175
1241
  } catch (error) {
1242
+ applyAffinityEcho(request, reply);
1176
1243
  reply.status(500).send({
1177
1244
  success: false,
1178
1245
  error: `Recover failed: ${error.message}`
@@ -7750,11 +7817,49 @@ function connectionRoutes(fastify2) {
7750
7817
  // src/controllers/mcp-configs.ts
7751
7818
  import {
7752
7819
  getStoreLattice as getStoreLattice9,
7820
+ getReplicaId as getReplicaId2,
7753
7821
  mcpManager,
7754
7822
  McpLatticeManager,
7755
7823
  convertToConnection
7756
7824
  } from "@axiom-lattice/core";
7757
7825
  import { randomUUID as randomUUID10 } from "crypto";
7826
+
7827
+ // src/services/mcpConfigSync.ts
7828
+ var MCP_CONFIG_CHANGED_TOPIC = "mcp-config-changed";
7829
+ async function publishMcpConfigChanged(outbox, owner, tenantId, serverKey) {
7830
+ if (!tenantId || !serverKey) return;
7831
+ try {
7832
+ await outbox.append({
7833
+ topic: MCP_CONFIG_CHANGED_TOPIC,
7834
+ payload: { tenantId, serverKey },
7835
+ originReplicaId: owner
7836
+ });
7837
+ } catch {
7838
+ }
7839
+ }
7840
+ async function reconcileMcpConfigChange(payload, deps) {
7841
+ const tenantId = payload?.tenantId;
7842
+ const serverKey = payload?.serverKey;
7843
+ if (typeof tenantId !== "string" || tenantId.length === 0) return;
7844
+ if (typeof serverKey !== "string" || serverKey.length === 0) return;
7845
+ const config = await deps.store.getConfigByKey(tenantId, serverKey);
7846
+ if (!config || config.status !== "connected") {
7847
+ if (deps.manager.hasServer(tenantId, serverKey)) {
7848
+ await deps.manager.removeServer(tenantId, serverKey);
7849
+ }
7850
+ return;
7851
+ }
7852
+ const connection = deps.convertToConnection(config.config);
7853
+ deps.manager.registerServer(tenantId, serverKey, connection, {
7854
+ timeoutMs: config.config.timeout,
7855
+ maxAttempts: config.config.retryAttempts,
7856
+ selectedTools: config.selectedTools
7857
+ });
7858
+ await deps.manager.ensureConnected(tenantId, serverKey);
7859
+ await deps.manager.registerToolsToToolLattice(tenantId, serverKey, config.selectedTools);
7860
+ }
7861
+
7862
+ // src/controllers/mcp-configs.ts
7758
7863
  function getTenantId10(request) {
7759
7864
  const userTenantId = request.user?.tenantId;
7760
7865
  if (userTenantId) {
@@ -7762,6 +7867,14 @@ function getTenantId10(request) {
7762
7867
  }
7763
7868
  return request.headers["x-tenant-id"] || "default";
7764
7869
  }
7870
+ function broadcastMcpConfigChanged(tenantId, serverKey) {
7871
+ if (!tenantId || !serverKey) return;
7872
+ try {
7873
+ const outbox = getStoreLattice9("default", "outbox").store;
7874
+ void publishMcpConfigChanged(outbox, getReplicaId2(), tenantId, serverKey);
7875
+ } catch {
7876
+ }
7877
+ }
7765
7878
  async function getMcpServerConfigList(request, reply) {
7766
7879
  const tenantId = getTenantId10(request);
7767
7880
  try {
@@ -7839,6 +7952,7 @@ async function createMcpServerConfig(request, reply) {
7839
7952
  await store.updateConfig(tenantId, id3, { status: "error" });
7840
7953
  config.status = "error";
7841
7954
  }
7955
+ broadcastMcpConfigChanged(tenantId, config.key);
7842
7956
  reply.code(201);
7843
7957
  return {
7844
7958
  success: true,
@@ -7868,7 +7982,9 @@ async function updateMcpServerConfig(request, reply) {
7868
7982
  message: "MCP server configuration not found"
7869
7983
  };
7870
7984
  }
7871
- const shouldReconnect = updates.config !== void 0;
7985
+ const configChanged = updates.config !== void 0;
7986
+ const toolsChanged = updates.selectedTools !== void 0;
7987
+ const statusChanged = updates.status !== void 0;
7872
7988
  const updated = await store.updateConfig(tenantId, existing.id, updates);
7873
7989
  if (!updated) {
7874
7990
  return {
@@ -7876,7 +7992,7 @@ async function updateMcpServerConfig(request, reply) {
7876
7992
  message: "Failed to update MCP server configuration"
7877
7993
  };
7878
7994
  }
7879
- if (shouldReconnect) {
7995
+ if (configChanged) {
7880
7996
  try {
7881
7997
  const connection = convertToConnection(updated.config);
7882
7998
  mcpManager.registerServer(tenantId, key, connection, {
@@ -7892,6 +8008,22 @@ async function updateMcpServerConfig(request, reply) {
7892
8008
  await store.updateConfig(tenantId, existing.id, { status: "error" });
7893
8009
  updated.status = "error";
7894
8010
  }
8011
+ } else if (toolsChanged && mcpManager.hasServer(tenantId, key)) {
8012
+ try {
8013
+ await mcpManager.registerToolsToToolLattice(tenantId, key, updated.selectedTools);
8014
+ } catch (error) {
8015
+ console.warn("Failed to re-register MCP tools:", error);
8016
+ }
8017
+ }
8018
+ if (statusChanged && updated.status !== "connected" && mcpManager.hasServer(tenantId, key)) {
8019
+ try {
8020
+ await mcpManager.removeServer(tenantId, key);
8021
+ } catch (error) {
8022
+ console.warn("Failed to remove MCP server:", error);
8023
+ }
8024
+ }
8025
+ if (configChanged || toolsChanged || statusChanged) {
8026
+ broadcastMcpConfigChanged(tenantId, key);
7895
8027
  }
7896
8028
  return {
7897
8029
  success: true,
@@ -7941,6 +8073,7 @@ async function deleteMcpServerConfig(request, reply) {
7941
8073
  message: "Failed to delete MCP server configuration"
7942
8074
  };
7943
8075
  }
8076
+ broadcastMcpConfigChanged(tenantId, configKey);
7944
8077
  return {
7945
8078
  success: true,
7946
8079
  message: "MCP server configuration deleted successfully"
@@ -8015,12 +8148,17 @@ async function listMcpServerTools(request, reply) {
8015
8148
  message: "MCP server configuration not found"
8016
8149
  };
8017
8150
  }
8018
- if (!mcpManager.isServerConnected(tenantId, key)) {
8151
+ const wasConnected = mcpManager.isServerConnected(tenantId, key);
8152
+ if (!wasConnected) {
8019
8153
  await connectAndRegisterTools(config);
8020
8154
  }
8021
- if (config.status !== "connected") {
8155
+ const statusStale = config.status !== "connected";
8156
+ if (statusStale) {
8022
8157
  await store.updateConfig(tenantId, config.id, { status: "connected" });
8023
8158
  }
8159
+ if (!wasConnected || statusStale) {
8160
+ broadcastMcpConfigChanged(tenantId, key);
8161
+ }
8024
8162
  const tools = await mcpManager.getServerTools(tenantId, key);
8025
8163
  return {
8026
8164
  success: true,
@@ -8055,6 +8193,7 @@ async function connectMcpServer(request, reply) {
8055
8193
  const updated = await store.updateConfig(tenantId, config.id, {
8056
8194
  status: "connected"
8057
8195
  });
8196
+ broadcastMcpConfigChanged(tenantId, key);
8058
8197
  return {
8059
8198
  success: true,
8060
8199
  message: "MCP server connected successfully",
@@ -8068,6 +8207,7 @@ async function connectMcpServer(request, reply) {
8068
8207
  if (config) {
8069
8208
  await store.updateConfig(tenantId, config.id, { status: "error" });
8070
8209
  }
8210
+ broadcastMcpConfigChanged(tenantId, key);
8071
8211
  return {
8072
8212
  success: false,
8073
8213
  message: `Failed to connect MCP server: ${error instanceof Error ? error.message : "Unknown error"}`
@@ -8094,6 +8234,7 @@ async function disconnectMcpServer(request, reply) {
8094
8234
  const updated = await store.updateConfig(tenantId, config.id, {
8095
8235
  status: "disconnected"
8096
8236
  });
8237
+ broadcastMcpConfigChanged(tenantId, key);
8097
8238
  return {
8098
8239
  success: true,
8099
8240
  message: "MCP server disconnected successfully",
@@ -10619,8 +10760,8 @@ function snapshotPlainObject(value) {
10619
10760
  }
10620
10761
  }
10621
10762
  async function getInstallationStore() {
10622
- const { getStoreLattice: getStoreLattice29 } = await import("@axiom-lattice/core");
10623
- const store = getStoreLattice29("default", "channelInstallation").store;
10763
+ const { getStoreLattice: getStoreLattice30 } = await import("@axiom-lattice/core");
10764
+ const store = getStoreLattice30("default", "channelInstallation").store;
10624
10765
  if (store) return store;
10625
10766
  const { PostgreSQLChannelInstallationStore } = await import("@axiom-lattice/pg-stores");
10626
10767
  const databaseUrl = process.env.DATABASE_URL;
@@ -12369,6 +12510,10 @@ var ProjectTaskExecutionService = class {
12369
12510
  input.inputMessageId,
12370
12511
  true
12371
12512
  );
12513
+ if (resumed.leaseContended) {
12514
+ settlement.cleanup();
12515
+ return { kind: "deferred", task: input.task, thread: input.thread, messageId: input.inputMessageId };
12516
+ }
12372
12517
  if (resumed.status === ThreadStatus3.INTERRUPTED && settlement.tryClaim()) {
12373
12518
  settlement.cleanup();
12374
12519
  const outcome = await this.settleInterruptedEvent(input.task, input.thread, input.inputMessageId);
@@ -14056,7 +14201,8 @@ import { z as z5, ZodError as ZodError2 } from "zod";
14056
14201
 
14057
14202
  // src/services/agent-web-app/AgentWebAppRuntimeService.ts
14058
14203
  import { createHash as createHash4, randomUUID as randomUUID14 } from "crypto";
14059
- import { MessageChunkTypes as MessageChunkTypes3, parseAgentWebAppGenUIBlock } from "@axiom-lattice/protocols";
14204
+ import { MessageChunkTypes as MessageChunkTypes3, parseAgentWebAppGenUIBlock, buildAffinityMissChunk as buildAffinityMissChunk2 } from "@axiom-lattice/protocols";
14205
+ import { getReplicaId as getReplicaId3, getThreadRunOwner as getThreadRunOwner2 } from "@axiom-lattice/core";
14060
14206
  function isRecord2(value) {
14061
14207
  return typeof value === "object" && value !== null && !Array.isArray(value);
14062
14208
  }
@@ -14218,6 +14364,20 @@ var AgentWebAppRuntimeService = class {
14218
14364
  async resumeStream(webAppId, userId, threadId, messageId, selection) {
14219
14365
  const runtime = await this.resolveRuntime(webAppId, selection);
14220
14366
  await this.requireOwnedThread(runtime, userId, threadId);
14367
+ if (process.env.AXIOM_REPLICA_ID) {
14368
+ const owner = await getThreadRunOwner2({
14369
+ tenantId: runtime.webApp.tenantId,
14370
+ assistantId: runtime.webApp.assistantId,
14371
+ threadId
14372
+ });
14373
+ if (owner && owner !== getReplicaId3()) {
14374
+ return {
14375
+ stream: (async function* () {
14376
+ yield buildAffinityMissChunk2(owner, threadId);
14377
+ })()
14378
+ };
14379
+ }
14380
+ }
14221
14381
  const agent = this.resolveAgent(runtime, threadId);
14222
14382
  return { stream: agent.chunkStream(messageId, []) };
14223
14383
  }
@@ -14562,7 +14722,8 @@ async function verifyWebAppToken(token, issuer, options = {}) {
14562
14722
  }
14563
14723
 
14564
14724
  // src/routes/agent-web-app-runtime.ts
14565
- import { createAgentWebAppStreamProjectionContext, projectAgentWebAppChunk } from "@axiom-lattice/protocols";
14725
+ import { createAgentWebAppStreamProjectionContext, projectAgentWebAppChunk, AGENT_AFFINITY_HEADER as AGENT_AFFINITY_HEADER2, AFFINITY_ECHO_HEADER as AFFINITY_ECHO_HEADER2, affinityEchoHeaders as affinityEchoHeaders2, REPLICA_HEADER as REPLICA_HEADER2 } from "@axiom-lattice/protocols";
14726
+ import { getReplicaId as getReplicaId4 } from "@axiom-lattice/core";
14566
14727
  var idSchema = z5.string().min(1).max(128);
14567
14728
  var paramsSchema = z5.object({ webAppId: idSchema }).strict();
14568
14729
  var threadParamsSchema = z5.object({ webAppId: idSchema, threadId: idSchema }).strict();
@@ -14711,7 +14872,7 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
14711
14872
  const identity = await resolveRuntimeIdentity(request, webAppId, identities);
14712
14873
  const input = streamBodySchema.parse(request.body);
14713
14874
  const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
14714
- beginSse(reply);
14875
+ beginSse(reply, request);
14715
14876
  await pipeNativeWebAppStream(reply.raw, result.stream);
14716
14877
  } catch (error) {
14717
14878
  return normalizeError2(reply, error);
@@ -14723,7 +14884,7 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
14723
14884
  const identity = await resolveRuntimeIdentity(request, webAppId, identities);
14724
14885
  const input = streamBodySchema.parse(request.body);
14725
14886
  const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
14726
- beginSse(reply);
14887
+ beginSse(reply, request);
14727
14888
  await pipeAgentWebAppStream(reply.raw, result.stream, result);
14728
14889
  } catch (error) {
14729
14890
  return normalizeError2(reply, error);
@@ -14735,7 +14896,7 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
14735
14896
  const identity = await resolveRuntimeIdentity(request, webAppId, identities);
14736
14897
  const { messageId, projectId, modelKey: modelKey2 } = resumeSchema.parse(request.body);
14737
14898
  const result = await deps.service.resumeStream(webAppId, identity.userId, threadId, messageId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
14738
- beginSse(reply);
14899
+ beginSse(reply, request);
14739
14900
  await pipeNativeWebAppStream(reply.raw, result.stream);
14740
14901
  } catch (error) {
14741
14902
  return normalizeError2(reply, error);
@@ -14768,7 +14929,7 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
14768
14929
  const identity = await resolveRuntimeIdentity(request, webAppId, identities);
14769
14930
  const { projectId, modelKey: modelKey2, message, response } = resumeBodySchema.parse(request.body);
14770
14931
  const result = await deps.service.resumeInterrupt(webAppId, identity.userId, threadId, interruptId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview), { message, response });
14771
- beginSse(reply);
14932
+ beginSse(reply, request);
14772
14933
  await pipeAgentWebAppStream(reply.raw, result.stream, result);
14773
14934
  } catch (error) {
14774
14935
  return normalizeError2(reply, error);
@@ -14996,6 +15157,10 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
14996
15157
  if (!threadId) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "thread_id is required");
14997
15158
  const content = body.messages.map((message) => message.content).join("\n");
14998
15159
  const { messageId } = await deps.service.enqueueMessage(webAppId, identity.userId, threadId, content, { projectId: body.projectId, modelKey: body.modelKey });
15160
+ reply.header(REPLICA_HEADER2, getReplicaId4());
15161
+ for (const [name, value] of Object.entries(affinityEchoHeaders2(request.headers[AGENT_AFFINITY_HEADER2]))) {
15162
+ reply.header(name, value);
15163
+ }
14999
15164
  return reply.code(202).send({ success: true, messageId, queued: true });
15000
15165
  } catch (error) {
15001
15166
  return normalizeError2(reply, error);
@@ -15016,7 +15181,7 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
15016
15181
  const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15017
15182
  const body = resumeStreamSchema2.parse(request.body ?? {});
15018
15183
  const result = await deps.service.resumeStream(webAppId, identity.userId, body.thread_id, body.message_id, { projectId: body.projectId, modelKey: body.modelKey });
15019
- beginSse(reply);
15184
+ beginSse(reply, request);
15020
15185
  await pipeNativeWebAppStream(reply.raw, result.stream);
15021
15186
  } catch (error) {
15022
15187
  return normalizeError2(reply, error);
@@ -15069,14 +15234,17 @@ async function pipeNativeWebAppStream(raw, stream) {
15069
15234
  }]
15070
15235
  });
15071
15236
  }
15072
- function beginSse(reply) {
15237
+ function beginSse(reply, request) {
15073
15238
  reply.hijack();
15074
15239
  reply.raw.writeHead(200, {
15075
15240
  "Content-Type": "text/event-stream; charset=utf-8",
15076
15241
  "Cache-Control": "no-cache, no-transform",
15077
15242
  Connection: "keep-alive",
15078
15243
  "X-Accel-Buffering": "no",
15079
- "Access-Control-Allow-Origin": "*"
15244
+ "Access-Control-Allow-Origin": "*",
15245
+ "Access-Control-Expose-Headers": `${REPLICA_HEADER2}, ${AFFINITY_ECHO_HEADER2}`,
15246
+ [REPLICA_HEADER2]: getReplicaId4(),
15247
+ ...affinityEchoHeaders2(request.headers[AGENT_AFFINITY_HEADER2])
15080
15248
  });
15081
15249
  }
15082
15250
  async function handle2(reply, operation) {
@@ -15676,6 +15844,17 @@ var ModelProviderServiceError = class extends Error {
15676
15844
  this.name = "ModelProviderServiceError";
15677
15845
  }
15678
15846
  };
15847
+ var UPSTREAM_ERROR_CODES = {
15848
+ UPSTREAM_UNREACHABLE: true,
15849
+ UPSTREAM_AUTH_FAILED: true,
15850
+ UPSTREAM_REDIRECT_BLOCKED: true,
15851
+ UPSTREAM_INVALID_RESPONSE: true,
15852
+ UPSTREAM_RATE_LIMITED: true,
15853
+ UPSTREAM_TIMEOUT: true
15854
+ };
15855
+ function isUpstreamErrorCode(code) {
15856
+ return Object.prototype.hasOwnProperty.call(UPSTREAM_ERROR_CODES, code);
15857
+ }
15679
15858
 
15680
15859
  // src/services/model-provider/discovery.ts
15681
15860
  var MAX_DISCOVERED_MODELS = 1e3;
@@ -15951,6 +16130,33 @@ var ModelProviderService = class {
15951
16130
  models
15952
16131
  };
15953
16132
  }
16133
+ /**
16134
+ * Hydrates the runtime model registry from the persisted provider catalog.
16135
+ *
16136
+ * @returns The model keys registered in this process.
16137
+ * @remarks Registers the Phase-1 (default tenant) enabled provider models so a
16138
+ * replica that did not perform discovery — or that restarted — can execute
16139
+ * them. Best-effort; the caller logs failures.
16140
+ */
16141
+ async hydrateRegistry() {
16142
+ const tenantId = PHASE1_REGISTRY_TENANT;
16143
+ if (!this.shouldRegister(tenantId)) {
16144
+ return [];
16145
+ }
16146
+ const providers = await this.deps.providerStore.listProviders(tenantId);
16147
+ const registered = [];
16148
+ for (const provider of providers) {
16149
+ if (!provider.enabled) {
16150
+ continue;
16151
+ }
16152
+ const models = await this.deps.providerStore.listModels(
16153
+ tenantId,
16154
+ provider.id
16155
+ );
16156
+ registered.push(...this.syncRegistry(tenantId, provider, models));
16157
+ }
16158
+ return registered;
16159
+ }
15954
16160
  /**
15955
16161
  * Create a provider configuration. Does not run discovery.
15956
16162
  */
@@ -16190,7 +16396,7 @@ var ModelProviderService = class {
16190
16396
  models
16191
16397
  };
16192
16398
  } catch (error) {
16193
- if (error instanceof ModelProviderServiceError) {
16399
+ if (error instanceof ModelProviderServiceError && isUpstreamErrorCode(error.code)) {
16194
16400
  return {
16195
16401
  reachable: false,
16196
16402
  latencyMs: Date.now() - startedAt,
@@ -16606,6 +16812,10 @@ var ModelProviderController = class {
16606
16812
  return sendError2(reply, error);
16607
16813
  }
16608
16814
  }
16815
+ /** Hydrates the runtime model registry from persisted providers (startup). */
16816
+ async hydrateRegistry() {
16817
+ return this.service.hydrateRegistry();
16818
+ }
16609
16819
  /** GET /api/model-providers */
16610
16820
  async listProviders(request, reply) {
16611
16821
  try {
@@ -19174,6 +19384,8 @@ function createRoomMessageRetentionJob(input) {
19174
19384
 
19175
19385
  // src/services/collaboration/ProjectTaskExecutionMutex.ts
19176
19386
  import { snapshotExactRecord as snapshotExactRecord8 } from "@axiom-lattice/protocols";
19387
+ import { getCoordinationLeaseStore, getReplicaId as getReplicaId5, withLease } from "@axiom-lattice/core";
19388
+ var MACHINE_LEASE_OPTIONS = { ttlMs: 6e4, acquireTimeoutMs: 9e4, retryDelayMs: 100 };
19177
19389
  function machineKey(scope) {
19178
19390
  const snapshot = snapshotExactRecord8(scope, ["tenantId", "assistantId", "projectId"]);
19179
19391
  if (!snapshot) throw new Error("Invalid Project task machine scope");
@@ -19197,9 +19409,12 @@ var ProjectTaskExecutionMutex = class {
19197
19409
  */
19198
19410
  async runExclusive(scope, operation, signal) {
19199
19411
  const key = machineKey(scope);
19412
+ const leaseKey = `project-task-machine:${encodeURIComponent(scope.tenantId)}:${encodeURIComponent(scope.assistantId)}:${encodeURIComponent(scope.projectId)}`;
19200
19413
  await this.acquire(key, signal);
19201
19414
  try {
19202
- return await operation();
19415
+ const store = getCoordinationLeaseStore();
19416
+ if (!store) return await operation();
19417
+ return await withLease(store, leaseKey, getReplicaId5(), operation, MACHINE_LEASE_OPTIONS);
19203
19418
  } finally {
19204
19419
  this.release(key);
19205
19420
  }
@@ -19258,6 +19473,20 @@ import {
19258
19473
  import {
19259
19474
  snapshotExactRecord as snapshotExactRecord9
19260
19475
  } from "@axiom-lattice/protocols";
19476
+
19477
+ // src/services/collaboration/projectTaskDispatchMarker.ts
19478
+ var EXECUTION_REQUESTED_AT_KEY = "execution_requested_at";
19479
+ function hasExecutionRequest(task) {
19480
+ const value = task.context?.[EXECUTION_REQUESTED_AT_KEY];
19481
+ if (typeof value !== "string" || value.length === 0) return false;
19482
+ const parsed = new Date(value);
19483
+ return Number.isFinite(parsed.getTime()) && parsed.toISOString() === value;
19484
+ }
19485
+ function withExecutionRequest(context, nowIso) {
19486
+ return { ...context ?? {}, [EXECUTION_REQUESTED_AT_KEY]: nowIso };
19487
+ }
19488
+
19489
+ // src/services/collaboration/ProjectTaskObserver.ts
19261
19490
  var SYSTEM_ACTOR2 = "system";
19262
19491
  var TERMINAL_ACTIONS = ["completed", "failed", "interrupted", "cancelled"];
19263
19492
  var ProjectTaskObserver = class {
@@ -19461,7 +19690,7 @@ var ProjectTaskObserver = class {
19461
19690
  return;
19462
19691
  }
19463
19692
  }
19464
- if (intent === "dispatch" && await this.isReady(task) && !signal?.aborted && !this.disposed) {
19693
+ if ((intent === "dispatch" || hasExecutionRequest(task)) && await this.isReady(task) && !signal?.aborted && !this.disposed) {
19465
19694
  await this.runStart(task, signal);
19466
19695
  }
19467
19696
  return;
@@ -19578,7 +19807,7 @@ var ProjectTaskObserver = class {
19578
19807
  }, signal);
19579
19808
  }
19580
19809
  async mapOutcome(outcome) {
19581
- if (outcome.kind === "aborted") return;
19810
+ if (outcome.kind === "aborted" || outcome.kind === "deferred") return;
19582
19811
  const current = snapshotTaskItem(await this.deps.tasks.getById(outcome.task.tenantId, outcome.task.id));
19583
19812
  if (!current || current.ownerId !== outcome.thread.assistantId || current.context?.thread_id !== outcome.thread.id) return;
19584
19813
  if (isTerminal(current.status) || current.status === "review") return;
@@ -20510,6 +20739,7 @@ var DEFAULT_MAX_EVENT_AGE_MS = 10 * 60 * 1e3;
20510
20739
  var DEFAULT_MAX_ROOMS = 1e3;
20511
20740
  var DEFAULT_MAX_GLOBAL_EVENTS = 1e5;
20512
20741
  var DEFAULT_MAX_GLOBAL_BYTES = 64 * 1024 * 1024;
20742
+ var DEFAULT_SOURCE_KEY_LIMIT = 2e3;
20513
20743
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
20514
20744
  var ProjectRoomBrokerResourceCapacityError = class extends Error {
20515
20745
  constructor(resource) {
@@ -20523,6 +20753,7 @@ var ProjectRoomEventBroker = class {
20523
20753
  constructor(options = {}) {
20524
20754
  this.rooms = /* @__PURE__ */ new Map();
20525
20755
  this.sequenceScopes = /* @__PURE__ */ new Map();
20756
+ this.seenSourceKeys = /* @__PURE__ */ new Map();
20526
20757
  this.eventCount = 0;
20527
20758
  this.byteCount = 0;
20528
20759
  this.subscriberCount = 0;
@@ -20550,6 +20781,10 @@ var ProjectRoomEventBroker = class {
20550
20781
  options.maxGlobalBytes ?? DEFAULT_MAX_GLOBAL_BYTES,
20551
20782
  "maxGlobalBytes"
20552
20783
  );
20784
+ this.sourceKeyLimit = positiveSafeInteger(
20785
+ options.sourceKeyLimit ?? DEFAULT_SOURCE_KEY_LIMIT,
20786
+ "sourceKeyLimit"
20787
+ );
20553
20788
  this.sequence = nonnegativeSafeInteger(options.initialSequence ?? 0, "initialSequence");
20554
20789
  this.log = options.log ?? { warn: () => void 0 };
20555
20790
  }
@@ -20561,8 +20796,22 @@ var ProjectRoomEventBroker = class {
20561
20796
  get retainedBytes() {
20562
20797
  return this.byteCount;
20563
20798
  }
20564
- /** Atomically stores an immutable event before queueing detached listener callbacks. */
20565
- publish(scope, draft) {
20799
+ /**
20800
+ * Installs (or clears) a hook invoked after a locally published event is stored
20801
+ * and queued to local subscribers. Used to mirror events to other replicas via
20802
+ * a shared transport. Best-effort: a throwing hook never affects `publish`.
20803
+ */
20804
+ setBroadcastHook(hook) {
20805
+ this.broadcast = hook;
20806
+ }
20807
+ /**
20808
+ * Atomically stores an immutable event before queueing detached listener callbacks.
20809
+ *
20810
+ * @param sourceKey Optional caller-owned fact key; when present it is retained
20811
+ * for bounded cross-replica duplicate suppression so a later foreign copy of the
20812
+ * same fact (or a local publish) is dropped by {@link ingestForeignEvent}.
20813
+ */
20814
+ publish(scope, draft, sourceKey) {
20566
20815
  this.assertOpen();
20567
20816
  const canonicalScope = snapshotScope(scope);
20568
20817
  const key = scopeKey2(canonicalScope);
@@ -20597,9 +20846,52 @@ var ProjectRoomEventBroker = class {
20597
20846
  this.sequence = nextSequence;
20598
20847
  this.eventCount += 1;
20599
20848
  this.byteCount += bytes;
20849
+ if (sourceKey !== void 0) this.rememberSourceKey(sourceKey);
20600
20850
  for (const subscription of [...room.subscriptions]) this.queueCallback(subscription, serialized);
20851
+ if (this.broadcast) {
20852
+ try {
20853
+ this.broadcast(canonicalScope, serialized, sourceKey);
20854
+ } catch {
20855
+ }
20856
+ }
20601
20857
  return parseScopedEvent(serialized);
20602
20858
  }
20859
+ /**
20860
+ * Delivers one event published by ANOTHER replica to this process's local
20861
+ * subscribers for its room, without adding it to the local replay ring or
20862
+ * consuming a local sequence (its id carries the origin replica's epoch).
20863
+ *
20864
+ * @param serializedEvent A serialized scoped business event produced by a peer.
20865
+ * @param sourceKey Optional caller-owned fact key shared by every replica's copy
20866
+ * of the same committed fact. When it was already seen (locally published or
20867
+ * ingested from a peer) the event is dropped to avoid N× duplicate delivery.
20868
+ * @returns True when it was queued to at least one local subscription.
20869
+ * @remarks Source-key deduplication is process-local and bounded, so it caps
20870
+ * duplicate fan-out within the retention window without replacing REST, which
20871
+ * remains the authority. A malformed payload or a room with no local
20872
+ * subscribers is ignored.
20873
+ */
20874
+ ingestForeignEvent(serializedEvent, sourceKey) {
20875
+ this.assertOpen();
20876
+ if (sourceKey !== void 0 && this.seenSourceKeys.has(sourceKey)) return false;
20877
+ let key;
20878
+ try {
20879
+ const event = parseScopedEvent(serializedEvent);
20880
+ key = scopeKey2(snapshotScope(event.scope));
20881
+ } catch {
20882
+ return false;
20883
+ }
20884
+ if (sourceKey !== void 0) this.rememberSourceKey(sourceKey);
20885
+ const room = this.rooms.get(key);
20886
+ if (!room || room.subscriptions.size === 0) return false;
20887
+ this.touch(room);
20888
+ let queued = false;
20889
+ for (const subscription of [...room.subscriptions]) {
20890
+ this.queueCallback(subscription, serializedEvent);
20891
+ queued = true;
20892
+ }
20893
+ return queued;
20894
+ }
20603
20895
  /** Atomically resolves replay and registers a distinct subscription for live events. */
20604
20896
  subscribe(scope, afterEventId, listener) {
20605
20897
  this.assertOpen();
@@ -20639,6 +20931,7 @@ var ProjectRoomEventBroker = class {
20639
20931
  }
20640
20932
  this.rooms.clear();
20641
20933
  this.sequenceScopes.clear();
20934
+ this.seenSourceKeys.clear();
20642
20935
  this.eventCount = 0;
20643
20936
  this.byteCount = 0;
20644
20937
  this.subscriberCount = 0;
@@ -20773,6 +21066,14 @@ var ProjectRoomEventBroker = class {
20773
21066
  this.usageClock += 1;
20774
21067
  room.lastUsed = this.usageClock;
20775
21068
  }
21069
+ rememberSourceKey(key) {
21070
+ this.seenSourceKeys.set(key, true);
21071
+ while (this.seenSourceKeys.size > this.sourceKeyLimit) {
21072
+ const oldest = this.seenSourceKeys.keys().next().value;
21073
+ if (oldest === void 0) break;
21074
+ this.seenSourceKeys.delete(oldest);
21075
+ }
21076
+ }
20776
21077
  };
20777
21078
  function positiveSafeInteger(value, name) {
20778
21079
  if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError(`${name} must be a positive safe integer`);
@@ -20970,7 +21271,7 @@ var BrokerProjectRoomRealtimePublisher = class {
20970
21271
  if (!event) throw new Error("Invalid Project Room realtime projection");
20971
21272
  const dedupeKey = createHash9("sha256").update(JSON.stringify([type, event.scope.tenantId, event.scope.projectId, event.scope.roomId, event.sourceKey])).digest("hex");
20972
21273
  if (this.sourceKeys.has(dedupeKey)) return;
20973
- this.deps.broker.publish(event.scope, event.draft);
21274
+ this.deps.broker.publish(event.scope, event.draft, event.sourceKey);
20974
21275
  this.sourceKeys.set(dedupeKey, true);
20975
21276
  while (this.sourceKeys.size > this.dedupeLimit) {
20976
21277
  const oldest = this.sourceKeys.keys().next().value;
@@ -22374,7 +22675,7 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
22374
22675
  bundleStore: getStoreLattice17("default", "capabilityBundle").store,
22375
22676
  projectStore
22376
22677
  }, { authRequired: process.env.AUTH_REQUIRED === "true" }));
22377
- registerModelProviderRoutes(app2, new ModelProviderController({
22678
+ const modelProviderController = new ModelProviderController({
22378
22679
  providerStore: getStoreLattice17("default", "modelProvider").store,
22379
22680
  userTenantLinkStore,
22380
22681
  registry: {
@@ -22383,7 +22684,9 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
22383
22684
  remove: (tenantId, modelKey2) => modelLatticeManager3.removeLatticeWithTenant(tenantId, modelKey2),
22384
22685
  listRegistered: (tenantId) => modelLatticeManager3.getLatticeKeysByTenant(tenantId)
22385
22686
  }
22386
- }, { authRequired: process.env.AUTH_REQUIRED === "true" }));
22687
+ }, { authRequired: process.env.AUTH_REQUIRED === "true" });
22688
+ registerModelProviderRoutes(app2, modelProviderController);
22689
+ void modelProviderController.hydrateRegistry().catch(() => void 0);
22387
22690
  registerAgentWebAppRoutes(app2, {
22388
22691
  service: new AgentWebAppService({
22389
22692
  webAppStore,
@@ -22559,13 +22862,17 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
22559
22862
  }) : void 0;
22560
22863
  const admitted = transferred ?? task;
22561
22864
  if (!admitted.workspaceId || !admitted.projectId) return;
22865
+ const markerUpdated = await taskStore.update(admitted.tenantId, admitted.id, {
22866
+ context: withExecutionRequest(admitted.context, (/* @__PURE__ */ new Date()).toISOString())
22867
+ }).catch(() => null);
22868
+ const eventTask = markerUpdated ?? admitted;
22562
22869
  void projectTaskAdmissionNotifier.publish({
22563
- tenantId: admitted.tenantId,
22564
- workspaceId: admitted.workspaceId,
22565
- projectId: admitted.projectId,
22566
- taskId: admitted.id,
22567
- ownerId: admitted.ownerId,
22568
- updatedAt: admitted.updatedAt.toISOString()
22870
+ tenantId: eventTask.tenantId,
22871
+ workspaceId: eventTask.workspaceId,
22872
+ projectId: eventTask.projectId,
22873
+ taskId: eventTask.id,
22874
+ ownerId: eventTask.ownerId,
22875
+ updatedAt: eventTask.updatedAt.toISOString()
22569
22876
  });
22570
22877
  }
22571
22878
  });
@@ -24800,13 +25107,25 @@ import {
24800
25107
  getLoggerLattice,
24801
25108
  loggerLatticeManager,
24802
25109
  sandboxLatticeManager as sandboxLatticeManager2,
24803
- getStoreLattice as getStoreLattice28,
25110
+ getStoreLattice as getStoreLattice29,
24804
25111
  agentInstanceManager as agentInstanceManager9,
24805
25112
  createSandboxProvider,
24806
25113
  TokenCache,
24807
25114
  mcpManager as mcpManager3,
24808
25115
  convertToConnection as convertToConnection2,
24809
- createTrustedAgentMessageDispatcher as createTrustedAgentMessageDispatcher2
25116
+ createTrustedAgentMessageDispatcher as createTrustedAgentMessageDispatcher2,
25117
+ runAsLeader,
25118
+ configureCoordinationLeaseStore,
25119
+ getReplicaId as getReplicaId6,
25120
+ taskLifecycleNotifier as taskLifecycleNotifier3,
25121
+ projectTaskAdmissionNotifier as projectTaskAdmissionNotifier2,
25122
+ scheduleLatticeManager as scheduleLatticeManager2,
25123
+ OutboxPoller,
25124
+ eventBus as eventBus4,
25125
+ agentLatticeManager as agentLatticeManager5,
25126
+ configurePluginCatalogSync,
25127
+ PLUGIN_CATALOG_CHANGED_TOPIC,
25128
+ getSandboxPluginCatalog as getSandboxPluginCatalog4
24810
25129
  } from "@axiom-lattice/core";
24811
25130
 
24812
25131
  // src/channels/ChannelInfrastructure.ts
@@ -25475,9 +25794,167 @@ async function prepareProjectTaskRuntime(runtime, log2) {
25475
25794
  }
25476
25795
  }
25477
25796
 
25797
+ // src/services/multiReplicaReadiness.ts
25798
+ import { getStoreLattice as getStoreLattice28 } from "@axiom-lattice/core";
25799
+ var SHARED_STORE_KINDS = ["lease", "outbox", "threadMessageQueue", "task", "taskWorkItem"];
25800
+ function storeClassName(kind) {
25801
+ try {
25802
+ return getStoreLattice28("default", kind).store.constructor?.name;
25803
+ } catch {
25804
+ return void 0;
25805
+ }
25806
+ }
25807
+ function warnMultiReplicaReadiness(logger7) {
25808
+ if (typeof process.env.AXIOM_REPLICA_ID !== "string" || process.env.AXIOM_REPLICA_ID.trim() === "") return;
25809
+ for (const kind of SHARED_STORE_KINDS) {
25810
+ const name = storeClassName(kind);
25811
+ if (name === void 0 || !name.startsWith("InMemory")) continue;
25812
+ logger7.warn(`Multi-replica: store '${kind}' is ${name} (not shared); configure a shared store`, {
25813
+ phase: "multi_replica_readiness",
25814
+ store: kind
25815
+ });
25816
+ }
25817
+ if (!process.env.AUTH_TOKEN_SECRET?.trim() && !process.env.JWT_SECRET?.trim()) {
25818
+ logger7.warn(
25819
+ "Multi-replica: AUTH_TOKEN_SECRET/JWT_SECRET unset; console sessions are signed per-process and will not verify across replicas",
25820
+ { phase: "multi_replica_readiness", env: "AUTH_TOKEN_SECRET" }
25821
+ );
25822
+ }
25823
+ if (!process.env.LATTICE_ENCRYPTION_KEY?.trim()) {
25824
+ logger7.warn(
25825
+ "Multi-replica: LATTICE_ENCRYPTION_KEY unset; stored credentials use the shared dev default and may not decrypt across differing keys",
25826
+ { phase: "multi_replica_readiness", env: "LATTICE_ENCRYPTION_KEY" }
25827
+ );
25828
+ }
25829
+ logger7.warn(
25830
+ "Multi-replica: agent run SSE and resume_stream are process-local; route a thread's follow-up runs to the replica streaming it (sticky routing) or share the stream/queue state",
25831
+ { phase: "multi_replica_readiness", feature: "agent-run-sse" }
25832
+ );
25833
+ }
25834
+
25835
+ // src/services/OutboxRetentionJob.ts
25836
+ var HOUR_MS = 60 * 60 * 1e3;
25837
+ var DEFAULT_INTERVAL_MS2 = 6 * HOUR_MS;
25838
+ var DEFAULT_INITIAL_DELAY_MS2 = 6e4;
25839
+ var AXIOM_OUTBOX_RETENTION_HOURS_ENV = "AXIOM_OUTBOX_RETENTION_HOURS";
25840
+ var DEFAULT_RETENTION_HOURS = 24;
25841
+ function parseRetentionHours(value) {
25842
+ if (value === void 0 || value === null) return DEFAULT_RETENTION_HOURS;
25843
+ if (typeof value !== "string") return void 0;
25844
+ const trimmed = value.trim();
25845
+ if (trimmed.length === 0) return DEFAULT_RETENTION_HOURS;
25846
+ const parsed = Number(trimmed);
25847
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
25848
+ }
25849
+ var OutboxRetentionJob = class {
25850
+ /** @param deps Outbox slice, logger, and optional retention/interval bounds. */
25851
+ constructor(deps) {
25852
+ this.deps = deps;
25853
+ this.running = false;
25854
+ this.stopped = false;
25855
+ }
25856
+ /** Schedules one delayed sweep plus the recurring interval (both unref'd). */
25857
+ start() {
25858
+ if (this.stopped || this.deps.retentionMs === void 0) return;
25859
+ if (this.initial !== void 0 || this.interval !== void 0) return;
25860
+ this.initial = setTimeout(() => {
25861
+ this.initial = void 0;
25862
+ void this.runOnce();
25863
+ }, this.deps.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS2);
25864
+ this.initial.unref?.();
25865
+ this.interval = setInterval(() => void this.runOnce(), this.deps.intervalMs ?? DEFAULT_INTERVAL_MS2);
25866
+ this.interval.unref?.();
25867
+ }
25868
+ /**
25869
+ * Runs one age-based prune.
25870
+ *
25871
+ * @returns The number of rows removed (0 when disabled, overlapping, or on failure).
25872
+ */
25873
+ async runOnce() {
25874
+ const retentionMs = this.deps.retentionMs;
25875
+ if (this.stopped || retentionMs === void 0 || this.running) return 0;
25876
+ this.running = true;
25877
+ try {
25878
+ const removed = await this.deps.outbox.pruneOlderThan(retentionMs);
25879
+ if (removed > 0) this.deps.log.info("outbox pruned", { removed });
25880
+ return removed;
25881
+ } catch (error) {
25882
+ this.deps.log.warn("outbox prune failed", {
25883
+ error: error instanceof Error ? error.message : String(error)
25884
+ });
25885
+ return 0;
25886
+ } finally {
25887
+ this.running = false;
25888
+ }
25889
+ }
25890
+ /** Stops the timers; an in-flight sweep finishes on its own. */
25891
+ stop() {
25892
+ this.stopped = true;
25893
+ if (this.initial !== void 0) {
25894
+ clearTimeout(this.initial);
25895
+ this.initial = void 0;
25896
+ }
25897
+ if (this.interval !== void 0) {
25898
+ clearInterval(this.interval);
25899
+ this.interval = void 0;
25900
+ }
25901
+ }
25902
+ };
25903
+ function createOutboxRetentionJob(deps) {
25904
+ const retentionMs = deps.retentionMs ?? resolveRetentionMs(process.env[AXIOM_OUTBOX_RETENTION_HOURS_ENV]);
25905
+ return new OutboxRetentionJob({ ...deps, retentionMs });
25906
+ }
25907
+ function resolveRetentionMs(value) {
25908
+ const hours = parseRetentionHours(value);
25909
+ return hours === void 0 ? void 0 : hours * HOUR_MS;
25910
+ }
25911
+
25912
+ // src/services/collaboration/roomEventSync.ts
25913
+ var ROOM_EVENT_TOPIC = "room-event";
25914
+ async function publishRoomEvent(outbox, owner, scope, serialized, sourceKey) {
25915
+ try {
25916
+ await outbox.append({
25917
+ topic: ROOM_EVENT_TOPIC,
25918
+ payload: { scope, serialized, sourceKey },
25919
+ originReplicaId: owner
25920
+ });
25921
+ } catch {
25922
+ }
25923
+ }
25924
+ function ingestRoomEvent(payload, broker) {
25925
+ const record = payload;
25926
+ const serialized = record?.serialized;
25927
+ if (typeof serialized !== "string" || serialized.length === 0) return false;
25928
+ const sourceKey = record?.sourceKey;
25929
+ if (typeof sourceKey === "string" && sourceKey.length > 0) {
25930
+ return broker.ingestForeignEvent(serialized, sourceKey);
25931
+ }
25932
+ return broker.ingestForeignEvent(serialized);
25933
+ }
25934
+
25935
+ // src/services/assistantRegistrySync.ts
25936
+ var ASSISTANT_CHANGE_EVENTS = ["assistant:created", "assistant:updated", "assistant:deleted"];
25937
+ var ASSISTANT_CHANGED_TOPIC = "assistant-changed";
25938
+ function bridgeAssistantEvents(subscribe, outbox, owner) {
25939
+ for (const event of ASSISTANT_CHANGE_EVENTS) {
25940
+ subscribe(event, (data) => {
25941
+ const tenantId = data?.tenantId;
25942
+ if (typeof tenantId !== "string" || tenantId.length === 0) return;
25943
+ void outbox.append({ topic: ASSISTANT_CHANGED_TOPIC, payload: { tenantId }, originReplicaId: owner }).catch(() => void 0);
25944
+ });
25945
+ }
25946
+ }
25947
+ async function handleAssistantChanged(payload, reload) {
25948
+ const tenantId = payload?.tenantId;
25949
+ if (typeof tenantId !== "string" || tenantId.length === 0) return;
25950
+ await reload(tenantId);
25951
+ }
25952
+
25478
25953
  // src/index.ts
25479
25954
  import {
25480
- LoggerType
25955
+ LoggerType,
25956
+ REPLICA_HEADER as REPLICA_HEADER3,
25957
+ AFFINITY_ECHO_HEADER as AFFINITY_ECHO_HEADER3
25481
25958
  } from "@axiom-lattice/protocols";
25482
25959
  process.on("unhandledRejection", (reason, promise) => {
25483
25960
  console.error("\u672A\u5904\u7406\u7684Promise\u62D2\u7EDD:", reason);
@@ -25557,7 +26034,7 @@ app.register(cors, {
25557
26034
  origin: true,
25558
26035
  methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
25559
26036
  allowedHeaders: "*",
25560
- exposedHeaders: ["Content-Type", "Content-Disposition"],
26037
+ exposedHeaders: ["Content-Type", "Content-Disposition", REPLICA_HEADER3, AFFINITY_ECHO_HEADER3],
25561
26038
  credentials: true
25562
26039
  });
25563
26040
  app.register(sensible);
@@ -25609,7 +26086,7 @@ function getConfiguredSandboxProvider() {
25609
26086
  }
25610
26087
  async function restoreMcpConnections() {
25611
26088
  try {
25612
- const storeLattice = getStoreLattice28("default", "mcp");
26089
+ const storeLattice = getStoreLattice29("default", "mcp");
25613
26090
  const store = storeLattice.store;
25614
26091
  if (!store) {
25615
26092
  logger6.info("MCP store not configured, skipping connection restoration");
@@ -25620,25 +26097,16 @@ async function restoreMcpConnections() {
25620
26097
  );
25621
26098
  if (configs.length === 0) return;
25622
26099
  logger6.info(`Restoring ${configs.length} MCP server connections...`);
25623
- for (const config of configs) {
25624
- try {
25625
- const connection = convertToConnection2(config.config);
25626
- mcpManager3.registerServer(config.tenantId, config.key, connection, {
25627
- timeoutMs: config.config.timeout,
25628
- maxAttempts: config.config.retryAttempts,
25629
- selectedTools: config.selectedTools
25630
- });
25631
- } catch (err) {
25632
- logger6.warn(`Failed to prepare MCP server "${config.tenantId}/${config.key}"`, { error: err });
25633
- }
25634
- }
25635
- await mcpManager3.connectAll();
26100
+ const deps = {
26101
+ store,
26102
+ manager: mcpManager3,
26103
+ convertToConnection: convertToConnection2
26104
+ };
25636
26105
  const results = await Promise.allSettled(
25637
26106
  configs.map(
25638
- (config) => mcpManager3.registerToolsToToolLattice(
25639
- config.tenantId,
25640
- config.key,
25641
- config.selectedTools
26107
+ (config) => reconcileMcpConfigChange(
26108
+ { tenantId: config.tenantId, serverKey: config.key },
26109
+ deps
25642
26110
  )
25643
26111
  )
25644
26112
  );
@@ -25646,7 +26114,7 @@ async function restoreMcpConnections() {
25646
26114
  if (result.status === "rejected") {
25647
26115
  const config = configs[index];
25648
26116
  logger6.warn(
25649
- `Failed to register tools for MCP server "${config.tenantId}/${config.key}"`,
26117
+ `Failed to restore MCP server "${config.tenantId}/${config.key}"`,
25650
26118
  { error: result.reason }
25651
26119
  );
25652
26120
  }
@@ -25673,9 +26141,9 @@ var start = async (config) => {
25673
26141
  let projectRoomRealtimeRuntime;
25674
26142
  if (projectRoomRealtimeMode === "single-gateway") {
25675
26143
  projectRoomRealtimeRuntime = createProjectRoomRealtimeRuntime({
25676
- tasks: getStoreLattice28("default", "task").store,
25677
- rooms: getStoreLattice28("default", "projectRoom").store,
25678
- memberships: getStoreLattice28("default", "projectBotMembership").store,
26144
+ tasks: getStoreLattice29("default", "task").store,
26145
+ rooms: getStoreLattice29("default", "projectRoom").store,
26146
+ memberships: getStoreLattice29("default", "projectBotMembership").store,
25679
26147
  log: { warn: (message, context) => logger6.warn(message, context) }
25680
26148
  });
25681
26149
  }
@@ -25718,7 +26186,7 @@ var start = async (config) => {
25718
26186
  }
25719
26187
  setEvalRunService(evalRunner);
25720
26188
  try {
25721
- const menuStore = getStoreLattice28("default", "menu").store;
26189
+ const menuStore = getStoreLattice29("default", "menu").store;
25722
26190
  setMenuRegistry(menuStore);
25723
26191
  logger6.info("Menu registry initialized");
25724
26192
  } catch {
@@ -25736,10 +26204,10 @@ var start = async (config) => {
25736
26204
  const { A2AAuthService, parseEnvKeys } = await import("./A2AAuthService-7UGQ7GMW.mjs");
25737
26205
  const { registerA2AStandardRoutes } = await import("./a2a-standard-PBYPNHOA.mjs");
25738
26206
  const a2a = await import("./a2a-FGYX2HH3.mjs");
25739
- const a2aKeyStore = getStoreLattice28("default", "a2aApiKey").store;
25740
- const assistantStore = getStoreLattice28("default", "assistant").store;
25741
- const projectStore = getStoreLattice28("default", "project").store;
25742
- const taskStore = getStoreLattice28("default", "task").store;
26207
+ const a2aKeyStore = getStoreLattice29("default", "a2aApiKey").store;
26208
+ const assistantStore = getStoreLattice29("default", "assistant").store;
26209
+ const projectStore = getStoreLattice29("default", "project").store;
26210
+ const taskStore = getStoreLattice29("default", "task").store;
25743
26211
  const authService = new A2AAuthService({
25744
26212
  keyStore: a2aKeyStore,
25745
26213
  assistantStore,
@@ -25764,22 +26232,22 @@ var start = async (config) => {
25764
26232
  });
25765
26233
  }
25766
26234
  try {
25767
- const { registerOpenRoutes } = await import("./open-STFMNRO5.mjs");
25768
- const { registerOpenSessionRoutes } = await import("./open-session-IHDGAXL3.mjs");
26235
+ const { registerOpenRoutes } = await import("./open-UVKMONVE.mjs");
26236
+ const { registerOpenSessionRoutes } = await import("./open-session-2Y7XJJW2.mjs");
25769
26237
  const { registerOpenCatalogRoutes } = await import("./open-catalog-SZQ435AY.mjs");
25770
- const { registerOpenFilesRoutes } = await import("./open-files-NPNQQ6TY.mjs");
26238
+ const { registerOpenFilesRoutes } = await import("./open-files-X3HXVEEM.mjs");
25771
26239
  const { registerOpenAuditRoutes } = await import("./open-audit-UJ3NXCGW.mjs");
25772
26240
  const { registerOpenExposureRoutes } = await import("./open-exposure-VU227IO3.mjs");
25773
26241
  const { openLattice } = await import("./openLattice-Q6M5S6CY.mjs");
25774
26242
  const { createKbProvider } = await import("./kbProvider-2D6DZ34N.mjs");
25775
- const { createOpenSandboxProvider } = await import("./sandboxProvider-I3P66VXT.mjs");
26243
+ const { createOpenSandboxProvider } = await import("./sandboxProvider-DUYWHYJN.mjs");
25776
26244
  const { createAgentProvider } = await import("./agentProvider-X4FVQPKW.mjs");
25777
26245
  const { registerExposedPluginTools } = await import("./registerExposedPlugins-6GCOE64C.mjs");
25778
26246
  const { CollectionService: CollectionService2 } = await import("./collection_service-RTRJEQFL.mjs");
25779
26247
  openLattice.register(createKbProvider(new CollectionService2()));
25780
26248
  openLattice.register(createOpenSandboxProvider());
25781
- const threadStore = getStoreLattice28("default", "thread").store;
25782
- const assistantStore = getStoreLattice28("default", "assistant").store;
26249
+ const threadStore = getStoreLattice29("default", "thread").store;
26250
+ const assistantStore = getStoreLattice29("default", "assistant").store;
25783
26251
  openLattice.register(
25784
26252
  createAgentProvider({
25785
26253
  threadStore,
@@ -25802,13 +26270,24 @@ var start = async (config) => {
25802
26270
  error: err instanceof Error ? err.message : String(err)
25803
26271
  });
25804
26272
  }
26273
+ try {
26274
+ const { registerGatewaySandboxOpenCredentialProvider } = await import("./sandboxOpenCredentialProvider-USIU7Z7H.mjs");
26275
+ registerGatewaySandboxOpenCredentialProvider((error) => {
26276
+ logger6.warn("sandbox open-credential provider failed", { error });
26277
+ });
26278
+ logger6.info("Sandbox open-credential provider registered");
26279
+ } catch (err) {
26280
+ logger6.warn("Sandbox open-credential provider unavailable", {
26281
+ error: err instanceof Error ? err.message : String(err)
26282
+ });
26283
+ }
25805
26284
  if (!sandboxLatticeManager2.hasLattice("default")) {
25806
26285
  sandboxLatticeManager2.registerLattice("default", getConfiguredSandboxProvider());
25807
26286
  logger6.info("Registered sandbox manager from env configuration");
25808
26287
  }
25809
26288
  try {
25810
- const { ResourceController } = await import("./resources-DXPXAXEZ.mjs");
25811
- const sharedResourceStore = getStoreLattice28("default", "sharedResource").store;
26289
+ const { ResourceController } = await import("./resources-2L3KSUUU.mjs");
26290
+ const sharedResourceStore = getStoreLattice29("default", "sharedResource").store;
25812
26291
  const cache = new TokenCache();
25813
26292
  const resourceController = new ResourceController({
25814
26293
  store: sharedResourceStore,
@@ -25837,30 +26316,98 @@ var start = async (config) => {
25837
26316
  }
25838
26317
  }
25839
26318
  const target_port = config?.port || Number(process.env.PORT) || 4001;
25840
- if (routes.projectTaskRuntime) {
25841
- await prepareProjectTaskRuntime(routes.projectTaskRuntime, logger6);
26319
+ configureCoordinationLeaseStore(getStoreLattice29("default", "lease").store);
26320
+ const outboxStore = getStoreLattice29("default", "outbox").store;
26321
+ taskLifecycleNotifier3.setOutbox(outboxStore);
26322
+ projectTaskAdmissionNotifier2.setOutbox(outboxStore);
26323
+ configurePluginCatalogSync({ outbox: outboxStore, owner: getReplicaId6() });
26324
+ bridgeAssistantEvents(
26325
+ (event, cb) => eventBus4.subscribe(event, cb),
26326
+ outboxStore,
26327
+ getReplicaId6()
26328
+ );
26329
+ if (projectRoomRealtimeRuntime) {
26330
+ projectRoomRealtimeRuntime.broker.setBroadcastHook((scope, serialized, sourceKey) => {
26331
+ void publishRoomEvent(outboxStore, getReplicaId6(), scope, serialized, sourceKey);
26332
+ });
25842
26333
  }
25843
- if (process.env.AXIOM_TASK_ORPHAN_RECOVERY === "true") {
25844
- try {
25845
- const { createOrphanRecoveryRuntime: createOrphanRecoveryRuntime2 } = await import("./OrphanRecoveryRuntime-MQDEJX3M.mjs");
25846
- const { createRecoveryCallerDeps: createRecoveryCallerDeps2 } = await import("./RecoveryCallerDeps-6SJUYJ5S.mjs");
25847
- const taskStore = getStoreLattice28("default", "task").store;
25848
- const workItemStore = getStoreLattice28("default", "taskWorkItem").store;
25849
- const tenantStore = getStoreLattice28("default", "tenant").store;
25850
- const runtime = createOrphanRecoveryRuntime2({
25851
- taskStore,
25852
- workItemStore,
25853
- tenantStore,
25854
- ...createRecoveryCallerDeps2(),
25855
- log: logger6
25856
- });
25857
- await runtime.recoverAll();
25858
- } catch (err) {
25859
- logger6.warn("Task orphan recovery failed", { error: err instanceof Error ? err.message : String(err) });
26334
+ const outboxPoller = new OutboxPoller({
26335
+ store: outboxStore,
26336
+ owner: getReplicaId6(),
26337
+ handlers: {
26338
+ "task-lifecycle": (payload) => taskLifecycleNotifier3.deliverFromOutbox(payload),
26339
+ "project-task-admission": (payload) => projectTaskAdmissionNotifier2.deliverFromOutbox(payload),
26340
+ [ROOM_EVENT_TOPIC]: async (payload) => {
26341
+ if (projectRoomRealtimeRuntime) ingestRoomEvent(payload, projectRoomRealtimeRuntime.broker);
26342
+ },
26343
+ "assistant-changed": (payload) => handleAssistantChanged(payload, (tenantId) => agentLatticeManager5.initializeStoredAssistantsForTenant(tenantId)),
26344
+ [MCP_CONFIG_CHANGED_TOPIC]: (payload) => reconcileMcpConfigChange(payload, {
26345
+ store: getStoreLattice29("default", "mcp").store,
26346
+ manager: mcpManager3,
26347
+ convertToConnection: convertToConnection2
26348
+ }),
26349
+ [PLUGIN_CATALOG_CHANGED_TOPIC]: async (payload) => {
26350
+ const tenantId = payload?.tenantId;
26351
+ if (typeof tenantId === "string" && tenantId.length > 0) getSandboxPluginCatalog4().invalidate(tenantId);
26352
+ }
26353
+ },
26354
+ log: logger6
26355
+ });
26356
+ try {
26357
+ await outboxPoller.start();
26358
+ } catch (err) {
26359
+ logger6.warn("Outbox poller failed to start", { error: err instanceof Error ? err.message : String(err) });
26360
+ }
26361
+ const leaseStore = getStoreLattice29("default", "lease").store;
26362
+ if (leaseStore.constructor?.name === "InMemoryLeaseStore") {
26363
+ logger6.warn(
26364
+ "Startup recovery leader gate uses a non-distributed lease store (per-process only); configure a shared 'lease' store for multi-replica deployments",
26365
+ { phase: "startup_recovery" }
26366
+ );
26367
+ }
26368
+ warnMultiReplicaReadiness(logger6);
26369
+ const startup = await runAsLeader(leaseStore, "startup-recovery", async () => {
26370
+ if (routes.projectTaskRuntime) {
26371
+ await prepareProjectTaskRuntime(routes.projectTaskRuntime, logger6);
25860
26372
  }
26373
+ if (process.env.AXIOM_TASK_ORPHAN_RECOVERY === "true") {
26374
+ try {
26375
+ const { createOrphanRecoveryRuntime: createOrphanRecoveryRuntime2 } = await import("./OrphanRecoveryRuntime-BEFILJMU.mjs");
26376
+ const { createRecoveryCallerDeps: createRecoveryCallerDeps2 } = await import("./RecoveryCallerDeps-6SJUYJ5S.mjs");
26377
+ const runtime = createOrphanRecoveryRuntime2({
26378
+ taskStore: getStoreLattice29("default", "task").store,
26379
+ workItemStore: getStoreLattice29("default", "taskWorkItem").store,
26380
+ tenantStore: getStoreLattice29("default", "tenant").store,
26381
+ ...createRecoveryCallerDeps2(),
26382
+ log: logger6
26383
+ });
26384
+ await runtime.recoverAll();
26385
+ } catch (err) {
26386
+ logger6.warn("Task orphan recovery failed", { error: err instanceof Error ? err.message : String(err) });
26387
+ }
26388
+ }
26389
+ if (scheduleLatticeManager2.hasLattice("default")) {
26390
+ try {
26391
+ const restored = await scheduleLatticeManager2.getScheduleLattice("default").restore();
26392
+ logger6.info("Scheduled tasks restored", { phase: "startup_recovery", restored });
26393
+ } catch (err) {
26394
+ logger6.warn("Scheduled task restore failed", {
26395
+ phase: "startup_recovery",
26396
+ error: err instanceof Error ? err.message : String(err)
26397
+ });
26398
+ }
26399
+ }
26400
+ }, { hold: true, ttlMs: 5 * 6e4 });
26401
+ if (!startup.ran) {
26402
+ logger6.info("Startup recovery skipped (another replica leads)", { phase: "startup_recovery" });
25861
26403
  }
25862
26404
  await app.listen({ port: target_port, host: "0.0.0.0" });
25863
26405
  logger6.info(`Lattice Gateway is running on port: ${target_port}`);
26406
+ const outboxRetention = createOutboxRetentionJob({
26407
+ outbox: outboxStore,
26408
+ log: logger6
26409
+ });
26410
+ outboxRetention.start();
25864
26411
  const queueServiceConfig = config?.queueServiceConfig;
25865
26412
  if (queueServiceConfig) {
25866
26413
  setQueueServiceType(queueServiceConfig.type);
@@ -25886,8 +26433,13 @@ var LatticeGateway = {
25886
26433
  };
25887
26434
  export {
25888
26435
  LatticeGateway,
26436
+ ProjectRoomEventBroker,
26437
+ ROOM_EVENT_TOPIC,
25889
26438
  createOrphanRecoveryRuntime,
25890
26439
  createRecoveryCallerDeps,
26440
+ createTaskRunLiveness,
26441
+ ingestRoomEvent,
26442
+ publishRoomEvent,
25891
26443
  recoverTask,
25892
26444
  recoverTopCaller,
25893
26445
  resolveTopCaller,