@axiom-lattice/gateway 4.0.0 → 4.0.2

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
@@ -10,11 +10,12 @@ import {
10
10
  PathOutsideRootError,
11
11
  ProjectFileValidationError,
12
12
  filesystemRootDirectory,
13
+ formatFileRefsSection,
13
14
  isBinaryContentType,
14
15
  isPathWithinRoot,
15
16
  resolvePathWithinRoot,
16
17
  saveProjectFile
17
- } from "./chunk-IDOEIBCF.mjs";
18
+ } from "./chunk-J4BK77PA.mjs";
18
19
  import {
19
20
  getContentTypeFromFilename,
20
21
  getFilenameFromPath
@@ -827,6 +828,90 @@ import {
827
828
  agentInstanceManager as agentInstanceManager2
828
829
  } from "@axiom-lattice/core";
829
830
  import { MessageChunkTypes } from "@axiom-lattice/protocols";
831
+
832
+ // src/services/agents/ScopedAgentExecution.ts
833
+ async function executeScopedAgentMessage(agent, request, stopTypes, options) {
834
+ const { messageId } = options === void 0 ? await agent.addMessage(request) : await agent.addMessage(request, options.queueMode);
835
+ return { messageId, stream: agent.chunkStream(messageId, stopTypes) };
836
+ }
837
+
838
+ // src/services/streaming/pipeAsyncIterableToSse.ts
839
+ async function pipeAsyncIterableToSse(raw, stream, options = {}) {
840
+ const iterator = stream[Symbol.asyncIterator]();
841
+ const project = options.project ?? ((value) => [value]);
842
+ const serialize = options.serialize ?? ((event) => `data: ${JSON.stringify(event)}
843
+
844
+ `);
845
+ const closeSources = options.closeSource && options.closeSource !== raw ? [raw, options.closeSource] : [raw];
846
+ let closed = raw.destroyed || raw.writableEnded;
847
+ let returned = false;
848
+ let resolveClosed;
849
+ const closedPromise = new Promise((resolve2) => {
850
+ resolveClosed = resolve2;
851
+ });
852
+ const returnConsumer = () => {
853
+ if (returned) return;
854
+ returned = true;
855
+ void Promise.resolve(iterator.return?.()).catch(() => void 0);
856
+ };
857
+ const onClose = () => {
858
+ closed = true;
859
+ returnConsumer();
860
+ resolveClosed?.();
861
+ };
862
+ for (const source of closeSources) {
863
+ source.on("close", onClose);
864
+ source.on("error", onClose);
865
+ }
866
+ try {
867
+ while (!closed) {
868
+ const next = await Promise.race([
869
+ iterator.next().then((result) => ({ kind: "next", result })),
870
+ closedPromise.then(() => ({ kind: "closed" }))
871
+ ]);
872
+ if (next.kind === "closed" || next.result.done) break;
873
+ for (const event of project(next.result.value)) {
874
+ if (!await writeSse(raw, serialize(event), closedPromise)) {
875
+ closed = true;
876
+ returnConsumer();
877
+ break;
878
+ }
879
+ }
880
+ }
881
+ } catch (error) {
882
+ if (!options.onError) throw error;
883
+ if (!closed && !raw.destroyed && !raw.writableEnded) {
884
+ for (const event of options.onError(error)) {
885
+ if (!await writeSse(raw, serialize(event), closedPromise)) break;
886
+ }
887
+ }
888
+ returnConsumer();
889
+ } finally {
890
+ for (const source of closeSources) {
891
+ source.removeListener("close", onClose);
892
+ source.removeListener("error", onClose);
893
+ }
894
+ if (!closed && !raw.destroyed && !raw.writableEnded) raw.end();
895
+ }
896
+ }
897
+ async function writeSse(raw, value, closed) {
898
+ if (raw.destroyed || raw.writableEnded) return false;
899
+ if (raw.write(value)) return true;
900
+ return new Promise((resolve2) => {
901
+ let settled = false;
902
+ const onDrain = () => finish(!raw.destroyed && !raw.writableEnded);
903
+ const finish = (writable) => {
904
+ if (settled) return;
905
+ settled = true;
906
+ raw.removeListener("drain", onDrain);
907
+ resolve2(writable);
908
+ };
909
+ raw.once("drain", onDrain);
910
+ void closed.then(() => finish(false));
911
+ });
912
+ }
913
+
914
+ // src/controllers/run.ts
830
915
  function getUserId2(request) {
831
916
  const authUser = request.user;
832
917
  if (authUser?.id) return authUser.id;
@@ -893,20 +978,22 @@ var createRun = async (request, reply) => {
893
978
  });
894
979
  try {
895
980
  const messageInput = message_id ? { ...input, id: message_id } : input;
896
- const result = await agent.addMessage({
981
+ const result = await executeScopedAgentMessage(agent, {
897
982
  input: messageInput,
898
983
  command,
899
984
  custom_run_config: mergedConfig
900
- }, mode);
901
- const stream = agent.chunkStream(result.messageId, [MessageChunkTypes.MESSAGE_COMPLETED, MessageChunkTypes.INTERRUPT, MessageChunkTypes.MESSAGE_FAILED]);
902
- for await (const chunk of stream) {
903
- const success = reply.raw.write(`data: ${JSON.stringify(chunk)}
904
-
905
- `);
906
- if (!success) {
907
- await new Promise((resolve2) => reply.raw.once("drain", resolve2));
908
- }
909
- }
985
+ }, [MessageChunkTypes.MESSAGE_COMPLETED, MessageChunkTypes.INTERRUPT, MessageChunkTypes.MESSAGE_FAILED], {
986
+ queueMode: mode
987
+ });
988
+ await pipeAsyncIterableToSse(reply.raw, result.stream, {
989
+ onError: (error) => [{
990
+ type: "error",
991
+ data: {
992
+ id: v4(),
993
+ content: error instanceof Error ? error.message : "Stream processing error"
994
+ }
995
+ }]
996
+ });
910
997
  } catch (error) {
911
998
  const errorEvent = {
912
999
  type: "error",
@@ -919,7 +1006,7 @@ var createRun = async (request, reply) => {
919
1006
 
920
1007
  `);
921
1008
  } finally {
922
- reply.raw.end();
1009
+ if (!reply.raw.writableEnded && !reply.raw.destroyed) reply.raw.end();
923
1010
  }
924
1011
  } else {
925
1012
  const { message: msg, ...restInputNonStream } = input;
@@ -968,17 +1055,16 @@ var resumeStream = async (request, reply) => {
968
1055
  workspace_id,
969
1056
  project_id
970
1057
  });
971
- const stream = agent.chunkStream(message_id, []);
972
- let closed = false;
973
- request.raw.on("close", () => {
974
- closed = true;
1058
+ await pipeAsyncIterableToSse(reply.raw, agent.chunkStream(message_id, []), {
1059
+ closeSource: request.raw,
1060
+ onError: (error) => [{
1061
+ type: "error",
1062
+ data: {
1063
+ id: v4(),
1064
+ content: error instanceof Error ? error.message : "Resume stream processing error"
1065
+ }
1066
+ }]
975
1067
  });
976
- for await (const chunk of stream) {
977
- if (closed || reply.raw.destroyed) break;
978
- reply.raw.write(`data: ${JSON.stringify(chunk)}
979
-
980
- `);
981
- }
982
1068
  } catch (error) {
983
1069
  const errorEvent = {
984
1070
  type: "error",
@@ -991,7 +1077,7 @@ var resumeStream = async (request, reply) => {
991
1077
 
992
1078
  `);
993
1079
  } finally {
994
- reply.raw.end();
1080
+ if (!reply.raw.writableEnded && !reply.raw.destroyed) reply.raw.end();
995
1081
  }
996
1082
  } catch (error) {
997
1083
  reply.status(500).send({
@@ -3358,7 +3444,7 @@ async function abortWorkflowRun(request, reply) {
3358
3444
  }
3359
3445
 
3360
3446
  // src/controllers/personal-assistant.ts
3361
- import { getStoreLattice as getStoreLattice5, PersonalAssistantConfig } from "@axiom-lattice/core";
3447
+ import { eventBus as eventBus2, getStoreLattice as getStoreLattice5, PersonalAssistantConfig } from "@axiom-lattice/core";
3362
3448
  import { randomUUID as randomUUID3 } from "crypto";
3363
3449
  function getWorkspaceId2(request) {
3364
3450
  return request.headers["x-workspace-id"] || "default";
@@ -3455,6 +3541,7 @@ async function deletePersonalAssistant(request, reply) {
3455
3541
  if (!deleted) {
3456
3542
  return reply.status(500).send({ success: false, message: "Failed to delete personal assistant" });
3457
3543
  }
3544
+ eventBus2.publish("assistant:deleted", { id: assistant.id, tenantId });
3458
3545
  const threadStore = getStoreLattice5("default", "thread").store;
3459
3546
  try {
3460
3547
  const threads = await threadStore.getThreadsByAssistantId(tenantId, assistant.id);
@@ -6908,11 +6995,11 @@ async function createProject(request, reply) {
6908
6995
  }
6909
6996
  const serverCfg = data.targetServerConfig || {};
6910
6997
  const judgeCfg = data.judgeModelConfig || {};
6911
- const modelKey = judgeCfg.modelKey || "";
6912
- if (modelKey) {
6913
- const { modelLatticeManager: modelLatticeManager3 } = await import("@axiom-lattice/core");
6914
- if (!modelLatticeManager3.hasLattice(modelKey)) {
6915
- return reply.status(400).send({ success: false, message: `Judge model "${modelKey}" is not registered` });
6998
+ const modelKey2 = judgeCfg.modelKey || "";
6999
+ if (modelKey2) {
7000
+ const { modelLatticeManager: modelLatticeManager4 } = await import("@axiom-lattice/core");
7001
+ if (!modelLatticeManager4.hasLattice(modelKey2)) {
7002
+ return reply.status(400).send({ success: false, message: `Judge model "${modelKey2}" is not registered` });
6916
7003
  }
6917
7004
  }
6918
7005
  const project = await store.createProject(tenantId, id, {
@@ -6920,7 +7007,7 @@ async function createProject(request, reply) {
6920
7007
  description: data.description,
6921
7008
  version: data.version,
6922
7009
  judgeModelConfig: {
6923
- modelKey,
7010
+ modelKey: modelKey2,
6924
7011
  displayName: judgeCfg.displayName || ""
6925
7012
  },
6926
7013
  targetServerConfig: {
@@ -6957,8 +7044,8 @@ async function listProjects(request, reply) {
6957
7044
  let projects = await store.getProjectsByTenant(tenantId);
6958
7045
  if (projects.length === 0) {
6959
7046
  try {
6960
- const { modelLatticeManager: modelLatticeManager3 } = await import("@axiom-lattice/core");
6961
- const models = modelLatticeManager3.getAllLattices();
7047
+ const { modelLatticeManager: modelLatticeManager4 } = await import("@axiom-lattice/core");
7048
+ const models = modelLatticeManager4.getAllLattices();
6962
7049
  const first = models[0];
6963
7050
  const judgeModel = first ? { modelKey: first.key } : {};
6964
7051
  const host = request.hostname || "localhost";
@@ -7024,15 +7111,15 @@ async function updateProject(request, reply) {
7024
7111
  }
7025
7112
  if (body.judgeModelConfig !== void 0) {
7026
7113
  const judgeCfg = body.judgeModelConfig || {};
7027
- const modelKey = judgeCfg.modelKey || "";
7028
- if (modelKey) {
7029
- const { modelLatticeManager: modelLatticeManager3 } = await import("@axiom-lattice/core");
7030
- if (!modelLatticeManager3.hasLattice(modelKey)) {
7031
- return reply.status(400).send({ success: false, message: `Judge model "${modelKey}" is not registered` });
7114
+ const modelKey2 = judgeCfg.modelKey || "";
7115
+ if (modelKey2) {
7116
+ const { modelLatticeManager: modelLatticeManager4 } = await import("@axiom-lattice/core");
7117
+ if (!modelLatticeManager4.hasLattice(modelKey2)) {
7118
+ return reply.status(400).send({ success: false, message: `Judge model "${modelKey2}" is not registered` });
7032
7119
  }
7033
7120
  }
7034
7121
  updateData.judgeModelConfig = {
7035
- modelKey,
7122
+ modelKey: modelKey2,
7036
7123
  displayName: judgeCfg.displayName || ""
7037
7124
  };
7038
7125
  }
@@ -7895,6 +7982,8 @@ function extractUserFromAuthHeader(authHeader, secret) {
7895
7982
  var PUBLIC_ROUTES = ["/api/auth/login", "/api/auth/register", "/health", "/api/stt/models"];
7896
7983
  function bypassesConsoleAuth(request) {
7897
7984
  const path4 = request.url.split("?")[0];
7985
+ const method = request.method;
7986
+ if (method === "GET" && /^\/api\/web-apps\/[^/]+\/runtime\/bootstrap$/.test(path4) || (method === "GET" || method === "POST") && /^\/api\/web-apps\/[^/]+\/runtime\/threads$/.test(path4) || (method === "PATCH" || method === "DELETE") && /^\/api\/web-apps\/[^/]+\/runtime\/threads\/[^/]+$/.test(path4) || method === "GET" && /^\/api\/web-apps\/[^/]+\/runtime\/threads\/[^/]+\/messages$/.test(path4) || method === "POST" && /^\/api\/web-apps\/[^/]+\/runtime\/threads\/[^/]+\/(messages\/stream|abort|attachments)$/.test(path4) || method === "POST" && /^\/api\/web-apps\/[^/]+\/runtime\/threads\/[^/]+\/interrupts\/[^/]+\/resume$/.test(path4)) return true;
7898
7987
  if (request.method === "GET" && /^\/api\/a2a\/agents\/[^/]+\/\.well-known\/agent-card\.json$/.test(path4)) {
7899
7988
  return true;
7900
7989
  }
@@ -7902,13 +7991,13 @@ function bypassesConsoleAuth(request) {
7902
7991
  }
7903
7992
  function registerConsoleAuthHook(app2, options) {
7904
7993
  app2.addHook("preHandler", async (request, reply) => {
7905
- if (bypassesConsoleAuth(request)) return;
7906
7994
  const secret = options.tokenSecret ?? resolveConsoleTokenSecret(options.authRequired);
7907
7995
  const user = extractUserFromAuthHeader(request.headers.authorization, secret);
7908
7996
  if (user) {
7909
7997
  request.user = user;
7910
- return;
7911
7998
  }
7999
+ if (bypassesConsoleAuth(request)) return;
8000
+ if (user) return;
7912
8001
  if (!options.authRequired || request.method === "OPTIONS") return;
7913
8002
  if (PUBLIC_ROUTES.some((route) => request.url === route)) return;
7914
8003
  if (request.url.startsWith("/s/")) return;
@@ -8305,8 +8394,8 @@ function snapshotPlainObject(value) {
8305
8394
  }
8306
8395
  }
8307
8396
  async function getInstallationStore() {
8308
- const { getStoreLattice: getStoreLattice27 } = await import("@axiom-lattice/core");
8309
- const store = getStoreLattice27("default", "channelInstallation").store;
8397
+ const { getStoreLattice: getStoreLattice28 } = await import("@axiom-lattice/core");
8398
+ const store = getStoreLattice28("default", "channelInstallation").store;
8310
8399
  if (store) return store;
8311
8400
  const { PostgreSQLChannelInstallationStore } = await import("@axiom-lattice/pg-stores");
8312
8401
  const databaseUrl = process.env.DATABASE_URL;
@@ -9002,7 +9091,7 @@ var registerSTTRoutes = (app2) => {
9002
9091
  }
9003
9092
  },
9004
9093
  async (request, reply) => {
9005
- const modelKey = request.query.model || "default";
9094
+ const modelKey2 = request.query.model || "default";
9006
9095
  const data = await request.file({
9007
9096
  limits: { fileSize: MAX_AUDIO_FILE_SIZE }
9008
9097
  });
@@ -9020,9 +9109,9 @@ var registerSTTRoutes = (app2) => {
9020
9109
  try {
9021
9110
  let client;
9022
9111
  try {
9023
- client = getSTTClient(modelKey);
9112
+ client = getSTTClient(modelKey2);
9024
9113
  } catch {
9025
- return reply.status(404).send({ error: `STT model "${modelKey}" not found` });
9114
+ return reply.status(404).send({ error: `STT model "${modelKey2}" not found` });
9026
9115
  }
9027
9116
  const result = await client.transcribe(buffer, format);
9028
9117
  return reply.send({
@@ -9300,6 +9389,8 @@ function registerA2ATestConnectionRoute(app2) {
9300
9389
 
9301
9390
  // src/controllers/tasks.ts
9302
9391
  import {
9392
+ agentLatticeManager as agentLatticeManager4,
9393
+ ensureBuiltinAgentsForTenant,
9303
9394
  getStoreLattice as getStoreLattice15
9304
9395
  } from "@axiom-lattice/core";
9305
9396
  var AGENT_CREATE_STATUSES = /* @__PURE__ */ new Set(["pending"]);
@@ -9437,8 +9528,10 @@ async function createTask(request, reply) {
9437
9528
  const ownerType = body.ownerType || "user";
9438
9529
  const ownerId = body.ownerId || userId;
9439
9530
  if (ownerType === "agent") {
9531
+ ensureBuiltinAgentsForTenant(tenantId);
9440
9532
  const assistant = await getAssistantStore2().getAssistantById(tenantId, ownerId);
9441
- if (!assistant || assistant.tenantId !== tenantId) {
9533
+ const configuredAgent = agentLatticeManager4.getAgentConfigWithTenant(tenantId, ownerId);
9534
+ if ((!assistant || assistant.tenantId !== tenantId) && !configuredAgent) {
9442
9535
  return reply.status(404).send({
9443
9536
  success: false,
9444
9537
  code: "AGENT_OWNER_NOT_FOUND",
@@ -9586,8 +9679,1034 @@ function registerTaskRoutes(app2) {
9586
9679
  app2.get("/api/tasks/:id/work-items", listTaskWorkItems);
9587
9680
  }
9588
9681
 
9682
+ // src/routes/index.ts
9683
+ import { agentInstanceManager as agentInstanceManager6, agentLatticeManager as agentLatticeManager5, getStoreLattice as getStoreLattice16, modelLatticeManager as modelLatticeManager3 } from "@axiom-lattice/core";
9684
+
9685
+ // src/services/agent-web-app/AgentWebAppService.ts
9686
+ function createConfiguredAgentResolver(registry) {
9687
+ return {
9688
+ async exists(tenantId, assistantId) {
9689
+ await registry.hydrate(tenantId);
9690
+ return registry.has(tenantId, assistantId);
9691
+ }
9692
+ };
9693
+ }
9694
+ var AgentWebAppError = class extends Error {
9695
+ constructor(code, statusCode, message) {
9696
+ super(message);
9697
+ this.code = code;
9698
+ this.statusCode = statusCode;
9699
+ this.name = "AgentWebAppError";
9700
+ }
9701
+ };
9702
+ var AgentWebAppService = class {
9703
+ constructor(deps) {
9704
+ this.deps = deps;
9705
+ }
9706
+ async create(context, input) {
9707
+ await this.validateAssistant(context, input.assistantId);
9708
+ const normalized = await this.validateConfig(context.tenantId, input);
9709
+ return this.deps.webAppStore.create(context.tenantId, normalized);
9710
+ }
9711
+ async list(context, assistantId) {
9712
+ const records = await this.deps.webAppStore.list(context.tenantId, assistantId);
9713
+ const visible = [];
9714
+ for (const record of records) {
9715
+ if (await this.canAccessAssistant(context, record.assistantId)) visible.push(record);
9716
+ }
9717
+ return visible;
9718
+ }
9719
+ async get(context, webAppId) {
9720
+ return this.requireAuthorizedWebApp(context, webAppId);
9721
+ }
9722
+ async update(context, webAppId, patch) {
9723
+ const current = await this.requireAuthorizedWebApp(context, webAppId);
9724
+ const effective = {
9725
+ assistantId: current.assistantId,
9726
+ name: patch.name ?? current.name,
9727
+ description: Object.prototype.hasOwnProperty.call(patch, "description") ? patch.description : current.description,
9728
+ integration: current.integration,
9729
+ scope: { ...current.scope, ...patch.scope },
9730
+ features: { ...current.features, ...patch.features },
9731
+ appearance: { ...current.appearance, ...patch.appearance }
9732
+ };
9733
+ await this.validateAssistant(context, current.assistantId);
9734
+ const validated = await this.validateConfig(context.tenantId, effective);
9735
+ const update = {
9736
+ ...patch.name !== void 0 ? { name: validated.name } : {},
9737
+ ...Object.prototype.hasOwnProperty.call(patch, "description") ? { description: validated.description } : {},
9738
+ ...patch.scope ? { scope: validated.scope } : {},
9739
+ ...patch.features ? { features: validated.features } : {},
9740
+ ...patch.appearance ? { appearance: validated.appearance } : {}
9741
+ };
9742
+ return this.writeSnapshot(context, current, update);
9743
+ }
9744
+ async enable(context, webAppId) {
9745
+ const current = await this.requireAuthorizedWebApp(context, webAppId);
9746
+ await this.validateConfig(context.tenantId, current);
9747
+ return this.writeSnapshot(context, current, { status: "active" });
9748
+ }
9749
+ async disable(context, webAppId) {
9750
+ const current = await this.requireAuthorizedWebApp(context, webAppId);
9751
+ return this.writeSnapshot(context, current, { status: "disabled" });
9752
+ }
9753
+ async delete(context, webAppId) {
9754
+ await this.requireAuthorizedWebApp(context, webAppId);
9755
+ if (!await this.deps.webAppStore.delete(context.tenantId, webAppId)) this.notFound();
9756
+ }
9757
+ async validateAssistant(context, assistantId) {
9758
+ const assistant = await this.deps.assistantStore.getAssistantById(context.tenantId, assistantId);
9759
+ if (assistant) {
9760
+ if (assistant.ownerUserId && assistant.ownerUserId !== context.userId) {
9761
+ throw new AgentWebAppError("ASSISTANT_FORBIDDEN", 403, "Assistant is not available to this user");
9762
+ }
9763
+ return;
9764
+ }
9765
+ if (await this.deps.configuredAgentResolver.exists(context.tenantId, assistantId)) return;
9766
+ throw new AgentWebAppError("ASSISTANT_NOT_FOUND", 404, "Assistant not found");
9767
+ }
9768
+ async validateConfig(tenantId, input) {
9769
+ const allowedProjectIds = [...new Set(input.scope.allowedProjectIds)];
9770
+ if (!allowedProjectIds.length || !allowedProjectIds.includes(input.scope.defaultProjectId)) {
9771
+ throw new AgentWebAppError("PROJECT_SCOPE_INVALID", 400, "Default project must be in the project allowlist");
9772
+ }
9773
+ if (!input.features.projectSelector && allowedProjectIds.length !== 1) {
9774
+ throw new AgentWebAppError("PROJECT_SCOPE_INVALID", 400, "Disabled project selection requires exactly one project");
9775
+ }
9776
+ for (const projectId of allowedProjectIds) {
9777
+ if (!await this.deps.projectStore.getProjectById(tenantId, projectId)) {
9778
+ throw new AgentWebAppError("PROJECT_NOT_FOUND", 404, "Project not found");
9779
+ }
9780
+ }
9781
+ const scope = this.validateModels(input.scope, input.features.modelSelector);
9782
+ return { ...input, scope: { ...scope, allowedProjectIds } };
9783
+ }
9784
+ validateModels(scope, modelSelector) {
9785
+ const allowedModelKeys = scope.allowedModelKeys ? [...new Set(scope.allowedModelKeys)] : void 0;
9786
+ if (!allowedModelKeys?.length) {
9787
+ if (scope.defaultModelKey || modelSelector) {
9788
+ throw new AgentWebAppError("MODEL_SCOPE_INVALID", 400, "Agent-default models cannot enable model selection or set a default");
9789
+ }
9790
+ return { ...scope, defaultModelKey: void 0, allowedModelKeys: void 0 };
9791
+ }
9792
+ if (!modelSelector && allowedModelKeys.length > 1) {
9793
+ throw new AgentWebAppError("MODEL_SCOPE_INVALID", 400, "Disabled model selection allows at most one model");
9794
+ }
9795
+ if (scope.defaultModelKey && !allowedModelKeys.includes(scope.defaultModelKey)) {
9796
+ throw new AgentWebAppError("MODEL_SCOPE_INVALID", 400, "Default model must be in the model allowlist");
9797
+ }
9798
+ for (const key of allowedModelKeys) {
9799
+ if (!this.deps.modelRegistry.has(key)) {
9800
+ throw new AgentWebAppError("MODEL_NOT_FOUND", 400, "Configured model is not registered");
9801
+ }
9802
+ }
9803
+ return { ...scope, allowedModelKeys };
9804
+ }
9805
+ async requireWebApp(tenantId, webAppId) {
9806
+ return await this.deps.webAppStore.getById(tenantId, webAppId) ?? this.notFound();
9807
+ }
9808
+ async requireAuthorizedWebApp(context, webAppId) {
9809
+ const webApp = await this.requireWebApp(context.tenantId, webAppId);
9810
+ if (!await this.canAccessAssistant(context, webApp.assistantId)) this.notFound();
9811
+ return webApp;
9812
+ }
9813
+ async canAccessAssistant(context, assistantId) {
9814
+ const assistant = await this.deps.assistantStore.getAssistantById(context.tenantId, assistantId);
9815
+ if (assistant) return !assistant.ownerUserId || assistant.ownerUserId === context.userId;
9816
+ return this.deps.configuredAgentResolver.exists(context.tenantId, assistantId);
9817
+ }
9818
+ async writeSnapshot(context, current, patch) {
9819
+ const updated = await this.deps.webAppStore.update(
9820
+ context.tenantId,
9821
+ current.id,
9822
+ patch,
9823
+ { expectedUpdatedAt: current.updatedAt }
9824
+ );
9825
+ if (updated) return updated;
9826
+ await this.requireAuthorizedWebApp(context, current.id);
9827
+ throw new AgentWebAppError("WEB_APP_CONFLICT", 409, "Agent Web App was modified concurrently");
9828
+ }
9829
+ notFound() {
9830
+ throw new AgentWebAppError("WEB_APP_NOT_FOUND", 404, "Agent Web App not found");
9831
+ }
9832
+ };
9833
+
9834
+ // src/routes/agent-web-apps.ts
9835
+ import { ZodError } from "zod";
9836
+
9837
+ // src/schemas/agent-web-app.ts
9838
+ import { z as z3 } from "zod";
9839
+ var externalId = z3.string().min(1).max(128).regex(/^[A-Za-z0-9._:@-]+$/);
9840
+ var modelKey = z3.string().min(1).max(128);
9841
+ var agentWebAppScopeSchema = z3.object({
9842
+ defaultProjectId: externalId,
9843
+ allowedProjectIds: z3.array(externalId).min(1).max(100),
9844
+ defaultModelKey: modelKey.optional(),
9845
+ allowedModelKeys: z3.array(modelKey).max(100).optional()
9846
+ }).strict();
9847
+ var agentWebAppFeaturesSchema = z3.object({
9848
+ projectSelector: z3.boolean(),
9849
+ modelSelector: z3.boolean(),
9850
+ threadManagement: z3.boolean(),
9851
+ attachments: z3.boolean(),
9852
+ hitl: z3.boolean(),
9853
+ genUI: z3.boolean()
9854
+ }).strict();
9855
+ var agentWebAppAppearanceSchema = z3.object({
9856
+ title: z3.string().max(120).optional(),
9857
+ welcomeMessage: z3.string().max(1e3).optional(),
9858
+ primaryColor: z3.string().regex(/^#[0-9A-Fa-f]{6}$/).optional()
9859
+ }).strict();
9860
+ var createAgentWebAppSchema = z3.object({
9861
+ assistantId: externalId,
9862
+ name: z3.string().min(1).max(120),
9863
+ description: z3.string().max(1e3).optional(),
9864
+ integration: z3.object({ type: z3.literal("react_sdk") }).strict(),
9865
+ scope: agentWebAppScopeSchema,
9866
+ features: agentWebAppFeaturesSchema,
9867
+ appearance: agentWebAppAppearanceSchema
9868
+ }).strict();
9869
+ var updateAgentWebAppSchema = z3.object({
9870
+ name: z3.string().min(1).max(120).optional(),
9871
+ description: z3.string().max(1e3).optional(),
9872
+ scope: agentWebAppScopeSchema.partial().strict().optional(),
9873
+ features: agentWebAppFeaturesSchema.partial().strict().optional(),
9874
+ appearance: agentWebAppAppearanceSchema.partial().strict().optional()
9875
+ }).strict().refine((value) => Object.keys(value).length > 0, "Patch must not be empty");
9876
+ var listAgentWebAppsQuerySchema = z3.object({
9877
+ assistantId: externalId.optional()
9878
+ }).strict();
9879
+
9880
+ // src/routes/agent-web-apps.ts
9881
+ function registerAgentWebAppRoutes(app2, deps) {
9882
+ app2.register(async (routes) => {
9883
+ routes.setErrorHandler((error, _request, reply) => normalizeError(reply, error));
9884
+ registerScopedRoutes(routes, deps.service);
9885
+ });
9886
+ }
9887
+ function registerScopedRoutes(app2, service2) {
9888
+ app2.post("/api/web-apps", async (request, reply) => handle(reply, async () => ({
9889
+ statusCode: 201,
9890
+ data: await service2.create(requireContext(request), createAgentWebAppSchema.parse(request.body))
9891
+ })));
9892
+ app2.get("/api/web-apps", async (request, reply) => handle(reply, async () => {
9893
+ const context = requireContext(request);
9894
+ const query = listAgentWebAppsQuerySchema.parse(request.query);
9895
+ const records = await service2.list(context, query.assistantId);
9896
+ return { data: { records, total: records.length } };
9897
+ }));
9898
+ app2.get("/api/web-apps/:webAppId", async (request, reply) => handle(reply, async () => ({ data: await service2.get(requireContext(request), request.params.webAppId) })));
9899
+ app2.patch("/api/web-apps/:webAppId", async (request, reply) => handle(reply, async () => ({
9900
+ data: await service2.update(
9901
+ requireContext(request),
9902
+ request.params.webAppId,
9903
+ updateAgentWebAppSchema.parse(request.body)
9904
+ )
9905
+ })));
9906
+ app2.post("/api/web-apps/:webAppId/enable", async (request, reply) => handle(reply, async () => ({ data: await service2.enable(requireContext(request), request.params.webAppId) })));
9907
+ app2.post("/api/web-apps/:webAppId/disable", async (request, reply) => handle(reply, async () => ({ data: await service2.disable(requireContext(request), request.params.webAppId) })));
9908
+ app2.delete("/api/web-apps/:webAppId", async (request, reply) => handle(reply, async () => {
9909
+ await service2.delete(requireContext(request), request.params.webAppId);
9910
+ return { data: { id: request.params.webAppId } };
9911
+ }));
9912
+ }
9913
+ function requireContext(request) {
9914
+ const user = request.user;
9915
+ const userId = user?.id ?? user?.userId;
9916
+ if (!userId || !user?.tenantId) {
9917
+ throw new AgentWebAppError("UNAUTHORIZED", 401, "Console session required");
9918
+ }
9919
+ return { tenantId: user.tenantId, userId };
9920
+ }
9921
+ async function handle(reply, operation) {
9922
+ try {
9923
+ const result = await operation();
9924
+ return reply.code(result.statusCode ?? 200).send({ success: true, data: result.data });
9925
+ } catch (error) {
9926
+ if (error instanceof ZodError) {
9927
+ return invalidRequest(reply, 400);
9928
+ }
9929
+ if (error instanceof AgentWebAppError) {
9930
+ return reply.code(error.statusCode).send({
9931
+ success: false,
9932
+ error: { code: error.code, message: error.message, retryable: false }
9933
+ });
9934
+ }
9935
+ throw error;
9936
+ }
9937
+ }
9938
+ function normalizeError(reply, error) {
9939
+ if (error instanceof AgentWebAppError) {
9940
+ return reply.code(error.statusCode).send({
9941
+ success: false,
9942
+ error: { code: error.code, message: error.message, retryable: false }
9943
+ });
9944
+ }
9945
+ if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
9946
+ return invalidRequest(reply, error.statusCode);
9947
+ }
9948
+ return reply.code(500).send({
9949
+ success: false,
9950
+ error: { code: "INTERNAL_ERROR", message: "Internal server error", retryable: false }
9951
+ });
9952
+ }
9953
+ function invalidRequest(reply, statusCode) {
9954
+ return reply.code(statusCode).send({
9955
+ success: false,
9956
+ error: { code: "INVALID_REQUEST", message: "Invalid request", retryable: false }
9957
+ });
9958
+ }
9959
+
9960
+ // src/routes/agent-web-app-runtime.ts
9961
+ import { z as z4, ZodError as ZodError2 } from "zod";
9962
+
9963
+ // src/services/agent-web-app/AgentWebAppRuntimeService.ts
9964
+ import { createHash, randomUUID as randomUUID9 } from "crypto";
9965
+ import { MessageChunkTypes as MessageChunkTypes3, parseAgentWebAppGenUIBlock } from "@axiom-lattice/protocols";
9966
+ function isRecord(value) {
9967
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9968
+ }
9969
+ var AgentWebAppRuntimeError = class extends Error {
9970
+ constructor(code, statusCode, message) {
9971
+ super(message);
9972
+ this.code = code;
9973
+ this.statusCode = statusCode;
9974
+ this.name = "AgentWebAppRuntimeError";
9975
+ }
9976
+ };
9977
+ var AgentWebAppRuntimeService = class {
9978
+ constructor(deps) {
9979
+ this.deps = deps;
9980
+ }
9981
+ async bootstrap(webAppId, userId, selection) {
9982
+ const runtime = await this.resolveRuntime(webAppId, selection);
9983
+ const assistant = await this.deps.assistantResolver.resolve(
9984
+ runtime.webApp.tenantId,
9985
+ runtime.webApp.assistantId
9986
+ );
9987
+ if (!assistant) this.fail("WEB_APP_DISABLED", 403, "Agent Web App is unavailable");
9988
+ const publicProjectIds = runtime.webApp.features.projectSelector ? runtime.webApp.scope.allowedProjectIds : [runtime.webApp.scope.defaultProjectId];
9989
+ const projects = await Promise.all(publicProjectIds.map(async (id) => {
9990
+ const project = await this.deps.projectStore.getProjectById(runtime.webApp.tenantId, id);
9991
+ if (!project) this.fail("PROJECT_NOT_ALLOWED", 400, "Project is not available");
9992
+ return { id: project.id, name: project.name };
9993
+ }));
9994
+ const defaultModelKey = runtime.webApp.scope.defaultModelKey;
9995
+ if (defaultModelKey && !this.deps.modelRegistry.has(defaultModelKey)) {
9996
+ this.fail("MODEL_NOT_ALLOWED", 400, "Default model is not available");
9997
+ }
9998
+ const configuredPublicModelKeys = runtime.webApp.features.modelSelector ? runtime.webApp.scope.allowedModelKeys ?? [] : defaultModelKey ? [defaultModelKey] : [];
9999
+ const publicModelKeys = [...new Set(configuredPublicModelKeys)].filter((key) => this.deps.modelRegistry.has(key));
10000
+ const models = publicModelKeys.map((key) => ({
10001
+ key,
10002
+ label: this.deps.modelRegistry.label(key)
10003
+ }));
10004
+ const result = {
10005
+ webApp: {
10006
+ id: runtime.webApp.id,
10007
+ name: runtime.webApp.name,
10008
+ description: runtime.webApp.description,
10009
+ assistant,
10010
+ defaultProjectId: runtime.webApp.scope.defaultProjectId,
10011
+ defaultModelKey: runtime.webApp.scope.defaultModelKey,
10012
+ features: runtime.webApp.features,
10013
+ appearance: runtime.webApp.appearance,
10014
+ identityAssurance: "unverified"
10015
+ },
10016
+ projects,
10017
+ models
10018
+ };
10019
+ if (!runtime.webApp.features.threadManagement) {
10020
+ result.thread = await this.resolveImplicitThread(runtime, userId);
10021
+ }
10022
+ return result;
10023
+ }
10024
+ async listThreads(webAppId, userId, selection) {
10025
+ const runtime = await this.resolveRuntime(webAppId, selection);
10026
+ this.requireFeature(runtime.webApp.features.threadManagement);
10027
+ const threads = await this.ownedThreads(runtime, userId);
10028
+ return threads.sort(compareThreads).map(toRuntimeThread);
10029
+ }
10030
+ async createThread(webAppId, userId, input) {
10031
+ const runtime = await this.resolveRuntime(webAppId, input);
10032
+ this.requireFeature(runtime.webApp.features.threadManagement);
10033
+ return this.createOwnedThread(runtime, userId, input.label);
10034
+ }
10035
+ /** Update a thread label; omitted or blank labels preserve the existing label. */
10036
+ async updateThread(webAppId, userId, threadId, input) {
10037
+ const runtime = await this.resolveRuntime(webAppId, input);
10038
+ this.requireFeature(runtime.webApp.features.threadManagement);
10039
+ const thread = await this.requireOwnedThread(runtime, userId, threadId);
10040
+ const label = normalizeLabel(input.label);
10041
+ const updated = await this.deps.threadStore.updateThread(runtime.webApp.tenantId, thread.id, {
10042
+ metadata: this.metadata(runtime, userId, label)
10043
+ });
10044
+ if (!updated) this.threadNotFound();
10045
+ return toRuntimeThread(updated);
10046
+ }
10047
+ async deleteThread(webAppId, userId, threadId, selection) {
10048
+ const runtime = await this.resolveRuntime(webAppId, selection);
10049
+ this.requireFeature(runtime.webApp.features.threadManagement);
10050
+ await this.requireOwnedThread(runtime, userId, threadId);
10051
+ if (!await this.deps.threadStore.deleteThread(runtime.webApp.tenantId, threadId)) this.threadNotFound();
10052
+ }
10053
+ async getMessages(webAppId, userId, threadId, selection) {
10054
+ const runtime = await this.resolveRuntime(webAppId, selection);
10055
+ await this.requireOwnedThread(runtime, userId, threadId);
10056
+ const messages = await this.deps.messageReader.getCurrentMessages({
10057
+ tenantId: runtime.webApp.tenantId,
10058
+ assistantId: runtime.webApp.assistantId,
10059
+ threadId,
10060
+ workspaceId: runtime.project.workspaceId,
10061
+ projectId: runtime.project.id
10062
+ });
10063
+ return messages.flatMap((message) => {
10064
+ if (!isRecord(message) || typeof message.id !== "string" || message.role !== "human" && message.role !== "ai") return [];
10065
+ const content = typeof message.content === "string" ? message.content : runtime.webApp.features.genUI && Array.isArray(message.content) ? message.content.map(parseAgentWebAppGenUIBlock).filter((block) => block !== void 0) : void 0;
10066
+ return [{ id: message.id, role: message.role, ...content && (typeof content === "string" || content.length > 0) ? { content } : {} }];
10067
+ });
10068
+ }
10069
+ async streamMessage(webAppId, userId, threadId, input) {
10070
+ const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, input);
10071
+ const refs = this.resolveAttachmentRefs(runtime, userId, threadId, input.attachmentRefs ?? []);
10072
+ const agent = this.resolveAgent(runtime, threadId);
10073
+ const content = refs.length ? `${input.content}
10074
+
10075
+ ${formatFileRefsSection(refs)}` : input.content;
10076
+ const execution = await this.execute(agent, runtime, { input: { message: content } });
10077
+ return { ...execution, allowInterrupts: runtime.webApp.features.hitl, allowGenUI: runtime.webApp.features.genUI };
10078
+ }
10079
+ async abort(webAppId, userId, threadId, selection) {
10080
+ const { agent } = await this.resolveOwnedAgent(webAppId, userId, threadId, selection);
10081
+ await agent.abort();
10082
+ }
10083
+ async uploadAttachment(webAppId, userId, threadId, selection, file) {
10084
+ const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, selection);
10085
+ this.requireNamedFeature(runtime.webApp.features.attachments, "Attachments are disabled");
10086
+ if (!this.deps.saveProjectFile || !this.deps.attachmentRefs) this.fail("INTERNAL_ERROR", 500, "Attachment service is unavailable");
10087
+ const userDigest = createHash("sha256").update(userId, "utf8").digest("hex");
10088
+ const contentDigest = createHash("sha256").update(file.bytes).digest("hex");
10089
+ const displayName = file.name;
10090
+ const physicalName = `${randomUUID9()}-${sanitizePhysicalFilename(file.name)}`;
10091
+ const saved = await this.deps.saveProjectFile({
10092
+ tenantId: runtime.webApp.tenantId,
10093
+ workspaceId: runtime.project.workspaceId,
10094
+ projectId: runtime.project.id,
10095
+ assistantId: runtime.webApp.assistantId,
10096
+ name: physicalName,
10097
+ mimeType: file.mimeType,
10098
+ bytes: file.bytes,
10099
+ path: `/project/web-apps/${runtime.webApp.id}/${userDigest}/${threadId}`
10100
+ });
10101
+ const ref = this.deps.attachmentRefs.sign({
10102
+ webAppId: runtime.webApp.id,
10103
+ userId,
10104
+ threadId,
10105
+ projectId: runtime.project.id,
10106
+ uri: saved.uri,
10107
+ name: displayName,
10108
+ mimeType: file.mimeType,
10109
+ size: saved.size,
10110
+ contentDigest
10111
+ });
10112
+ return { ref, name: displayName, mimeType: file.mimeType, size: saved.size };
10113
+ }
10114
+ async resumeInterrupt(webAppId, userId, threadId, interruptId, selection, input) {
10115
+ const { runtime, agent } = await this.resolveOwnedAgent(webAppId, userId, threadId, selection);
10116
+ this.requireNamedFeature(runtime.webApp.features.hitl, "Human review is disabled");
10117
+ const state = await agent.getCurrentState();
10118
+ if (!hasInterrupt(state, interruptId)) this.fail("INVALID_REQUEST", 400, "Interrupt not found");
10119
+ const execution = await this.execute(agent, runtime, {
10120
+ input: { message: input.message },
10121
+ command: { resume: input.response }
10122
+ });
10123
+ return { ...execution, allowInterrupts: true, allowGenUI: runtime.webApp.features.genUI };
10124
+ }
10125
+ execute(agent, runtime, request) {
10126
+ return executeScopedAgentMessage(agent, {
10127
+ ...request,
10128
+ ...runtime.modelKey ? { custom_run_config: { modelConfig: { modelKey: runtime.modelKey } } } : {}
10129
+ }, [MessageChunkTypes3.MESSAGE_COMPLETED, MessageChunkTypes3.INTERRUPT, MessageChunkTypes3.MESSAGE_FAILED]);
10130
+ }
10131
+ async resolveRuntime(webAppId, selection) {
10132
+ const webApp = await this.deps.webAppStore.findById(webAppId);
10133
+ if (!webApp) this.fail("WEB_APP_NOT_FOUND", 404, "Agent Web App not found");
10134
+ const previewAllowed = webApp.status === "draft" && selection.preview?.tenantId === webApp.tenantId && await this.deps.authorizePreview?.(webApp, selection.preview) === true;
10135
+ if (webApp.status !== "active" && !previewAllowed) this.fail("WEB_APP_DISABLED", 403, "Agent Web App is disabled");
10136
+ if (!webApp.features.projectSelector && selection.projectId !== void 0) {
10137
+ this.fail("PROJECT_SELECTOR_DISABLED", 400, "Project selection is disabled");
10138
+ }
10139
+ const projectId = selection.projectId ?? webApp.scope.defaultProjectId;
10140
+ if (!webApp.scope.allowedProjectIds.includes(projectId)) {
10141
+ this.fail("PROJECT_NOT_ALLOWED", 400, "Project is not allowed");
10142
+ }
10143
+ const project = await this.deps.projectStore.getProjectById(webApp.tenantId, projectId);
10144
+ if (!project) this.fail("PROJECT_NOT_ALLOWED", 400, "Project is not available");
10145
+ if (!webApp.features.modelSelector && selection.modelKey !== void 0) {
10146
+ this.fail("MODEL_NOT_ALLOWED", 400, "Model selection is disabled");
10147
+ }
10148
+ const modelKey2 = selection.modelKey ?? webApp.scope.defaultModelKey;
10149
+ const allowedModels = webApp.scope.allowedModelKeys;
10150
+ if (modelKey2 && (!allowedModels?.includes(modelKey2) || !this.deps.modelRegistry.has(modelKey2))) {
10151
+ this.fail("MODEL_NOT_ALLOWED", 400, "Model is not allowed");
10152
+ }
10153
+ return { webApp, project, modelKey: modelKey2 };
10154
+ }
10155
+ async resolveOwnedAgent(webAppId, userId, threadId, selection) {
10156
+ const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, selection);
10157
+ return { runtime, agent: this.resolveAgent(runtime, threadId) };
10158
+ }
10159
+ async resolveOwnedRuntime(webAppId, userId, threadId, selection) {
10160
+ const runtime = await this.resolveRuntime(webAppId, selection);
10161
+ await this.requireOwnedThread(runtime, userId, threadId);
10162
+ return runtime;
10163
+ }
10164
+ resolveAgent(runtime, threadId) {
10165
+ if (!this.deps.agentResolver) this.fail("INTERNAL_ERROR", 500, "Agent runtime is unavailable");
10166
+ return this.deps.agentResolver.getAgent({
10167
+ tenantId: runtime.webApp.tenantId,
10168
+ assistantId: runtime.webApp.assistantId,
10169
+ threadId,
10170
+ workspaceId: runtime.project.workspaceId,
10171
+ projectId: runtime.project.id,
10172
+ ...runtime.modelKey ? { modelKey: runtime.modelKey } : {}
10173
+ });
10174
+ }
10175
+ resolveAttachmentRefs(runtime, userId, threadId, refs) {
10176
+ if (refs.length === 0) return [];
10177
+ this.requireNamedFeature(runtime.webApp.features.attachments, "Attachments are disabled");
10178
+ if (!this.deps.attachmentRefs) this.fail("INTERNAL_ERROR", 500, "Attachment service is unavailable");
10179
+ try {
10180
+ return refs.map((ref) => ({
10181
+ ...this.deps.attachmentRefs.verify(ref, {
10182
+ webAppId: runtime.webApp.id,
10183
+ userId,
10184
+ threadId,
10185
+ projectId: runtime.project.id
10186
+ }),
10187
+ addedBy: "user"
10188
+ }));
10189
+ } catch {
10190
+ return this.fail("INVALID_REQUEST", 400, "Invalid attachment reference");
10191
+ }
10192
+ }
10193
+ async ownedThreads(runtime, userId) {
10194
+ return this.deps.threadStore.getThreadsByAssistantId(
10195
+ runtime.webApp.tenantId,
10196
+ runtime.webApp.assistantId,
10197
+ {
10198
+ source: "web_app",
10199
+ webAppId: runtime.webApp.id,
10200
+ userId,
10201
+ projectId: runtime.project.id
10202
+ }
10203
+ );
10204
+ }
10205
+ async requireOwnedThread(runtime, userId, threadId) {
10206
+ const thread = await this.deps.threadStore.getThreadById(runtime.webApp.tenantId, threadId);
10207
+ const metadata = thread?.metadata;
10208
+ if (!thread || thread.assistantId !== runtime.webApp.assistantId || metadata?.source !== "web_app" || metadata.webAppId !== runtime.webApp.id || metadata.userId !== userId || metadata.projectId !== runtime.project.id) this.threadNotFound();
10209
+ return thread;
10210
+ }
10211
+ async resolveImplicitThread(runtime, userId) {
10212
+ const latest = (await this.ownedThreads(runtime, userId)).sort(compareThreads)[0];
10213
+ if (latest) return toRuntimeThread(latest);
10214
+ const threadId = implicitThreadId(runtime, userId);
10215
+ const existing = await this.deps.threadStore.getThreadById(
10216
+ runtime.webApp.tenantId,
10217
+ threadId
10218
+ );
10219
+ if (existing) return toRuntimeThread(this.requireImplicitOwnership(runtime, userId, existing));
10220
+ await this.deps.threadStore.createThread(
10221
+ runtime.webApp.tenantId,
10222
+ runtime.webApp.assistantId,
10223
+ threadId,
10224
+ { metadata: this.metadata(runtime, userId) }
10225
+ );
10226
+ const persisted = await this.deps.threadStore.getThreadById(
10227
+ runtime.webApp.tenantId,
10228
+ threadId
10229
+ );
10230
+ if (!persisted) this.fail("INTERNAL_ERROR", 500, "Implicit thread could not be resolved");
10231
+ return toRuntimeThread(this.requireImplicitOwnership(runtime, userId, persisted));
10232
+ }
10233
+ requireImplicitOwnership(runtime, userId, thread) {
10234
+ const metadata = thread.metadata;
10235
+ if (thread.assistantId !== runtime.webApp.assistantId || metadata?.source !== "web_app" || metadata.webAppId !== runtime.webApp.id || metadata.userId !== userId || metadata.projectId !== runtime.project.id) this.fail("INTERNAL_ERROR", 500, "Implicit thread identity conflict");
10236
+ return thread;
10237
+ }
10238
+ async createOwnedThread(runtime, userId, rawLabel) {
10239
+ const thread = await this.deps.threadStore.createThread(
10240
+ runtime.webApp.tenantId,
10241
+ runtime.webApp.assistantId,
10242
+ randomUUID9(),
10243
+ { metadata: this.metadata(runtime, userId, normalizeLabel(rawLabel)) }
10244
+ );
10245
+ return toRuntimeThread(thread);
10246
+ }
10247
+ metadata(runtime, userId, label) {
10248
+ return {
10249
+ source: "web_app",
10250
+ webAppId: runtime.webApp.id,
10251
+ userId,
10252
+ projectId: runtime.project.id,
10253
+ ...label ? { label } : {}
10254
+ };
10255
+ }
10256
+ requireFeature(enabled) {
10257
+ if (!enabled) this.fail("FEATURE_DISABLED", 403, "Thread management is disabled");
10258
+ }
10259
+ requireNamedFeature(enabled, message) {
10260
+ if (!enabled) this.fail("FEATURE_DISABLED", 403, message);
10261
+ }
10262
+ threadNotFound() {
10263
+ return this.fail("THREAD_NOT_FOUND", 404, "Thread not found");
10264
+ }
10265
+ fail(code, statusCode, message) {
10266
+ throw new AgentWebAppRuntimeError(code, statusCode, message);
10267
+ }
10268
+ };
10269
+ function hasInterrupt(state, interruptId) {
10270
+ if (typeof state !== "object" || state === null || !("tasks" in state) || !Array.isArray(state.tasks)) return false;
10271
+ return state.tasks.some((task) => {
10272
+ if (typeof task !== "object" || task === null || !("interrupts" in task) || !Array.isArray(task.interrupts)) return false;
10273
+ return task.interrupts.some((interrupt) => typeof interrupt === "object" && interrupt !== null && "id" in interrupt && interrupt.id === interruptId);
10274
+ });
10275
+ }
10276
+ function sanitizePhysicalFilename(name) {
10277
+ const sanitized = name.replace(/[^A-Za-z0-9._-]/g, "_").replace(/^\.+/, "");
10278
+ return sanitized || "file";
10279
+ }
10280
+ function normalizeLabel(label) {
10281
+ const normalized = label?.trim();
10282
+ return normalized || void 0;
10283
+ }
10284
+ function implicitThreadId(runtime, userId) {
10285
+ const canonicalTuple = JSON.stringify([
10286
+ "axiom.agent-web-app.implicit-thread",
10287
+ 1,
10288
+ runtime.webApp.tenantId,
10289
+ runtime.webApp.assistantId,
10290
+ runtime.webApp.id,
10291
+ userId,
10292
+ runtime.project.id
10293
+ ]);
10294
+ const bytes = createHash("sha256").update(canonicalTuple, "utf8").digest().subarray(0, 16);
10295
+ bytes[6] = bytes[6] & 15 | 128;
10296
+ bytes[8] = bytes[8] & 63 | 128;
10297
+ const hex = bytes.toString("hex");
10298
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
10299
+ }
10300
+ function compareThreads(left, right) {
10301
+ return right.updatedAt.getTime() - left.updatedAt.getTime() || right.id.localeCompare(left.id);
10302
+ }
10303
+ function toRuntimeThread(thread) {
10304
+ return {
10305
+ id: thread.id,
10306
+ projectId: String(thread.metadata?.projectId),
10307
+ ...typeof thread.metadata?.label === "string" ? { label: thread.metadata.label } : {},
10308
+ createdAt: thread.createdAt,
10309
+ updatedAt: thread.updatedAt
10310
+ };
10311
+ }
10312
+
10313
+ // src/services/agent-web-app/AgentWebAppStreamProjector.ts
10314
+ import {
10315
+ MessageChunkTypes as MessageChunkTypes4,
10316
+ parseAgentWebAppGenUIBlock as parseAgentWebAppGenUIBlock2
10317
+ } from "@axiom-lattice/protocols";
10318
+ function createAgentWebAppStreamProjectionContext(options = {}) {
10319
+ return { startedToolIds: /* @__PURE__ */ new Set(), allowInterrupts: options.allowInterrupts ?? true, allowGenUI: options.allowGenUI ?? false };
10320
+ }
10321
+ function projectAgentWebAppChunk(chunk, context) {
10322
+ if (chunk.type === MessageChunkTypes4.AI) {
10323
+ const events = [];
10324
+ const startedToolIds = context?.startedToolIds ?? /* @__PURE__ */ new Set();
10325
+ if (typeof chunk.data.content === "string" && chunk.data.content) events.push({ type: "message.delta", text: chunk.data.content });
10326
+ if (context?.allowGenUI && Array.isArray(chunk.data.content)) {
10327
+ for (const value of chunk.data.content) {
10328
+ const block = parseAgentWebAppGenUIBlock2(value);
10329
+ if (block) events.push({ type: "genui.render", block });
10330
+ }
10331
+ }
10332
+ for (const call of chunk.data.tool_call_chunks ?? []) {
10333
+ if (call.id && call.name && !startedToolIds.has(call.id)) {
10334
+ startedToolIds.add(call.id);
10335
+ events.push({ type: "tool.started", id: call.id, name: call.name });
10336
+ }
10337
+ }
10338
+ for (const call of chunk.data.tool_calls ?? []) {
10339
+ if (!startedToolIds.has(call.id)) {
10340
+ startedToolIds.add(call.id);
10341
+ events.push({ type: "tool.started", id: call.id, name: call.name });
10342
+ }
10343
+ }
10344
+ return events;
10345
+ }
10346
+ if (chunk.type === MessageChunkTypes4.TOOL && chunk.data.tool_call_id) {
10347
+ return [{ type: "tool.completed", id: chunk.data.tool_call_id }];
10348
+ }
10349
+ if (chunk.type === MessageChunkTypes4.INTERRUPT) {
10350
+ if (context && !context.allowInterrupts) return [{ type: "stream.completed" }];
10351
+ const interrupt = publicInterrupt(chunk);
10352
+ return interrupt ? [{ type: "interrupt.created", interrupt }, { type: "stream.completed" }] : [{ type: "stream.completed" }];
10353
+ }
10354
+ if (chunk.type === MessageChunkTypes4.MESSAGE_COMPLETED) {
10355
+ return [
10356
+ { type: "message.completed", messageId: chunk.data.id },
10357
+ { type: "stream.completed" }
10358
+ ];
10359
+ }
10360
+ if (chunk.type === MessageChunkTypes4.MESSAGE_FAILED) {
10361
+ return [
10362
+ { type: "error", error: { code: "STREAM_FAILED", message: "Stream failed", retryable: true } },
10363
+ { type: "stream.completed" }
10364
+ ];
10365
+ }
10366
+ return [];
10367
+ }
10368
+ function publicInterrupt(chunk) {
10369
+ const value = parseInterruptContent(chunk.data.content);
10370
+ if (!value) return void 0;
10371
+ const topLevelId = chunk.id;
10372
+ const id = typeof topLevelId === "string" ? topLevelId : chunk.data.id;
10373
+ if (!id) return void 0;
10374
+ return {
10375
+ id,
10376
+ type: typeof value.type === "string" ? value.type : "input",
10377
+ prompt: typeof value.prompt === "string" ? value.prompt : typeof value.message === "string" ? value.message : "Input required",
10378
+ ...isRecord2(value.data) ? { data: value.data } : {}
10379
+ };
10380
+ }
10381
+ function parseInterruptContent(content) {
10382
+ if (!content) return {};
10383
+ if (isRecord2(content)) return content;
10384
+ if (typeof content !== "string") return void 0;
10385
+ try {
10386
+ const parsed = JSON.parse(content);
10387
+ return isRecord2(parsed) ? parsed : { prompt: content };
10388
+ } catch {
10389
+ return { prompt: content };
10390
+ }
10391
+ }
10392
+ function isRecord2(value) {
10393
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10394
+ }
10395
+
10396
+ // src/routes/agent-web-app-runtime.ts
10397
+ var idSchema = z4.string().min(1).max(128);
10398
+ var paramsSchema = z4.object({ webAppId: idSchema }).strict();
10399
+ var threadParamsSchema = z4.object({ webAppId: idSchema, threadId: idSchema }).strict();
10400
+ var interruptParamsSchema = z4.object({ webAppId: idSchema, threadId: idSchema, interruptId: idSchema }).strict();
10401
+ var bootstrapQuerySchema = z4.object({ projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
10402
+ var projectQuerySchema = z4.object({ projectId: idSchema.optional() }).strict();
10403
+ var threadBodySchema = z4.object({ projectId: idSchema.optional(), label: z4.string().max(128).optional() }).strict();
10404
+ var streamBodySchema = z4.object({
10405
+ content: z4.string().trim().min(1).max(32768),
10406
+ projectId: idSchema.optional(),
10407
+ modelKey: idSchema.optional(),
10408
+ attachmentRefs: z4.array(z4.string().min(1).max(8192)).max(10).optional()
10409
+ }).strict();
10410
+ var resumeBodySchema = z4.object({
10411
+ projectId: idSchema.optional(),
10412
+ modelKey: idSchema.optional(),
10413
+ message: z4.string().trim().min(1).max(4096),
10414
+ response: z4.union([
10415
+ z4.boolean(),
10416
+ z4.string().max(4096),
10417
+ z4.object({ approved: z4.boolean(), feedback: z4.string().max(4096).optional() }).strict()
10418
+ ])
10419
+ }).strict();
10420
+ var MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
10421
+ function createExternalHeaderIdentityResolver() {
10422
+ return { resolve(request) {
10423
+ const raw = request.headers["x-axiom-external-user-id"];
10424
+ if (raw === void 0) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "External user id is required");
10425
+ if (Array.isArray(raw) || typeof raw !== "string") throw new AgentWebAppRuntimeError("INVALID_USER_ID", 400, "External user id is invalid");
10426
+ const userId = raw.trim();
10427
+ if (!/^[A-Za-z0-9._:@-]+$/.test(userId) || userId.length > 128) throw new AgentWebAppRuntimeError("INVALID_USER_ID", 400, "External user id is invalid");
10428
+ return { userId, assurance: "unverified" };
10429
+ } };
10430
+ }
10431
+ function registerAgentWebAppRuntimeRoutes(app2, deps) {
10432
+ const identities = deps.identityResolver ?? createExternalHeaderIdentityResolver();
10433
+ app2.register(async (routes) => {
10434
+ routes.setErrorHandler((error, _request, reply) => normalizeError2(reply, error));
10435
+ routes.get("/api/web-apps/:webAppId/runtime/bootstrap", async (request, reply) => handle2(reply, async () => {
10436
+ const { webAppId } = paramsSchema.parse(request.params);
10437
+ const identity = resolveRuntimeIdentity(request, webAppId, identities);
10438
+ return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview)) };
10439
+ }));
10440
+ routes.get("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
10441
+ const { webAppId } = paramsSchema.parse(request.params);
10442
+ const identity = resolveRuntimeIdentity(request, webAppId, identities);
10443
+ const records = await deps.service.listThreads(webAppId, identity.userId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
10444
+ return { data: { records, total: records.length } };
10445
+ }));
10446
+ routes.post("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
10447
+ const { webAppId } = paramsSchema.parse(request.params);
10448
+ const identity = resolveRuntimeIdentity(request, webAppId, identities);
10449
+ return { statusCode: 201, data: await deps.service.createThread(webAppId, identity.userId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
10450
+ }));
10451
+ routes.patch("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
10452
+ const { webAppId, threadId } = threadParamsSchema.parse(request.params);
10453
+ const identity = resolveRuntimeIdentity(request, webAppId, identities);
10454
+ return { data: await deps.service.updateThread(webAppId, identity.userId, threadId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
10455
+ }));
10456
+ routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
10457
+ const { webAppId, threadId } = threadParamsSchema.parse(request.params);
10458
+ const identity = resolveRuntimeIdentity(request, webAppId, identities);
10459
+ await deps.service.deleteThread(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
10460
+ return { data: { id: threadId } };
10461
+ }));
10462
+ routes.get("/api/web-apps/:webAppId/runtime/threads/:threadId/messages", async (request, reply) => handle2(reply, async () => {
10463
+ const { webAppId, threadId } = threadParamsSchema.parse(request.params);
10464
+ const identity = resolveRuntimeIdentity(request, webAppId, identities);
10465
+ const messages = await deps.service.getMessages(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
10466
+ return { data: { messages } };
10467
+ }));
10468
+ routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/messages/stream", async (request, reply) => {
10469
+ try {
10470
+ const { webAppId, threadId } = threadParamsSchema.parse(request.params);
10471
+ const identity = resolveRuntimeIdentity(request, webAppId, identities);
10472
+ const input = streamBodySchema.parse(request.body);
10473
+ const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
10474
+ beginSse(reply);
10475
+ await pipeAgentWebAppStream(reply.raw, result.stream, result);
10476
+ } catch (error) {
10477
+ return normalizeError2(reply, error);
10478
+ }
10479
+ });
10480
+ routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/abort", async (request, reply) => handle2(reply, async () => {
10481
+ const { webAppId, threadId } = threadParamsSchema.parse(request.params);
10482
+ const identity = resolveRuntimeIdentity(request, webAppId, identities);
10483
+ await deps.service.abort(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
10484
+ return { data: { aborted: true } };
10485
+ }));
10486
+ routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/attachments", async (request, reply) => handle2(reply, async () => {
10487
+ const { webAppId, threadId } = threadParamsSchema.parse(request.params);
10488
+ const identity = resolveRuntimeIdentity(request, webAppId, identities);
10489
+ if (!request.isMultipart()) throw new ZodError2([]);
10490
+ const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
10491
+ if (!part || !part.filename || part.filename.length > 255) throw new ZodError2([]);
10492
+ const bytes = await part.toBuffer();
10493
+ if (bytes.length === 0 || part.file.truncated) throw new ZodError2([]);
10494
+ const data = await deps.service.uploadAttachment(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview), {
10495
+ name: part.filename,
10496
+ mimeType: part.mimetype || "application/octet-stream",
10497
+ bytes
10498
+ });
10499
+ return { statusCode: 201, data };
10500
+ }));
10501
+ routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/interrupts/:interruptId/resume", async (request, reply) => {
10502
+ try {
10503
+ const { webAppId, threadId, interruptId } = interruptParamsSchema.parse(request.params);
10504
+ const identity = resolveRuntimeIdentity(request, webAppId, identities);
10505
+ const { projectId, modelKey: modelKey2, message, response } = resumeBodySchema.parse(request.body);
10506
+ const result = await deps.service.resumeInterrupt(webAppId, identity.userId, threadId, interruptId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview), { message, response });
10507
+ beginSse(reply);
10508
+ await pipeAgentWebAppStream(reply.raw, result.stream, result);
10509
+ } catch (error) {
10510
+ return normalizeError2(reply, error);
10511
+ }
10512
+ });
10513
+ });
10514
+ }
10515
+ function resolveRuntimeIdentity(request, webAppId, identities) {
10516
+ if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request);
10517
+ const user = request.user;
10518
+ if (!user?.id || !user.tenantId) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "Authenticated Console preview is required");
10519
+ return { userId: `console-preview:${user.id}:${webAppId}`, preview: { tenantId: user.tenantId, userId: user.id } };
10520
+ }
10521
+ function withPreview(selection, preview) {
10522
+ return { ...selection, ...preview ? { preview } : {} };
10523
+ }
10524
+ async function pipeAgentWebAppStream(raw, stream, options = {}) {
10525
+ const projection = createAgentWebAppStreamProjectionContext(options);
10526
+ await pipeAsyncIterableToSse(raw, stream, {
10527
+ project: (chunk) => projectAgentWebAppChunk(chunk, projection),
10528
+ onError: () => [
10529
+ { type: "error", error: { code: "STREAM_FAILED", message: "Stream failed", retryable: true } },
10530
+ { type: "stream.completed" }
10531
+ ]
10532
+ });
10533
+ }
10534
+ function beginSse(reply) {
10535
+ reply.hijack();
10536
+ reply.raw.writeHead(200, {
10537
+ "Content-Type": "text/event-stream; charset=utf-8",
10538
+ "Cache-Control": "no-cache, no-transform",
10539
+ Connection: "keep-alive",
10540
+ "X-Accel-Buffering": "no"
10541
+ });
10542
+ }
10543
+ async function handle2(reply, operation) {
10544
+ try {
10545
+ const result = await operation();
10546
+ return reply.code(result.statusCode ?? 200).send({ success: true, data: result.data });
10547
+ } catch (error) {
10548
+ return normalizeError2(reply, error);
10549
+ }
10550
+ }
10551
+ function normalizeError2(reply, error) {
10552
+ if (error instanceof AgentWebAppRuntimeError) return reply.code(error.statusCode).send({ success: false, error: { code: error.code, message: error.message, retryable: false } });
10553
+ if (error instanceof ZodError2) return reply.code(400).send({ success: false, error: { code: "INVALID_REQUEST", message: "Invalid request", retryable: false } });
10554
+ if (isMultipartRequestError(error)) return reply.code(400).send({ success: false, error: { code: "INVALID_REQUEST", message: "Invalid request", retryable: false } });
10555
+ return reply.code(500).send({ success: false, error: { code: "INTERNAL_ERROR", message: "Internal server error", retryable: false } });
10556
+ }
10557
+ function isMultipartRequestError(error) {
10558
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && error.code.startsWith("FST_");
10559
+ }
10560
+
10561
+ // src/services/agent-web-app/AgentWebAppAttachmentRef.ts
10562
+ import { createCipheriv, createDecipheriv, createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
10563
+ var TTL_MS = 15 * 6e4;
10564
+ function resolveAgentWebAppAttachmentSecret(consoleSecret) {
10565
+ return process.env.WEB_APP_ATTACHMENT_SECRET || process.env.AUTH_TOKEN_SECRET || process.env.JWT_SECRET || consoleSecret;
10566
+ }
10567
+ var AgentWebAppAttachmentRefService = class {
10568
+ constructor(secret, now = Date.now) {
10569
+ this.now = now;
10570
+ if (!secret) throw new Error("Attachment signing secret is required");
10571
+ this.key = createHash2("sha256").update(secret, "utf8").digest();
10572
+ }
10573
+ sign(input) {
10574
+ const payload = {
10575
+ webAppId: input.webAppId,
10576
+ userDigest: digestUser(input.userId),
10577
+ threadId: input.threadId,
10578
+ projectId: input.projectId,
10579
+ uri: input.uri,
10580
+ name: input.name,
10581
+ mimeType: input.mimeType,
10582
+ size: input.size,
10583
+ contentDigest: input.contentDigest,
10584
+ exp: this.now() + TTL_MS
10585
+ };
10586
+ const iv = randomBytes2(12);
10587
+ const cipher = createCipheriv("aes-256-gcm", this.key, iv);
10588
+ const ciphertext = Buffer.concat([
10589
+ cipher.update(JSON.stringify(payload), "utf8"),
10590
+ cipher.final()
10591
+ ]);
10592
+ const tag = cipher.getAuthTag();
10593
+ return `v1.${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
10594
+ }
10595
+ verify(ref, scope) {
10596
+ const [version, encodedIv, encodedCiphertext, encodedTag, extra] = ref.split(".");
10597
+ if (version !== "v1" || !encodedIv || !encodedCiphertext || !encodedTag || extra !== void 0 || !/^[A-Za-z0-9_-]{16}$/.test(encodedIv) || !/^[A-Za-z0-9_-]+$/.test(encodedCiphertext) || !/^[A-Za-z0-9_-]{22}$/.test(encodedTag)) this.invalid();
10598
+ try {
10599
+ const iv = decodeCanonical(encodedIv, 12);
10600
+ const ciphertext = decodeCanonical(encodedCiphertext);
10601
+ const tag = decodeCanonical(encodedTag, 16);
10602
+ const decipher = createDecipheriv("aes-256-gcm", this.key, iv);
10603
+ decipher.setAuthTag(tag);
10604
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
10605
+ const value = JSON.parse(plaintext.toString("utf8"));
10606
+ if (!isAttachmentPayload(value)) this.invalid();
10607
+ if (value.exp <= this.now()) throw new Error("Attachment reference expired");
10608
+ if (value.webAppId !== scope.webAppId || value.userDigest !== digestUser(scope.userId) || value.threadId !== scope.threadId || value.projectId !== scope.projectId) this.invalid();
10609
+ return {
10610
+ uri: value.uri,
10611
+ name: value.name,
10612
+ mimeType: value.mimeType,
10613
+ size: value.size,
10614
+ contentDigest: value.contentDigest
10615
+ };
10616
+ } catch (error) {
10617
+ if (error instanceof Error && error.message === "Attachment reference expired") throw error;
10618
+ this.invalid();
10619
+ }
10620
+ }
10621
+ invalid() {
10622
+ throw new Error("Invalid attachment reference");
10623
+ }
10624
+ };
10625
+ function decodeCanonical(value, expectedBytes) {
10626
+ const decoded = Buffer.from(value, "base64url");
10627
+ if (decoded.toString("base64url") !== value || expectedBytes !== void 0 && decoded.length !== expectedBytes) {
10628
+ throw new Error("Invalid attachment reference");
10629
+ }
10630
+ return decoded;
10631
+ }
10632
+ function digestUser(userId) {
10633
+ return createHash2("sha256").update(userId, "utf8").digest("hex");
10634
+ }
10635
+ function isAttachmentPayload(value) {
10636
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
10637
+ const record = value;
10638
+ if (Object.keys(record).sort().join(",") !== "contentDigest,exp,mimeType,name,projectId,size,threadId,uri,userDigest,webAppId") return false;
10639
+ return typeof record.webAppId === "string" && typeof record.userDigest === "string" && /^[a-f0-9]{64}$/.test(record.userDigest) && typeof record.threadId === "string" && typeof record.projectId === "string" && typeof record.uri === "string" && record.uri.startsWith("/") && typeof record.name === "string" && record.name.length > 0 && typeof record.mimeType === "string" && record.mimeType.length > 0 && typeof record.size === "number" && Number.isSafeInteger(record.size) && record.size >= 0 && typeof record.contentDigest === "string" && /^[a-f0-9]{64}$/.test(record.contentDigest) && typeof record.exp === "number" && Number.isSafeInteger(record.exp);
10640
+ }
10641
+
9589
10642
  // src/routes/index.ts
9590
10643
  var registerLatticeRoutes = (app2, channelDeps) => {
10644
+ const webAppStore = getStoreLattice16("default", "agentWebApp").store;
10645
+ const assistantStore = getStoreLattice16("default", "assistant").store;
10646
+ const projectStore = getStoreLattice16("default", "project").store;
10647
+ registerAgentWebAppRoutes(app2, {
10648
+ service: new AgentWebAppService({
10649
+ webAppStore,
10650
+ assistantStore,
10651
+ projectStore,
10652
+ modelRegistry: { has: (key) => modelLatticeManager3.hasLattice(key) },
10653
+ configuredAgentResolver: createConfiguredAgentResolver({
10654
+ hydrate: (tenantId) => agentLatticeManager5.initializeStoredAssistantsForTenant(tenantId),
10655
+ has: (tenantId, assistantId) => agentLatticeManager5.hasAgentLatticeWithTenant(tenantId, assistantId)
10656
+ })
10657
+ })
10658
+ });
10659
+ registerAgentWebAppRuntimeRoutes(app2, {
10660
+ service: new AgentWebAppRuntimeService({
10661
+ webAppStore,
10662
+ threadStore: getStoreLattice16("default", "thread").store,
10663
+ projectStore,
10664
+ assistantResolver: {
10665
+ resolve: async (tenantId, assistantId) => {
10666
+ const stored = await assistantStore.getAssistantById(tenantId, assistantId);
10667
+ if (stored) return stored;
10668
+ await agentLatticeManager5.initializeStoredAssistantsForTenant(tenantId);
10669
+ const config = agentLatticeManager5.getAgentConfigWithTenant(tenantId, assistantId);
10670
+ return config ? { id: assistantId, name: config.name, description: config.description } : null;
10671
+ }
10672
+ },
10673
+ modelRegistry: {
10674
+ has: (key) => modelLatticeManager3.hasLattice(key),
10675
+ label: (key) => key
10676
+ },
10677
+ messageReader: {
10678
+ getCurrentMessages: ({ tenantId, assistantId, threadId, workspaceId, projectId }) => agentInstanceManager6.getAgent({
10679
+ tenant_id: tenantId,
10680
+ assistant_id: assistantId,
10681
+ thread_id: threadId,
10682
+ workspace_id: workspaceId,
10683
+ project_id: projectId
10684
+ }).getCurrentMessages()
10685
+ },
10686
+ agentResolver: {
10687
+ getAgent: ({ tenantId, assistantId, threadId, workspaceId, projectId }) => agentInstanceManager6.getAgent({
10688
+ tenant_id: tenantId,
10689
+ assistant_id: assistantId,
10690
+ thread_id: threadId,
10691
+ workspace_id: workspaceId,
10692
+ project_id: projectId
10693
+ })
10694
+ },
10695
+ attachmentRefs: new AgentWebAppAttachmentRefService(
10696
+ resolveAgentWebAppAttachmentSecret(
10697
+ resolveConsoleTokenSecret(process.env.AUTH_REQUIRED === "true")
10698
+ )
10699
+ ),
10700
+ saveProjectFile,
10701
+ authorizePreview: async (webApp, preview) => {
10702
+ if (webApp.tenantId !== preview.tenantId) return false;
10703
+ const assistant = await assistantStore.getAssistantById(preview.tenantId, webApp.assistantId);
10704
+ if (assistant) return !assistant.ownerUserId || assistant.ownerUserId === preview.userId;
10705
+ await agentLatticeManager5.initializeStoredAssistantsForTenant(preview.tenantId);
10706
+ return agentLatticeManager5.hasAgentLatticeWithTenant(preview.tenantId, webApp.assistantId);
10707
+ }
10708
+ })
10709
+ });
9591
10710
  registerA2ABridgeRoutes(app2);
9592
10711
  registerSTTRoutes(app2);
9593
10712
  app2.get("/api/local-a2a", getLocalA2AStatus);
@@ -10031,9 +11150,9 @@ var skillRegistration = {
10031
11150
  };
10032
11151
 
10033
11152
  // src/export_registrations/agent.registration.ts
10034
- import { getStoreLattice as getStoreLattice16 } from "@axiom-lattice/core";
11153
+ import { getStoreLattice as getStoreLattice17 } from "@axiom-lattice/core";
10035
11154
  function getStore3() {
10036
- return getStoreLattice16("default", "assistant").store;
11155
+ return getStoreLattice17("default", "assistant").store;
10037
11156
  }
10038
11157
  var agentRegistration = {
10039
11158
  entityType: "agent",
@@ -10113,9 +11232,9 @@ var agentRegistration = {
10113
11232
  };
10114
11233
 
10115
11234
  // src/export_registrations/database-config.registration.ts
10116
- import { getStoreLattice as getStoreLattice17 } from "@axiom-lattice/core";
11235
+ import { getStoreLattice as getStoreLattice18 } from "@axiom-lattice/core";
10117
11236
  function getStore4() {
10118
- return getStoreLattice17("default", "database").store;
11237
+ return getStoreLattice18("default", "database").store;
10119
11238
  }
10120
11239
  var databaseConfigRegistration = {
10121
11240
  entityType: "database_config",
@@ -10195,9 +11314,9 @@ var databaseConfigRegistration = {
10195
11314
  };
10196
11315
 
10197
11316
  // src/export_registrations/metrics-config.registration.ts
10198
- import { getStoreLattice as getStoreLattice18 } from "@axiom-lattice/core";
11317
+ import { getStoreLattice as getStoreLattice19 } from "@axiom-lattice/core";
10199
11318
  function getStore5() {
10200
- return getStoreLattice18("default", "metrics").store;
11319
+ return getStoreLattice19("default", "metrics").store;
10201
11320
  }
10202
11321
  var metricsConfigRegistration = {
10203
11322
  entityType: "metrics_config",
@@ -10277,9 +11396,9 @@ var metricsConfigRegistration = {
10277
11396
  };
10278
11397
 
10279
11398
  // src/export_registrations/mcp-config.registration.ts
10280
- import { getStoreLattice as getStoreLattice19 } from "@axiom-lattice/core";
11399
+ import { getStoreLattice as getStoreLattice20 } from "@axiom-lattice/core";
10281
11400
  function getStore6() {
10282
- return getStoreLattice19("default", "mcp").store;
11401
+ return getStoreLattice20("default", "mcp").store;
10283
11402
  }
10284
11403
  var mcpConfigRegistration = {
10285
11404
  entityType: "mcp_config",
@@ -10362,9 +11481,9 @@ var mcpConfigRegistration = {
10362
11481
  };
10363
11482
 
10364
11483
  // src/export_registrations/connection.registration.ts
10365
- import { getStoreLattice as getStoreLattice20 } from "@axiom-lattice/core";
11484
+ import { getStoreLattice as getStoreLattice21 } from "@axiom-lattice/core";
10366
11485
  function getStore7() {
10367
- return getStoreLattice20("default", "connection").store;
11486
+ return getStoreLattice21("default", "connection").store;
10368
11487
  }
10369
11488
  async function listAllConnections(tenantId) {
10370
11489
  const store = getStore7();
@@ -10446,9 +11565,9 @@ var connectionRegistration = {
10446
11565
  };
10447
11566
 
10448
11567
  // src/export_registrations/collection.registration.ts
10449
- import { getStoreLattice as getStoreLattice21 } from "@axiom-lattice/core";
11568
+ import { getStoreLattice as getStoreLattice22 } from "@axiom-lattice/core";
10450
11569
  function getStore8() {
10451
- return getStoreLattice21("default", "collection").store;
11570
+ return getStoreLattice22("default", "collection").store;
10452
11571
  }
10453
11572
  var collectionRegistration = {
10454
11573
  entityType: "collection",
@@ -10519,11 +11638,11 @@ var collectionRegistration = {
10519
11638
  };
10520
11639
 
10521
11640
  // src/export_registrations/channel-installation.registration.ts
10522
- import { getStoreLattice as getStoreLattice22 } from "@axiom-lattice/core";
10523
- import { randomUUID as randomUUID9 } from "crypto";
11641
+ import { getStoreLattice as getStoreLattice23 } from "@axiom-lattice/core";
11642
+ import { randomUUID as randomUUID10 } from "crypto";
10524
11643
  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}$/i;
10525
11644
  function getStore9() {
10526
- return getStoreLattice22("default", "channelInstallation").store;
11645
+ return getStoreLattice23("default", "channelInstallation").store;
10527
11646
  }
10528
11647
  var channelInstallationRegistration = {
10529
11648
  entityType: "channel_installation",
@@ -10610,7 +11729,7 @@ var channelInstallationRegistration = {
10610
11729
  }
10611
11730
  id = sourceId;
10612
11731
  } else {
10613
- id = randomUUID9();
11732
+ id = randomUUID10();
10614
11733
  }
10615
11734
  await store.createInstallation(tenantId, id, {
10616
11735
  channel: data.channel,
@@ -10622,9 +11741,9 @@ var channelInstallationRegistration = {
10622
11741
  };
10623
11742
 
10624
11743
  // src/export_registrations/menu.registration.ts
10625
- import { getStoreLattice as getStoreLattice23 } from "@axiom-lattice/core";
11744
+ import { getStoreLattice as getStoreLattice24 } from "@axiom-lattice/core";
10626
11745
  function getStore10() {
10627
- return getStoreLattice23("default", "menu").store;
11746
+ return getStoreLattice24("default", "menu").store;
10628
11747
  }
10629
11748
  var menuRegistration = {
10630
11749
  entityType: "menu",
@@ -10702,9 +11821,9 @@ var menuRegistration = {
10702
11821
  };
10703
11822
 
10704
11823
  // src/export_registrations/eval.registration.ts
10705
- import { getStoreLattice as getStoreLattice24 } from "@axiom-lattice/core";
11824
+ import { getStoreLattice as getStoreLattice25 } from "@axiom-lattice/core";
10706
11825
  function getStore11() {
10707
- return getStoreLattice24("default", "eval").store;
11826
+ return getStoreLattice25("default", "eval").store;
10708
11827
  }
10709
11828
  var evalRegistration = {
10710
11829
  entityType: "eval",
@@ -11045,10 +12164,10 @@ function registerAllBuiltinEntities() {
11045
12164
 
11046
12165
  // src/router/MessageRouter.ts
11047
12166
  import {
11048
- getStoreLattice as getStoreLattice25,
11049
- agentInstanceManager as agentInstanceManager6
12167
+ getStoreLattice as getStoreLattice26,
12168
+ agentInstanceManager as agentInstanceManager7
11050
12169
  } from "@axiom-lattice/core";
11051
- import { randomUUID as randomUUID10 } from "crypto";
12170
+ import { randomUUID as randomUUID11 } from "crypto";
11052
12171
  var BindingNotFoundError = class extends Error {
11053
12172
  constructor(message) {
11054
12173
  super(message);
@@ -11217,7 +12336,7 @@ var MessageRouter = class {
11217
12336
  channel: message.channel,
11218
12337
  adapterChannel: adapter.channel
11219
12338
  }, "Thread resolved by adapter strategy");
11220
- const threadStore = getStoreLattice25("default", "thread").store;
12339
+ const threadStore = getStoreLattice26("default", "thread").store;
11221
12340
  try {
11222
12341
  await threadStore.createThread(
11223
12342
  tenantId,
@@ -11254,8 +12373,8 @@ var MessageRouter = class {
11254
12373
  }
11255
12374
  }
11256
12375
  if (!threadId) {
11257
- const threadStore = getStoreLattice25("default", "thread").store;
11258
- const newThreadId = randomUUID10();
12376
+ const threadStore = getStoreLattice26("default", "thread").store;
12377
+ const newThreadId = randomUUID11();
11259
12378
  console.log({
11260
12379
  event: "dispatch:thread:create",
11261
12380
  agentId,
@@ -11294,7 +12413,7 @@ var MessageRouter = class {
11294
12413
  senderId: message.sender.id,
11295
12414
  contentLength: message.content.text.length
11296
12415
  }, "Dispatching to agent");
11297
- const agent = agentInstanceManager6.getAgent({
12416
+ const agent = agentInstanceManager7.getAgent({
11298
12417
  tenant_id: tenantId,
11299
12418
  assistant_id: agentId,
11300
12419
  thread_id: threadId,
@@ -11598,7 +12717,7 @@ var configureSwagger = async (app2, customSwaggerConfig, customSwaggerUiConfig)
11598
12717
  };
11599
12718
 
11600
12719
  // src/services/agent_task_consumer.ts
11601
- import { eventBus as eventBus2, AGENT_TASK_EVENT, agentInstanceManager as agentInstanceManager7, QueueMode as QueueMode2 } from "@axiom-lattice/core";
12720
+ import { eventBus as eventBus3, AGENT_TASK_EVENT, agentInstanceManager as agentInstanceManager8, QueueMode as QueueMode2 } from "@axiom-lattice/core";
11602
12721
  var handleAgentTask = async (taskRequest, retryCount = 0) => {
11603
12722
  const {
11604
12723
  assistant_id,
@@ -11616,17 +12735,17 @@ var handleAgentTask = async (taskRequest, retryCount = 0) => {
11616
12735
  console.log(
11617
12736
  `\u5F00\u59CB\u5904\u7406\u4EFB\u52A1 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
11618
12737
  );
11619
- const agent = agentInstanceManager7.getAgent({ assistant_id, thread_id, tenant_id, workspace_id: runConfig?.workspaceId, project_id: runConfig?.projectId, custom_run_config: runConfig });
12738
+ const agent = agentInstanceManager8.getAgent({ assistant_id, thread_id, tenant_id, workspace_id: runConfig?.workspaceId, project_id: runConfig?.projectId, custom_run_config: runConfig });
11620
12739
  await agent.addMessage({ input, command, custom_run_config: runConfig }, QueueMode2.STEER);
11621
12740
  if (callback_event) {
11622
12741
  agent.subscribeOnce("message:completed", (evt) => {
11623
- eventBus2.publish(callback_event, {
12742
+ eventBus3.publish(callback_event, {
11624
12743
  success: true,
11625
12744
  state: evt.state
11626
12745
  });
11627
12746
  if (main_thread_id && main_tenant_id) {
11628
12747
  try {
11629
- const mainAgent = agentInstanceManager7.getAgent({
12748
+ const mainAgent = agentInstanceManager8.getAgent({
11630
12749
  assistant_id: main_assistant_id ?? assistant_id,
11631
12750
  thread_id: main_thread_id,
11632
12751
  tenant_id: main_tenant_id,
@@ -11656,7 +12775,7 @@ ${summary}`
11656
12775
  }
11657
12776
  });
11658
12777
  agent.subscribeOnce("message:interrupted", (evt) => {
11659
- eventBus2.publish(callback_event, {
12778
+ eventBus3.publish(callback_event, {
11660
12779
  success: true,
11661
12780
  state: evt.state
11662
12781
  });
@@ -11679,7 +12798,7 @@ ${summary}`
11679
12798
  return handleAgentTask(taskRequest, nextRetryCount);
11680
12799
  }
11681
12800
  if (callback_event) {
11682
- eventBus2.publish(callback_event, {
12801
+ eventBus3.publish(callback_event, {
11683
12802
  success: false,
11684
12803
  error: error instanceof Error ? error.message : String(error)
11685
12804
  });
@@ -11716,7 +12835,7 @@ var _AgentTaskConsumer = class _AgentTaskConsumer {
11716
12835
  * 初始化事件监听和队列轮询
11717
12836
  */
11718
12837
  initialize() {
11719
- eventBus2.subscribe(AGENT_TASK_EVENT, this.trigger_agent_task.bind(this));
12838
+ eventBus3.subscribe(AGENT_TASK_EVENT, this.trigger_agent_task.bind(this));
11720
12839
  this.startPollingQueue();
11721
12840
  console.log("Agent\u4EFB\u52A1\u6D88\u8D39\u8005\u5DF2\u542F\u52A8\u5E76\u76D1\u542C\u4EFB\u52A1\u4E8B\u4EF6\u548C\u961F\u5217");
11722
12841
  }
@@ -11835,7 +12954,7 @@ var _AgentTaskConsumer = class _AgentTaskConsumer {
11835
12954
  handleAgentTask(taskRequest).catch((error) => {
11836
12955
  console.error("\u5904\u7406Agent\u4EFB\u52A1\u65F6\u53D1\u751F\u672A\u6355\u83B7\u7684\u9519\u8BEF:", error);
11837
12956
  if (taskRequest.callback_event) {
11838
- eventBus2.publish(taskRequest.callback_event, {
12957
+ eventBus3.publish(taskRequest.callback_event, {
11839
12958
  success: false,
11840
12959
  error: error instanceof Error ? error.message : String(error)
11841
12960
  });
@@ -11855,8 +12974,8 @@ import {
11855
12974
  getLoggerLattice,
11856
12975
  loggerLatticeManager,
11857
12976
  sandboxLatticeManager as sandboxLatticeManager2,
11858
- getStoreLattice as getStoreLattice26,
11859
- agentInstanceManager as agentInstanceManager8,
12977
+ getStoreLattice as getStoreLattice27,
12978
+ agentInstanceManager as agentInstanceManager9,
11860
12979
  createSandboxProvider,
11861
12980
  TokenCache,
11862
12981
  mcpManager
@@ -11990,7 +13109,7 @@ function getConfiguredSandboxProvider() {
11990
13109
  }
11991
13110
  async function restoreMcpConnections() {
11992
13111
  try {
11993
- const storeLattice = getStoreLattice26("default", "mcp");
13112
+ const storeLattice = getStoreLattice27("default", "mcp");
11994
13113
  const store = storeLattice.store;
11995
13114
  if (!store) {
11996
13115
  logger4.info("MCP store not configured, skipping connection restoration");
@@ -12064,7 +13183,7 @@ var start = async (config) => {
12064
13183
  }
12065
13184
  setEvalRunService(evalRunner);
12066
13185
  try {
12067
- const menuStore = getStoreLattice26("default", "menu").store;
13186
+ const menuStore = getStoreLattice27("default", "menu").store;
12068
13187
  setMenuRegistry(menuStore);
12069
13188
  logger4.info("Menu registry initialized");
12070
13189
  } catch {
@@ -12076,12 +13195,12 @@ var start = async (config) => {
12076
13195
  registerLatticeRoutes(app, channelDeps);
12077
13196
  try {
12078
13197
  const { A2AAuthService, parseEnvKeys } = await import("./A2AAuthService-DK5X6VIL.mjs");
12079
- const { registerA2AStandardRoutes } = await import("./a2a-standard-U2XYG45L.mjs");
13198
+ const { registerA2AStandardRoutes } = await import("./a2a-standard-VYQCHMBV.mjs");
12080
13199
  const a2a = await import("./a2a-7N3WDAOP.mjs");
12081
- const a2aKeyStore = getStoreLattice26("default", "a2aApiKey").store;
12082
- const assistantStore = getStoreLattice26("default", "assistant").store;
12083
- const projectStore = getStoreLattice26("default", "project").store;
12084
- const taskStore = getStoreLattice26("default", "task").store;
13200
+ const a2aKeyStore = getStoreLattice27("default", "a2aApiKey").store;
13201
+ const assistantStore = getStoreLattice27("default", "assistant").store;
13202
+ const projectStore = getStoreLattice27("default", "project").store;
13203
+ const taskStore = getStoreLattice27("default", "task").store;
12085
13204
  const authService = new A2AAuthService({
12086
13205
  keyStore: a2aKeyStore,
12087
13206
  assistantStore,
@@ -12111,7 +13230,7 @@ var start = async (config) => {
12111
13230
  }
12112
13231
  try {
12113
13232
  const { ResourceController } = await import("./resources-VA7LSDKN.mjs");
12114
- const sharedResourceStore = getStoreLattice26("default", "sharedResource").store;
13233
+ const sharedResourceStore = getStoreLattice27("default", "sharedResource").store;
12115
13234
  const cache = new TokenCache();
12116
13235
  const resourceController = new ResourceController({
12117
13236
  store: sharedResourceStore,
@@ -12151,7 +13270,7 @@ var start = async (config) => {
12151
13270
  }
12152
13271
  }
12153
13272
  if (process.env.AXIOM_RESTORE_PENDING_ON_STARTUP === "true") {
12154
- agentInstanceManager8.restore().then((stats) => {
13273
+ agentInstanceManager9.restore().then((stats) => {
12155
13274
  logger4.info(`Agent recovery complete: ${stats.restored} threads restored, ${stats.errors} errors`);
12156
13275
  }).catch((error) => {
12157
13276
  logger4.error("Agent recovery failed", { error });