@axiom-lattice/gateway 4.1.6 → 4.1.7

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.js CHANGED
@@ -1512,7 +1512,7 @@ function extractFailureReason(task) {
1512
1512
  return extractText2(task.status.message.parts);
1513
1513
  }
1514
1514
  function observationEventKey(task, result, failureReason) {
1515
- const digest = (0, import_node_crypto19.createHash)("sha256").update(JSON.stringify({
1515
+ const digest = (0, import_node_crypto21.createHash)("sha256").update(JSON.stringify({
1516
1516
  state: task.status.state,
1517
1517
  timestamp: task.status.timestamp,
1518
1518
  message: task.status.message,
@@ -1522,7 +1522,7 @@ function observationEventKey(task, result, failureReason) {
1522
1522
  return `a2a-status-observation:${task.id}:${digest}`;
1523
1523
  }
1524
1524
  function snapshotEventKey(task) {
1525
- const digest = (0, import_node_crypto19.createHash)("sha256").update(JSON.stringify(task)).digest("hex");
1525
+ const digest = (0, import_node_crypto21.createHash)("sha256").update(JSON.stringify(task)).digest("hex");
1526
1526
  return `a2a-task-snapshot:${task.id}:${digest}`;
1527
1527
  }
1528
1528
  function isGovernedObservation(state) {
@@ -1534,11 +1534,11 @@ function isA2ATaskSnapshot(value) {
1534
1534
  function snapshotSequence(value) {
1535
1535
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
1536
1536
  }
1537
- var import_node_crypto19, import_node_util, TASK_STATUS_TO_A2A_STATE, TITLE_MAX_LENGTH, A2ATaskScopeConflictError, A2ATaskStoreAdapter;
1537
+ var import_node_crypto21, import_node_util, TASK_STATUS_TO_A2A_STATE, TITLE_MAX_LENGTH, A2ATaskScopeConflictError, A2ATaskStoreAdapter;
1538
1538
  var init_A2ATaskStoreAdapter = __esm({
1539
1539
  "src/services/a2a/A2ATaskStoreAdapter.ts"() {
1540
1540
  "use strict";
1541
- import_node_crypto19 = require("crypto");
1541
+ import_node_crypto21 = require("crypto");
1542
1542
  import_node_util = require("util");
1543
1543
  TASK_STATUS_TO_A2A_STATE = {
1544
1544
  pending: "submitted",
@@ -1999,7 +1999,7 @@ ${formatFileRefsSection(inputFiles)}` : text;
1999
1999
  const messageText = [
2000
2000
  `A2A governed task instruction: get and update the existing TaskItem ${ctx.taskId}; do not create a replacement task.`,
2001
2001
  "Before doing the requested work, initialize its canonical description through manage_task with ## Objective, ## Acceptance Criteria, and ## Belief State sections.",
2002
- "Keep lifecycle status truthful. Complete only through manage_task update with a nonblank result and structured beliefImpact; use interrupted, failed, or cancelled when appropriate.",
2002
+ "Keep lifecycle status truthful. Change status only through manage_task set_status; complete with a nonblank result and structured beliefImpact, and use interrupted, failed, or cancelled when appropriate. Use manage_task update for content fields only.",
2003
2003
  "",
2004
2004
  requestText
2005
2005
  ].join("\n");
@@ -3313,7 +3313,7 @@ var import_static = __toESM(require("@fastify/static"));
3313
3313
  var import_path = __toESM(require("path"));
3314
3314
 
3315
3315
  // src/routes/index.ts
3316
- var import_node_crypto18 = require("crypto");
3316
+ var import_node_crypto20 = require("crypto");
3317
3317
  var import_protocols24 = require("@axiom-lattice/protocols");
3318
3318
 
3319
3319
  // src/controllers/assistant.ts
@@ -9688,7 +9688,195 @@ function registerWorkspaceRoutes(app2, projectCollaboration) {
9688
9688
  "/api/filesystem/browse",
9689
9689
  controller.browseFilesystem.bind(controller)
9690
9690
  );
9691
+ return controller;
9692
+ }
9693
+
9694
+ // src/services/collaboration/ProjectAccessService.ts
9695
+ var import_node_crypto3 = require("crypto");
9696
+ var ProjectCollaborationError = class extends Error {
9697
+ /** Creates a project collaboration error. */
9698
+ constructor(code, statusCode, message) {
9699
+ super(message);
9700
+ this.code = code;
9701
+ this.statusCode = statusCode;
9702
+ this.name = "ProjectCollaborationError";
9703
+ }
9704
+ };
9705
+ var allowedRoles = {
9706
+ read: /* @__PURE__ */ new Set(["owner", "admin", "member", "viewer"]),
9707
+ send: /* @__PURE__ */ new Set(["owner", "admin", "member"]),
9708
+ manage: /* @__PURE__ */ new Set(["owner", "admin"])
9709
+ };
9710
+ var membershipConflictNames = /* @__PURE__ */ new Set([
9711
+ "DuplicateProjectMembershipError",
9712
+ "ProjectMembershipIdConflictError"
9713
+ ]);
9714
+ function isProjectMembershipConflict(error) {
9715
+ return error instanceof Error && membershipConflictNames.has(error.name);
9691
9716
  }
9717
+ var ProjectAccessService = class {
9718
+ /** Creates a project access service from explicitly injected stores. */
9719
+ constructor(deps) {
9720
+ this.deps = deps;
9721
+ this.idFactory = deps.idFactory ?? import_node_crypto3.randomUUID;
9722
+ }
9723
+ /** Returns the project and active membership when the user has the requested capability. */
9724
+ async requireProjectAccess(input) {
9725
+ const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
9726
+ if (!project) {
9727
+ throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
9728
+ }
9729
+ if (project.kind === "public") {
9730
+ return this.requirePublicProjectAccess(project, input);
9731
+ }
9732
+ const membership = await this.deps.membershipStore.findByUser(
9733
+ input.tenantId,
9734
+ input.projectId,
9735
+ input.userId
9736
+ );
9737
+ if (membership?.status === "active") {
9738
+ this.requireCapability(membership, input.capability);
9739
+ return { project, membership };
9740
+ }
9741
+ const memberships = await this.deps.membershipStore.list(input.tenantId, input.projectId);
9742
+ if (memberships.length !== 0) this.accessDenied();
9743
+ if (project.kind === "personal") this.accessDenied();
9744
+ const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
9745
+ if (tenantLink?.role !== "owner" && tenantLink?.role !== "admin") this.accessDenied();
9746
+ let claim;
9747
+ try {
9748
+ claim = await this.deps.membershipStore.createInitialOwner({
9749
+ id: this.idFactory(),
9750
+ tenantId: input.tenantId,
9751
+ projectId: input.projectId,
9752
+ userId: input.userId
9753
+ });
9754
+ } catch (error) {
9755
+ if (isProjectMembershipConflict(error)) {
9756
+ throw new ProjectCollaborationError(
9757
+ "PROJECT_MEMBERSHIP_CONFLICT",
9758
+ 409,
9759
+ "Project membership could not be initialized"
9760
+ );
9761
+ }
9762
+ throw error;
9763
+ }
9764
+ if (claim.kind === "created" || claim.kind === "existing") {
9765
+ return { project, membership: claim.membership };
9766
+ }
9767
+ const racedMembership = await this.deps.membershipStore.findByUser(
9768
+ input.tenantId,
9769
+ input.projectId,
9770
+ input.userId
9771
+ );
9772
+ if (racedMembership?.status !== "active") this.accessDenied();
9773
+ this.requireCapability(racedMembership, input.capability);
9774
+ return { project, membership: racedMembership };
9775
+ }
9776
+ /** Authorizes an already-existing membership, or any tenant member for public rooms; never mutates persistence. */
9777
+ async requireExistingProjectAccess(input) {
9778
+ const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
9779
+ if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
9780
+ if (project.kind === "public") {
9781
+ return this.requireExistingPublicProjectAccess(project, input);
9782
+ }
9783
+ const membership = await this.deps.membershipStore.findByUser(
9784
+ input.tenantId,
9785
+ input.projectId,
9786
+ input.userId
9787
+ );
9788
+ if (!membership || membership.tenantId !== input.tenantId || membership.projectId !== input.projectId || membership.userId !== input.userId || membership.status !== "active") this.accessDenied();
9789
+ this.requireCapability(membership, input.capability);
9790
+ return { project, membership };
9791
+ }
9792
+ /** Authorizes roster visibility: read for public rooms, manage elsewhere. */
9793
+ async requireRosterAccess(input) {
9794
+ const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
9795
+ if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
9796
+ return this.requireProjectAccess({
9797
+ ...input,
9798
+ capability: project.kind === "public" ? "read" : "manage"
9799
+ });
9800
+ }
9801
+ async requirePublicProjectAccess(project, input) {
9802
+ const membership = await this.deps.membershipStore.findByUser(
9803
+ input.tenantId,
9804
+ input.projectId,
9805
+ input.userId
9806
+ );
9807
+ if (membership?.status === "active") {
9808
+ this.requireCapability(membership, input.capability);
9809
+ return { project, membership };
9810
+ }
9811
+ const materialized = await this.materializePublicMembership(input);
9812
+ this.requireCapability(materialized, input.capability);
9813
+ return { project, membership: materialized };
9814
+ }
9815
+ async requireExistingPublicProjectAccess(project, input) {
9816
+ const membership = await this.deps.membershipStore.findByUser(
9817
+ input.tenantId,
9818
+ input.projectId,
9819
+ input.userId
9820
+ );
9821
+ if (membership?.status === "active") {
9822
+ this.requireCapability(membership, input.capability);
9823
+ return { project, membership };
9824
+ }
9825
+ if (membership) this.accessDenied();
9826
+ const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
9827
+ if (!tenantLink) this.accessDenied();
9828
+ const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
9829
+ const synthetic = {
9830
+ id: `public:${input.userId}`,
9831
+ tenantId: input.tenantId,
9832
+ projectId: input.projectId,
9833
+ userId: input.userId,
9834
+ role,
9835
+ status: "active",
9836
+ joinedAt: project.createdAt,
9837
+ updatedAt: project.updatedAt
9838
+ };
9839
+ this.requireCapability(synthetic, input.capability);
9840
+ return { project, membership: synthetic };
9841
+ }
9842
+ async materializePublicMembership(input) {
9843
+ const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
9844
+ if (!tenantLink) this.accessDenied();
9845
+ const existing = await this.deps.membershipStore.findByUser(
9846
+ input.tenantId,
9847
+ input.projectId,
9848
+ input.userId
9849
+ );
9850
+ if (existing) this.accessDenied();
9851
+ const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
9852
+ try {
9853
+ return await this.deps.membershipStore.create({
9854
+ id: this.idFactory(),
9855
+ tenantId: input.tenantId,
9856
+ projectId: input.projectId,
9857
+ userId: input.userId,
9858
+ role,
9859
+ status: "active"
9860
+ });
9861
+ } catch (error) {
9862
+ if (isProjectMembershipConflict(error)) {
9863
+ const raced = await this.deps.membershipStore.findByUser(
9864
+ input.tenantId,
9865
+ input.projectId,
9866
+ input.userId
9867
+ );
9868
+ if (raced?.status === "active") return raced;
9869
+ }
9870
+ throw error;
9871
+ }
9872
+ }
9873
+ requireCapability(membership, capability2) {
9874
+ if (!allowedRoles[capability2].has(membership.role)) this.accessDenied();
9875
+ }
9876
+ accessDenied() {
9877
+ throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project access denied");
9878
+ }
9879
+ };
9692
9880
 
9693
9881
  // src/controllers/database-configs.ts
9694
9882
  var import_core27 = require("@axiom-lattice/core");
@@ -11745,7 +11933,7 @@ function registerTenantRoutes(app2) {
11745
11933
  // src/controllers/auth.ts
11746
11934
  var import_core35 = require("@axiom-lattice/core");
11747
11935
  var import_uuid8 = require("uuid");
11748
- var import_node_crypto3 = require("crypto");
11936
+ var import_node_crypto4 = require("crypto");
11749
11937
  var defaultAuthConfig = {
11750
11938
  autoApproveUsers: true,
11751
11939
  allowTenantRegistration: true,
@@ -12100,7 +12288,7 @@ var AuthController = class {
12100
12288
  function resolveConsoleTokenSecret(_authRequired) {
12101
12289
  const configured = process.env.AUTH_TOKEN_SECRET || process.env.JWT_SECRET;
12102
12290
  if (configured) return configured;
12103
- devSessionSecret ?? (devSessionSecret = (0, import_node_crypto3.randomBytes)(32).toString("hex"));
12291
+ devSessionSecret ?? (devSessionSecret = (0, import_node_crypto4.randomBytes)(32).toString("hex"));
12104
12292
  console.warn(
12105
12293
  "[auth] AUTH_TOKEN_SECRET/JWT_SECRET is not set; using an in-process generated signing secret (sessions are invalidated on restart). Set AUTH_TOKEN_SECRET for production."
12106
12294
  );
@@ -12109,7 +12297,7 @@ function resolveConsoleTokenSecret(_authRequired) {
12109
12297
  var devSessionSecret;
12110
12298
  function createConsoleSessionToken(payload, secret) {
12111
12299
  const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
12112
- const signature = (0, import_node_crypto3.createHmac)("sha256", secret).update(encodedPayload).digest("base64url");
12300
+ const signature = (0, import_node_crypto4.createHmac)("sha256", secret).update(encodedPayload).digest("base64url");
12113
12301
  return `${encodedPayload}.${signature}`;
12114
12302
  }
12115
12303
  function isConsoleSessionPayload(value) {
@@ -12136,8 +12324,8 @@ function verifySignedConsoleSession(authHeader, secret) {
12136
12324
  try {
12137
12325
  const actualSignature = Buffer.from(encodedSignature, "base64url");
12138
12326
  if (actualSignature.toString("base64url") !== encodedSignature) return void 0;
12139
- const expectedSignature = (0, import_node_crypto3.createHmac)("sha256", secret).update(encodedPayload).digest();
12140
- if (actualSignature.length !== expectedSignature.length || !(0, import_node_crypto3.timingSafeEqual)(actualSignature, expectedSignature)) {
12327
+ const expectedSignature = (0, import_node_crypto4.createHmac)("sha256", secret).update(encodedPayload).digest();
12328
+ if (actualSignature.length !== expectedSignature.length || !(0, import_node_crypto4.timingSafeEqual)(actualSignature, expectedSignature)) {
12141
12329
  return void 0;
12142
12330
  }
12143
12331
  const payload = JSON.parse(
@@ -13718,7 +13906,7 @@ async function getLocalA2ATemplates(_request, reply) {
13718
13906
  }
13719
13907
 
13720
13908
  // src/routes/a2a-test-connection.ts
13721
- var import_node_crypto4 = require("crypto");
13909
+ var import_node_crypto5 = require("crypto");
13722
13910
  function describeTestConnectionError(error, agentCardUrl) {
13723
13911
  if (!(error instanceof Error)) {
13724
13912
  return `Failed to fetch agent card from ${agentCardUrl}: ${String(error)}`;
@@ -13758,7 +13946,7 @@ async function ensureLocalRuntimeForConnectionTest(localRuntime, agentCardUrl) {
13758
13946
  return { error: portValidation.error };
13759
13947
  }
13760
13948
  const state = await localA2AService.startProvider(
13761
- `test-${(0, import_node_crypto4.randomUUID)()}`,
13949
+ `test-${(0, import_node_crypto5.randomUUID)()}`,
13762
13950
  testRuntime
13763
13951
  );
13764
13952
  if (state.status === "running") return { runtimeId: state.runtimeId };
@@ -13835,233 +14023,46 @@ var import_core51 = require("@axiom-lattice/core");
13835
14023
  var import_protocols10 = require("@axiom-lattice/protocols");
13836
14024
  var import_core42 = require("@axiom-lattice/core");
13837
14025
 
13838
- // src/services/collaboration/ProjectAccessService.ts
13839
- var import_node_crypto5 = require("crypto");
13840
- var ProjectCollaborationError = class extends Error {
13841
- /** Creates a project collaboration error. */
13842
- constructor(code, statusCode, message) {
13843
- super(message);
13844
- this.code = code;
13845
- this.statusCode = statusCode;
13846
- this.name = "ProjectCollaborationError";
13847
- }
13848
- };
13849
- var allowedRoles = {
13850
- read: /* @__PURE__ */ new Set(["owner", "admin", "member", "viewer"]),
13851
- send: /* @__PURE__ */ new Set(["owner", "admin", "member"]),
13852
- manage: /* @__PURE__ */ new Set(["owner", "admin"])
13853
- };
13854
- var membershipConflictNames = /* @__PURE__ */ new Set([
13855
- "DuplicateProjectMembershipError",
13856
- "ProjectMembershipIdConflictError"
13857
- ]);
13858
- function isProjectMembershipConflict(error) {
13859
- return error instanceof Error && membershipConflictNames.has(error.name);
13860
- }
13861
- var ProjectAccessService = class {
13862
- /** Creates a project access service from explicitly injected stores. */
13863
- constructor(deps) {
13864
- this.deps = deps;
13865
- this.idFactory = deps.idFactory ?? import_node_crypto5.randomUUID;
13866
- }
13867
- /** Returns the project and active membership when the user has the requested capability. */
13868
- async requireProjectAccess(input) {
13869
- const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
13870
- if (!project) {
13871
- throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
13872
- }
13873
- if (project.kind === "public") {
13874
- return this.requirePublicProjectAccess(project, input);
13875
- }
13876
- const membership = await this.deps.membershipStore.findByUser(
13877
- input.tenantId,
13878
- input.projectId,
13879
- input.userId
13880
- );
13881
- if (membership?.status === "active") {
13882
- this.requireCapability(membership, input.capability);
13883
- return { project, membership };
13884
- }
13885
- const memberships = await this.deps.membershipStore.list(input.tenantId, input.projectId);
13886
- if (memberships.length !== 0) this.accessDenied();
13887
- if (project.kind === "personal") this.accessDenied();
13888
- const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
13889
- if (tenantLink?.role !== "owner" && tenantLink?.role !== "admin") this.accessDenied();
13890
- let claim;
13891
- try {
13892
- claim = await this.deps.membershipStore.createInitialOwner({
13893
- id: this.idFactory(),
13894
- tenantId: input.tenantId,
13895
- projectId: input.projectId,
13896
- userId: input.userId
13897
- });
13898
- } catch (error) {
13899
- if (isProjectMembershipConflict(error)) {
13900
- throw new ProjectCollaborationError(
13901
- "PROJECT_MEMBERSHIP_CONFLICT",
13902
- 409,
13903
- "Project membership could not be initialized"
13904
- );
13905
- }
13906
- throw error;
13907
- }
13908
- if (claim.kind === "created" || claim.kind === "existing") {
13909
- return { project, membership: claim.membership };
13910
- }
13911
- const racedMembership = await this.deps.membershipStore.findByUser(
13912
- input.tenantId,
13913
- input.projectId,
13914
- input.userId
13915
- );
13916
- if (racedMembership?.status !== "active") this.accessDenied();
13917
- this.requireCapability(racedMembership, input.capability);
13918
- return { project, membership: racedMembership };
13919
- }
13920
- /** Authorizes an already-existing membership, or any tenant member for public rooms; never mutates persistence. */
13921
- async requireExistingProjectAccess(input) {
13922
- const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
13923
- if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
13924
- if (project.kind === "public") {
13925
- return this.requireExistingPublicProjectAccess(project, input);
13926
- }
13927
- const membership = await this.deps.membershipStore.findByUser(
13928
- input.tenantId,
13929
- input.projectId,
13930
- input.userId
13931
- );
13932
- if (!membership || membership.tenantId !== input.tenantId || membership.projectId !== input.projectId || membership.userId !== input.userId || membership.status !== "active") this.accessDenied();
13933
- this.requireCapability(membership, input.capability);
13934
- return { project, membership };
13935
- }
13936
- /** Authorizes roster visibility: read for public rooms, manage elsewhere. */
13937
- async requireRosterAccess(input) {
13938
- const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
13939
- if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
13940
- return this.requireProjectAccess({
13941
- ...input,
13942
- capability: project.kind === "public" ? "read" : "manage"
13943
- });
13944
- }
13945
- async requirePublicProjectAccess(project, input) {
13946
- const membership = await this.deps.membershipStore.findByUser(
13947
- input.tenantId,
13948
- input.projectId,
13949
- input.userId
13950
- );
13951
- if (membership?.status === "active") {
13952
- this.requireCapability(membership, input.capability);
13953
- return { project, membership };
13954
- }
13955
- const materialized = await this.materializePublicMembership(input);
13956
- this.requireCapability(materialized, input.capability);
13957
- return { project, membership: materialized };
13958
- }
13959
- async requireExistingPublicProjectAccess(project, input) {
13960
- const membership = await this.deps.membershipStore.findByUser(
13961
- input.tenantId,
13962
- input.projectId,
13963
- input.userId
13964
- );
13965
- if (membership?.status === "active") {
13966
- this.requireCapability(membership, input.capability);
13967
- return { project, membership };
13968
- }
13969
- if (membership) this.accessDenied();
13970
- const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
13971
- if (!tenantLink) this.accessDenied();
13972
- const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
13973
- const synthetic = {
13974
- id: `public:${input.userId}`,
13975
- tenantId: input.tenantId,
13976
- projectId: input.projectId,
13977
- userId: input.userId,
13978
- role,
13979
- status: "active",
13980
- joinedAt: project.createdAt,
13981
- updatedAt: project.updatedAt
13982
- };
13983
- this.requireCapability(synthetic, input.capability);
13984
- return { project, membership: synthetic };
13985
- }
13986
- async materializePublicMembership(input) {
13987
- const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
13988
- if (!tenantLink) this.accessDenied();
13989
- const existing = await this.deps.membershipStore.findByUser(
13990
- input.tenantId,
13991
- input.projectId,
13992
- input.userId
13993
- );
13994
- if (existing) this.accessDenied();
13995
- const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
13996
- try {
13997
- return await this.deps.membershipStore.create({
13998
- id: this.idFactory(),
13999
- tenantId: input.tenantId,
14000
- projectId: input.projectId,
14001
- userId: input.userId,
14002
- role,
14003
- status: "active"
14004
- });
14005
- } catch (error) {
14006
- if (isProjectMembershipConflict(error)) {
14007
- const raced = await this.deps.membershipStore.findByUser(
14008
- input.tenantId,
14009
- input.projectId,
14010
- input.userId
14011
- );
14012
- if (raced?.status === "active") return raced;
14013
- }
14014
- throw error;
14015
- }
14016
- }
14017
- requireCapability(membership, capability2) {
14018
- if (!allowedRoles[capability2].has(membership.role)) this.accessDenied();
14019
- }
14020
- accessDenied() {
14021
- throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project access denied");
14022
- }
14023
- };
14024
-
14025
- // src/services/collaboration/ProjectTaskThreadService.ts
14026
- var import_core40 = require("@axiom-lattice/core");
14027
- var import_protocols9 = require("@axiom-lattice/protocols");
14028
-
14029
- // src/services/collaboration/ProjectTaskSnapshots.ts
14030
- var import_protocols8 = require("@axiom-lattice/protocols");
14031
- function snapshotJsonValue(value, stack = /* @__PURE__ */ new WeakSet()) {
14032
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
14033
- if (typeof value === "number" && Number.isFinite(value)) return value;
14034
- if (typeof value !== "object") throw new Error("Invalid JSON value");
14035
- if (stack.has(value)) throw new Error("Cyclic JSON value");
14036
- stack.add(value);
14037
- try {
14038
- const descriptors = Object.getOwnPropertyDescriptors(value);
14039
- const keys2 = Reflect.ownKeys(value);
14040
- if (Reflect.ownKeys(descriptors).length !== keys2.length || keys2.some((key) => !Reflect.ownKeys(descriptors).includes(key))) throw new Error("Invalid JSON descriptors");
14041
- if (Array.isArray(value)) {
14042
- const lengthDescriptor = descriptors.length;
14043
- if (!lengthDescriptor || !("value" in lengthDescriptor) || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0 || lengthDescriptor.enumerable || lengthDescriptor.configurable || !lengthDescriptor.writable || keys2.length !== lengthDescriptor.value + 1) throw new Error("Invalid JSON array");
14044
- const result2 = [];
14045
- for (let index = 0; index < lengthDescriptor.value; index += 1) {
14046
- const descriptor = descriptors[String(index)];
14047
- if (!descriptor || !descriptor.enumerable || !("value" in descriptor)) throw new Error("Invalid JSON array entry");
14048
- result2.push(snapshotJsonValue(descriptor.value, stack));
14049
- }
14050
- if (keys2.some((key) => typeof key !== "string" || key !== "length" && !/^(0|[1-9][0-9]*)$/.test(key))) {
14051
- throw new Error("Extra JSON array key");
14052
- }
14053
- return result2;
14054
- }
14055
- const result = {};
14056
- for (const key of keys2) {
14057
- if (typeof key !== "string") throw new Error("Symbol JSON key");
14058
- const descriptor = descriptors[key];
14059
- if (!descriptor.enumerable || !("value" in descriptor)) throw new Error("Accessor JSON key");
14060
- result[key] = snapshotJsonValue(descriptor.value, stack);
14061
- }
14062
- return result;
14063
- } finally {
14064
- stack.delete(value);
14026
+ // src/services/collaboration/ProjectTaskThreadService.ts
14027
+ var import_core40 = require("@axiom-lattice/core");
14028
+ var import_protocols9 = require("@axiom-lattice/protocols");
14029
+
14030
+ // src/services/collaboration/ProjectTaskSnapshots.ts
14031
+ var import_protocols8 = require("@axiom-lattice/protocols");
14032
+ function snapshotJsonValue(value, stack = /* @__PURE__ */ new WeakSet()) {
14033
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
14034
+ if (typeof value === "number" && Number.isFinite(value)) return value;
14035
+ if (typeof value !== "object") throw new Error("Invalid JSON value");
14036
+ if (stack.has(value)) throw new Error("Cyclic JSON value");
14037
+ stack.add(value);
14038
+ try {
14039
+ const descriptors = Object.getOwnPropertyDescriptors(value);
14040
+ const keys2 = Reflect.ownKeys(value);
14041
+ if (Reflect.ownKeys(descriptors).length !== keys2.length || keys2.some((key) => !Reflect.ownKeys(descriptors).includes(key))) throw new Error("Invalid JSON descriptors");
14042
+ if (Array.isArray(value)) {
14043
+ const lengthDescriptor = descriptors.length;
14044
+ if (!lengthDescriptor || !("value" in lengthDescriptor) || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0 || lengthDescriptor.enumerable || lengthDescriptor.configurable || !lengthDescriptor.writable || keys2.length !== lengthDescriptor.value + 1) throw new Error("Invalid JSON array");
14045
+ const result2 = [];
14046
+ for (let index = 0; index < lengthDescriptor.value; index += 1) {
14047
+ const descriptor = descriptors[String(index)];
14048
+ if (!descriptor || !descriptor.enumerable || !("value" in descriptor)) throw new Error("Invalid JSON array entry");
14049
+ result2.push(snapshotJsonValue(descriptor.value, stack));
14050
+ }
14051
+ if (keys2.some((key) => typeof key !== "string" || key !== "length" && !/^(0|[1-9][0-9]*)$/.test(key))) {
14052
+ throw new Error("Extra JSON array key");
14053
+ }
14054
+ return result2;
14055
+ }
14056
+ const result = {};
14057
+ for (const key of keys2) {
14058
+ if (typeof key !== "string") throw new Error("Symbol JSON key");
14059
+ const descriptor = descriptors[key];
14060
+ if (!descriptor.enumerable || !("value" in descriptor)) throw new Error("Accessor JSON key");
14061
+ result[key] = snapshotJsonValue(descriptor.value, stack);
14062
+ }
14063
+ return result;
14064
+ } finally {
14065
+ stack.delete(value);
14065
14066
  }
14066
14067
  }
14067
14068
  function cloneExactDate(value) {
@@ -14553,9 +14554,6 @@ function governedUpdate(task, body) {
14553
14554
  if (has("ownerType") && body.ownerType !== task.ownerType || has("ownerId") && body.ownerId !== task.ownerId) {
14554
14555
  return "TASK_OWNERSHIP_IMMUTABLE";
14555
14556
  }
14556
- if (task.ownerType !== "agent") return void 0;
14557
- if (has("parentId") && body.parentId !== task.parentId || has("dependencies") || has("requireReview") || has("result") || has("failureReason") || has("status")) return "TASK_LIFECYCLE_TRANSITION_REQUIRED";
14558
- if (has("context") || has("description")) return "COMPLETION_CONTEXT_MUTATION";
14559
14557
  return void 0;
14560
14558
  }
14561
14559
  function lifecycleStatus(code) {
@@ -14996,7 +14994,8 @@ var AgentWebAppService = class {
14996
14994
  integration: current.integration,
14997
14995
  scope: { ...current.scope, ...patch.scope },
14998
14996
  features: { ...current.features, ...patch.features },
14999
- appearance: { ...current.appearance, ...patch.appearance }
14997
+ appearance: { ...current.appearance, ...patch.appearance },
14998
+ identity: patch.identity ?? current.identity
15000
14999
  };
15001
15000
  await this.validateAssistant(context, current.assistantId);
15002
15001
  const validated = await this.validateConfig(context.tenantId, effective);
@@ -15005,7 +15004,8 @@ var AgentWebAppService = class {
15005
15004
  ...Object.prototype.hasOwnProperty.call(patch, "description") ? { description: validated.description } : {},
15006
15005
  ...patch.scope ? { scope: validated.scope } : {},
15007
15006
  ...patch.features ? { features: validated.features } : {},
15008
- ...patch.appearance ? { appearance: validated.appearance } : {}
15007
+ ...patch.appearance ? { appearance: validated.appearance } : {},
15008
+ ...patch.identity !== void 0 ? { identity: patch.identity } : {}
15009
15009
  };
15010
15010
  return this.writeSnapshot(context, current, update);
15011
15011
  }
@@ -15118,13 +15118,28 @@ var agentWebAppFeaturesSchema = import_zod4.z.object({
15118
15118
  threadManagement: import_zod4.z.boolean(),
15119
15119
  attachments: import_zod4.z.boolean(),
15120
15120
  hitl: import_zod4.z.boolean(),
15121
- genUI: import_zod4.z.boolean()
15121
+ genUI: import_zod4.z.boolean(),
15122
+ files: import_zod4.z.boolean().optional()
15122
15123
  }).strict();
15123
15124
  var agentWebAppAppearanceSchema = import_zod4.z.object({
15124
15125
  title: import_zod4.z.string().max(120).optional(),
15125
15126
  welcomeMessage: import_zod4.z.string().max(1e3).optional(),
15126
15127
  primaryColor: import_zod4.z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional()
15127
15128
  }).strict();
15129
+ var agentWebAppIssuerSchema = import_zod4.z.object({
15130
+ iss: import_zod4.z.string().min(1).max(255),
15131
+ aud: import_zod4.z.string().min(1).max(255),
15132
+ alg: import_zod4.z.enum(["HS256", "RS256"]),
15133
+ secret: import_zod4.z.string().min(1).max(512).optional(),
15134
+ jwksUrl: import_zod4.z.string().url().max(2048).optional()
15135
+ }).strict().refine(
15136
+ (value) => value.alg === "HS256" ? value.secret !== void 0 : value.jwksUrl !== void 0,
15137
+ { message: "HS256 requires secret; RS256 requires jwksUrl" }
15138
+ );
15139
+ var agentWebAppIdentitySchema = import_zod4.z.object({
15140
+ policy: import_zod4.z.enum(["unverified", "verified", "both"]),
15141
+ issuers: import_zod4.z.array(agentWebAppIssuerSchema).min(1).max(10)
15142
+ }).strict();
15128
15143
  var createAgentWebAppSchema = import_zod4.z.object({
15129
15144
  assistantId: externalId,
15130
15145
  name: import_zod4.z.string().min(1).max(120),
@@ -15132,14 +15147,16 @@ var createAgentWebAppSchema = import_zod4.z.object({
15132
15147
  integration: import_zod4.z.object({ type: import_zod4.z.literal("react_sdk") }).strict(),
15133
15148
  scope: agentWebAppScopeSchema,
15134
15149
  features: agentWebAppFeaturesSchema,
15135
- appearance: agentWebAppAppearanceSchema
15150
+ appearance: agentWebAppAppearanceSchema,
15151
+ identity: agentWebAppIdentitySchema.optional()
15136
15152
  }).strict();
15137
15153
  var updateAgentWebAppSchema = import_zod4.z.object({
15138
15154
  name: import_zod4.z.string().min(1).max(120).optional(),
15139
15155
  description: import_zod4.z.string().max(1e3).optional(),
15140
15156
  scope: agentWebAppScopeSchema.partial().strict().optional(),
15141
15157
  features: agentWebAppFeaturesSchema.partial().strict().optional(),
15142
- appearance: agentWebAppAppearanceSchema.partial().strict().optional()
15158
+ appearance: agentWebAppAppearanceSchema.partial().strict().optional(),
15159
+ identity: agentWebAppIdentitySchema.optional()
15143
15160
  }).strict().refine((value) => Object.keys(value).length > 0, "Patch must not be empty");
15144
15161
  var listAgentWebAppsQuerySchema = import_zod4.z.object({
15145
15162
  assistantId: externalId.optional()
@@ -15226,6 +15243,7 @@ function invalidRequest(reply, statusCode) {
15226
15243
  }
15227
15244
 
15228
15245
  // src/routes/agent-web-app-runtime.ts
15246
+ var import_node_crypto8 = require("crypto");
15229
15247
  var import_zod6 = require("zod");
15230
15248
 
15231
15249
  // src/services/agent-web-app/AgentWebAppRuntimeService.ts
@@ -15247,7 +15265,16 @@ var AgentWebAppRuntimeService = class {
15247
15265
  constructor(deps) {
15248
15266
  this.deps = deps;
15249
15267
  }
15250
- async bootstrap(webAppId, userId, selection) {
15268
+ /** Identity policy configuration for the web app, or null when the app is missing/unconfigured. */
15269
+ async getIdentityConfig(webAppId) {
15270
+ try {
15271
+ const runtime = await this.resolveRuntime(webAppId, {});
15272
+ return runtime.webApp.identity ?? null;
15273
+ } catch {
15274
+ return null;
15275
+ }
15276
+ }
15277
+ async bootstrap(webAppId, userId, selection, identity) {
15251
15278
  const runtime = await this.resolveRuntime(webAppId, selection);
15252
15279
  const assistant = await this.deps.assistantResolver.resolve(
15253
15280
  runtime.webApp.tenantId,
@@ -15280,7 +15307,7 @@ var AgentWebAppRuntimeService = class {
15280
15307
  defaultModelKey: runtime.webApp.scope.defaultModelKey,
15281
15308
  features: runtime.webApp.features,
15282
15309
  appearance: runtime.webApp.appearance,
15283
- identityAssurance: "unverified"
15310
+ identityAssurance: identity?.assurance ?? "unverified"
15284
15311
  },
15285
15312
  projects,
15286
15313
  models
@@ -15301,6 +15328,14 @@ var AgentWebAppRuntimeService = class {
15301
15328
  this.requireFeature(runtime.webApp.features.threadManagement);
15302
15329
  return this.createOwnedThread(runtime, userId, input.label);
15303
15330
  }
15331
+ /** Fetch one owned thread. */
15332
+ async getThread(webAppId, userId, threadId, selection) {
15333
+ const runtime = await this.resolveRuntime(webAppId, selection);
15334
+ await this.requireOwnedThread(runtime, userId, threadId);
15335
+ const thread = await this.deps.threadStore.getThreadById(runtime.webApp.tenantId, threadId);
15336
+ if (!thread) this.threadNotFound();
15337
+ return toRuntimeThread(thread);
15338
+ }
15304
15339
  /** Update a thread label; omitted or blank labels preserve the existing label. */
15305
15340
  async updateThread(webAppId, userId, threadId, input) {
15306
15341
  const runtime = await this.resolveRuntime(webAppId, input);
@@ -15335,6 +15370,60 @@ var AgentWebAppRuntimeService = class {
15335
15370
  return [{ id: message.id, role: message.role, ...content && (typeof content === "string" || content.length > 0) ? { content } : {} }];
15336
15371
  });
15337
15372
  }
15373
+ async getState(webAppId, userId, threadId, selection) {
15374
+ const runtime = await this.resolveRuntime(webAppId, selection);
15375
+ await this.requireOwnedThread(runtime, userId, threadId);
15376
+ const agent = this.resolveAgent(runtime, threadId);
15377
+ const state = await agent.getCurrentState();
15378
+ const pendingMessages = await agent.getPendingMessages();
15379
+ const normalized = pendingMessages.map((message) => ({
15380
+ ...message,
15381
+ content: typeof message.content === "object" && message.content !== null ? "message" in message.content ? message.content.message : JSON.stringify(message.content) : String(message.content ?? "")
15382
+ }));
15383
+ return { ...state && typeof state === "object" ? state : {}, pendingMessages: normalized };
15384
+ }
15385
+ /** Raw stored messages (Console memory contract, tool/interrupt preserved). */
15386
+ async getConsoleMessages(webAppId, userId, threadId, selection) {
15387
+ const runtime = await this.resolveRuntime(webAppId, selection);
15388
+ await this.requireOwnedThread(runtime, userId, threadId);
15389
+ return this.deps.messageReader.getCurrentMessages({
15390
+ tenantId: runtime.webApp.tenantId,
15391
+ assistantId: runtime.webApp.assistantId,
15392
+ threadId,
15393
+ workspaceId: runtime.project.workspaceId,
15394
+ projectId: runtime.project.id
15395
+ });
15396
+ }
15397
+ /** Background enqueue only (ADR-026): returns the message id; delivery happens over resume_stream. */
15398
+ async enqueueMessage(webAppId, userId, threadId, content, selection) {
15399
+ const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, selection);
15400
+ const agent = this.resolveAgent(runtime, threadId);
15401
+ const execution = await this.execute(agent, runtime, { input: { message: content } });
15402
+ return { messageId: execution.messageId };
15403
+ }
15404
+ async removePendingMessage(webAppId, userId, threadId, messageId, selection) {
15405
+ const runtime = await this.resolveRuntime(webAppId, selection);
15406
+ await this.requireOwnedThread(runtime, userId, threadId);
15407
+ const agent = this.resolveAgent(runtime, threadId);
15408
+ if (!await agent.removePendingMessage(messageId)) this.threadNotFound();
15409
+ }
15410
+ /** Thread-lifetime SSE resume from the last known client message id. */
15411
+ async resumeStream(webAppId, userId, threadId, messageId, selection) {
15412
+ const runtime = await this.resolveRuntime(webAppId, selection);
15413
+ await this.requireOwnedThread(runtime, userId, threadId);
15414
+ const agent = this.resolveAgent(runtime, threadId);
15415
+ return { stream: agent.chunkStream(messageId, []) };
15416
+ }
15417
+ async resolveFileScope(webAppId, selection) {
15418
+ const runtime = await this.resolveRuntime(webAppId, selection);
15419
+ return {
15420
+ tenantId: runtime.webApp.tenantId,
15421
+ workspaceId: runtime.project.workspaceId,
15422
+ projectId: runtime.project.id,
15423
+ assistantId: runtime.webApp.assistantId,
15424
+ enabled: runtime.webApp.features.files === true
15425
+ };
15426
+ }
15338
15427
  async streamMessage(webAppId, userId, threadId, input) {
15339
15428
  const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, input);
15340
15429
  const refs = this.resolveAttachmentRefs(runtime, userId, threadId, input.attachmentRefs ?? []);
@@ -15579,93 +15668,102 @@ function toRuntimeThread(thread) {
15579
15668
  };
15580
15669
  }
15581
15670
 
15582
- // src/services/agent-web-app/AgentWebAppStreamProjector.ts
15583
- var import_protocols12 = require("@axiom-lattice/protocols");
15584
- function createAgentWebAppStreamProjectionContext(options = {}) {
15585
- return { startedToolIds: /* @__PURE__ */ new Set(), allowInterrupts: options.allowInterrupts ?? true, allowGenUI: options.allowGenUI ?? false };
15586
- }
15587
- function projectAgentWebAppChunk(chunk, context) {
15588
- if (chunk.type === import_protocols12.MessageChunkTypes.AI) {
15589
- const events = [];
15590
- const startedToolIds = context?.startedToolIds ?? /* @__PURE__ */ new Set();
15591
- if (typeof chunk.data.content === "string" && chunk.data.content) events.push({ type: "message.delta", text: chunk.data.content });
15592
- if (context?.allowGenUI && Array.isArray(chunk.data.content)) {
15593
- for (const value of chunk.data.content) {
15594
- const block = (0, import_protocols12.parseAgentWebAppGenUIBlock)(value);
15595
- if (block) events.push({ type: "genui.render", block });
15596
- }
15597
- }
15598
- for (const call of chunk.data.tool_call_chunks ?? []) {
15599
- if (call.id && call.name && !startedToolIds.has(call.id)) {
15600
- startedToolIds.add(call.id);
15601
- events.push({ type: "tool.started", id: call.id, name: call.name });
15602
- }
15603
- }
15604
- for (const call of chunk.data.tool_calls ?? []) {
15605
- if (!startedToolIds.has(call.id)) {
15606
- startedToolIds.add(call.id);
15607
- events.push({ type: "tool.started", id: call.id, name: call.name });
15608
- }
15609
- }
15610
- return events;
15611
- }
15612
- if (chunk.type === import_protocols12.MessageChunkTypes.TOOL && chunk.data.tool_call_id) {
15613
- return [{ type: "tool.completed", id: chunk.data.tool_call_id }];
15614
- }
15615
- if (chunk.type === import_protocols12.MessageChunkTypes.INTERRUPT) {
15616
- if (context && !context.allowInterrupts) return [{ type: "stream.completed" }];
15617
- const interrupt = publicInterrupt(chunk);
15618
- return interrupt ? [{ type: "interrupt.created", interrupt }, { type: "stream.completed" }] : [{ type: "stream.completed" }];
15619
- }
15620
- if (chunk.type === import_protocols12.MessageChunkTypes.MESSAGE_COMPLETED) {
15621
- return [
15622
- { type: "message.completed", messageId: chunk.data.id },
15623
- { type: "stream.completed" }
15624
- ];
15625
- }
15626
- if (chunk.type === import_protocols12.MessageChunkTypes.MESSAGE_FAILED) {
15627
- return [
15628
- { type: "error", error: { code: "STREAM_FAILED", message: "Stream failed", retryable: true } },
15629
- { type: "stream.completed" }
15630
- ];
15671
+ // src/services/agent-web-app/AgentWebAppTokenVerifier.ts
15672
+ var import_node_crypto7 = require("crypto");
15673
+ var WebAppTokenError = class extends Error {
15674
+ constructor(message) {
15675
+ super(message);
15676
+ this.name = "WebAppTokenError";
15631
15677
  }
15632
- return [];
15633
- }
15634
- function publicInterrupt(chunk) {
15635
- const value = parseInterruptContent(chunk.data.content);
15636
- if (!value) return void 0;
15637
- const topLevelId = chunk.id;
15638
- const id3 = typeof topLevelId === "string" ? topLevelId : chunk.data.id;
15639
- if (!id3) return void 0;
15640
- return {
15641
- id: id3,
15642
- type: typeof value.type === "string" ? value.type : "input",
15643
- prompt: typeof value.prompt === "string" ? value.prompt : typeof value.message === "string" ? value.message : "Input required",
15644
- ...isRecord3(value.data) ? { data: value.data } : {}
15645
- };
15646
- }
15647
- function parseInterruptContent(content) {
15648
- if (!content) return {};
15649
- if (isRecord3(content)) return content;
15650
- if (typeof content !== "string") return void 0;
15678
+ };
15679
+ var MAX_TOKEN_BYTES = 8 * 1024;
15680
+ var CLOCK_SKEW_SECONDS = 60;
15681
+ var JWKS_TTL_MS = 10 * 60 * 1e3;
15682
+ var jwksCache = /* @__PURE__ */ new Map();
15683
+ function decodeSegment(segment) {
15684
+ const json = Buffer.from(segment, "base64url").toString("utf8");
15685
+ let value;
15651
15686
  try {
15652
- const parsed = JSON.parse(content);
15653
- return isRecord3(parsed) ? parsed : { prompt: content };
15687
+ value = JSON.parse(json);
15654
15688
  } catch {
15655
- return { prompt: content };
15689
+ throw new WebAppTokenError("Malformed token segment");
15656
15690
  }
15691
+ if (typeof value !== "object" || value === null) throw new WebAppTokenError("Malformed token segment");
15692
+ return value;
15657
15693
  }
15658
- function isRecord3(value) {
15659
- return typeof value === "object" && value !== null && !Array.isArray(value);
15694
+ async function resolveKey(issuer, kid, fetchImpl) {
15695
+ const cacheKey = issuer.jwksUrl ?? "";
15696
+ const load = async () => {
15697
+ const response = await fetchImpl(issuer.jwksUrl ?? "");
15698
+ if (!response.ok) throw new WebAppTokenError("JWKS fetch failed");
15699
+ const document = await response.json();
15700
+ const keys2 = document.keys;
15701
+ const entry2 = { keys: Array.isArray(keys2) ? keys2 : [], fetchedAt: Date.now() };
15702
+ jwksCache.set(cacheKey, entry2);
15703
+ return entry2;
15704
+ };
15705
+ const cached = jwksCache.get(cacheKey);
15706
+ let entry = cached && Date.now() - cached.fetchedAt < JWKS_TTL_MS ? cached : await load();
15707
+ let jwk = entry.keys.find((key) => kid === void 0 ? true : key.kid === kid);
15708
+ if (!jwk && kid !== void 0) {
15709
+ entry = await load();
15710
+ jwk = entry.keys.find((key) => key.kid === kid);
15711
+ }
15712
+ if (!jwk) throw new WebAppTokenError("No matching JWKS key");
15713
+ return (0, import_node_crypto7.createPublicKey)({ key: jwk, format: "jwk" });
15714
+ }
15715
+ async function verifyWebAppToken(token, issuer, options = {}) {
15716
+ const nowMs = options.now ? options.now() : Date.now();
15717
+ const now = Math.floor(nowMs / 1e3);
15718
+ if (token.length === 0 || Buffer.byteLength(token) > MAX_TOKEN_BYTES) throw new WebAppTokenError("Token size invalid");
15719
+ const parts = token.split(".");
15720
+ if (parts.length !== 3) throw new WebAppTokenError("Token structure invalid");
15721
+ const [rawHeader, rawPayload, rawSignature] = parts;
15722
+ const header2 = decodeSegment(rawHeader);
15723
+ const payload = decodeSegment(rawPayload);
15724
+ if (header2.alg !== issuer.alg) throw new WebAppTokenError("Token algorithm mismatch");
15725
+ if (payload.iss !== issuer.iss) throw new WebAppTokenError("Token issuer mismatch");
15726
+ if (payload.aud !== issuer.aud) throw new WebAppTokenError("Token audience mismatch");
15727
+ const exp = typeof payload.exp === "number" ? payload.exp : void 0;
15728
+ if (exp === void 0 || exp + CLOCK_SKEW_SECONDS < now) throw new WebAppTokenError("Token expired");
15729
+ const nbf = typeof payload.nbf === "number" ? payload.nbf : void 0;
15730
+ if (nbf !== void 0 && nbf - CLOCK_SKEW_SECONDS > now) throw new WebAppTokenError("Token not yet valid");
15731
+ const signature = Buffer.from(rawSignature, "base64url");
15732
+ const data = Buffer.from(`${rawHeader}.${rawPayload}`);
15733
+ if (issuer.alg === "HS256") {
15734
+ if (typeof issuer.secret !== "string" || issuer.secret.length === 0) throw new WebAppTokenError("Issuer secret missing");
15735
+ const expected = (0, import_node_crypto7.createHmac)("sha256", issuer.secret).update(data).digest();
15736
+ if (signature.length !== expected.length || !(0, import_node_crypto7.timingSafeEqual)(signature, expected)) {
15737
+ throw new WebAppTokenError("Token signature invalid");
15738
+ }
15739
+ } else {
15740
+ if (typeof issuer.jwksUrl !== "string") throw new WebAppTokenError("Issuer JWKS URL missing");
15741
+ const key = await resolveKey(issuer, typeof header2.kid === "string" ? header2.kid : void 0, options.fetchImpl ?? fetch);
15742
+ let valid = false;
15743
+ try {
15744
+ valid = (0, import_node_crypto7.verify)("RSA-SHA256", data, key, signature);
15745
+ } catch {
15746
+ valid = false;
15747
+ }
15748
+ if (!valid) throw new WebAppTokenError("Token signature invalid");
15749
+ }
15750
+ const sub = payload.sub;
15751
+ if (typeof sub !== "string" || sub.length === 0 || sub.length > 128 || !/^[A-Za-z0-9._:@-]+$/.test(sub)) {
15752
+ throw new WebAppTokenError("Token subject invalid");
15753
+ }
15754
+ return sub;
15660
15755
  }
15661
15756
 
15662
15757
  // src/routes/agent-web-app-runtime.ts
15758
+ var import_protocols12 = require("@axiom-lattice/protocols");
15663
15759
  var idSchema = import_zod6.z.string().min(1).max(128);
15664
15760
  var paramsSchema = import_zod6.z.object({ webAppId: idSchema }).strict();
15665
15761
  var threadParamsSchema = import_zod6.z.object({ webAppId: idSchema, threadId: idSchema }).strict();
15666
15762
  var interruptParamsSchema = import_zod6.z.object({ webAppId: idSchema, threadId: idSchema, interruptId: idSchema }).strict();
15763
+ var pendingMessageParamsSchema = import_zod6.z.object({ webAppId: idSchema, threadId: idSchema, messageId: idSchema }).strict();
15667
15764
  var bootstrapQuerySchema = import_zod6.z.object({ projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
15668
15765
  var projectQuerySchema = import_zod6.z.object({ projectId: idSchema.optional() }).strict();
15766
+ var projectQueryLenientSchema = projectQuerySchema.passthrough();
15669
15767
  var threadBodySchema = import_zod6.z.object({ projectId: idSchema.optional(), label: import_zod6.z.string().max(128).optional() }).strict();
15670
15768
  var streamBodySchema = import_zod6.z.object({
15671
15769
  content: import_zod6.z.string().trim().min(1).max(32768),
@@ -15684,8 +15782,27 @@ var resumeBodySchema = import_zod6.z.object({
15684
15782
  ])
15685
15783
  }).strict();
15686
15784
  var MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
15785
+ var stateQuerySchema = import_zod6.z.object({
15786
+ threadId: idSchema,
15787
+ projectId: idSchema.optional(),
15788
+ modelKey: idSchema.optional()
15789
+ }).strict();
15790
+ var resumeSchema = import_zod6.z.object({
15791
+ messageId: idSchema,
15792
+ projectId: idSchema.optional(),
15793
+ modelKey: idSchema.optional()
15794
+ }).strict();
15795
+ var devTokenSchema = import_zod6.z.object({
15796
+ iss: import_zod6.z.string().min(1).max(255).optional(),
15797
+ sub: import_zod6.z.string().regex(/^[A-Za-z0-9._:@-]+$/).max(128).optional(),
15798
+ hours: import_zod6.z.number().gt(0).max(720).optional()
15799
+ }).strict();
15800
+ var fileQuerySchema = import_zod6.z.object({
15801
+ projectId: idSchema.optional(),
15802
+ modelKey: idSchema.optional()
15803
+ }).passthrough();
15687
15804
  function createExternalHeaderIdentityResolver() {
15688
- return { resolve(request) {
15805
+ return { async resolve(request) {
15689
15806
  const raw = request.headers["x-axiom-external-user-id"];
15690
15807
  if (raw === void 0) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "External user id is required");
15691
15808
  if (Array.isArray(raw) || typeof raw !== "string") throw new AgentWebAppRuntimeError("INVALID_USER_ID", 400, "External user id is invalid");
@@ -15694,47 +15811,109 @@ function createExternalHeaderIdentityResolver() {
15694
15811
  return { userId, assurance: "unverified" };
15695
15812
  } };
15696
15813
  }
15814
+ function createWebAppIdentityResolver(deps) {
15815
+ const headerResolver = createExternalHeaderIdentityResolver();
15816
+ return {
15817
+ async resolve(request, webAppId) {
15818
+ let config;
15819
+ try {
15820
+ config = await deps.getIdentityConfig(webAppId);
15821
+ } catch {
15822
+ config = void 0;
15823
+ }
15824
+ const policy = config?.policy ?? "unverified";
15825
+ const authHeader = request.headers.authorization;
15826
+ const bearer = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : void 0;
15827
+ if (policy !== "unverified" && bearer !== void 0) {
15828
+ const issuers = config?.issuers ?? [];
15829
+ if (issuers.length === 0) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 401, "No trusted issuer configured");
15830
+ let lastError = new AgentWebAppRuntimeError("INVALID_REQUEST", 401, "Invalid identity token");
15831
+ for (const issuer of issuers) {
15832
+ try {
15833
+ const sub = await verifyWebAppToken(bearer, issuer);
15834
+ return { userId: sub, assurance: "verified" };
15835
+ } catch (error) {
15836
+ if (error instanceof WebAppTokenError) {
15837
+ lastError = error;
15838
+ continue;
15839
+ }
15840
+ throw error;
15841
+ }
15842
+ }
15843
+ const message = lastError instanceof WebAppTokenError ? `Invalid identity token: ${lastError.message}` : "Invalid identity token";
15844
+ throw new AgentWebAppRuntimeError("INVALID_REQUEST", 401, message);
15845
+ }
15846
+ if (policy === "verified") throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "A verified identity token is required");
15847
+ return headerResolver.resolve(request, webAppId);
15848
+ }
15849
+ };
15850
+ }
15697
15851
  function registerAgentWebAppRuntimeRoutes(app2, deps) {
15698
- const identities = deps.identityResolver ?? createExternalHeaderIdentityResolver();
15852
+ const identities = deps.identityResolver ?? createWebAppIdentityResolver({ getIdentityConfig: (webAppId) => deps.service.getIdentityConfig(webAppId) });
15699
15853
  app2.register(async (routes) => {
15700
15854
  routes.setErrorHandler((error, _request, reply) => normalizeError2(reply, error));
15701
15855
  routes.get("/api/web-apps/:webAppId/runtime/bootstrap", async (request, reply) => handle2(reply, async () => {
15702
15856
  const { webAppId } = paramsSchema.parse(request.params);
15703
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
15704
- return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview)) };
15857
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15858
+ return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview), { assurance: identity.assurance ?? "unverified" }) };
15705
15859
  }));
15706
15860
  routes.get("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
15707
15861
  const { webAppId } = paramsSchema.parse(request.params);
15708
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
15862
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15709
15863
  const records = await deps.service.listThreads(webAppId, identity.userId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
15710
15864
  return { data: { records, total: records.length } };
15711
15865
  }));
15712
15866
  routes.post("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
15713
15867
  const { webAppId } = paramsSchema.parse(request.params);
15714
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
15868
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15715
15869
  return { statusCode: 201, data: await deps.service.createThread(webAppId, identity.userId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
15716
15870
  }));
15717
15871
  routes.patch("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
15718
15872
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
15719
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
15873
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15720
15874
  return { data: await deps.service.updateThread(webAppId, identity.userId, threadId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
15721
15875
  }));
15722
15876
  routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
15723
15877
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
15724
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
15878
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15725
15879
  await deps.service.deleteThread(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
15726
15880
  return { data: { id: threadId } };
15727
15881
  }));
15728
15882
  routes.get("/api/web-apps/:webAppId/runtime/threads/:threadId/messages", async (request, reply) => handle2(reply, async () => {
15729
15883
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
15730
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
15884
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15731
15885
  const messages = await deps.service.getMessages(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
15732
15886
  return { data: { messages } };
15733
15887
  }));
15888
+ routes.get("/api/web-apps/:webAppId/runtime/state", async (request, reply) => handle2(reply, async () => {
15889
+ const { webAppId } = paramsSchema.parse(request.params);
15890
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15891
+ const { threadId, projectId, modelKey: modelKey2 } = stateQuerySchema.parse(request.query);
15892
+ const state = await deps.service.getState(webAppId, identity.userId, threadId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
15893
+ return { data: state };
15894
+ }));
15895
+ routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId/pending-messages/:messageId", async (request, reply) => handle2(reply, async () => {
15896
+ const { webAppId, threadId, messageId } = pendingMessageParamsSchema.parse(request.params);
15897
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15898
+ await deps.service.removePendingMessage(webAppId, identity.userId, threadId, messageId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
15899
+ return { data: { removed: true } };
15900
+ }));
15734
15901
  routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/messages/stream", async (request, reply) => {
15735
15902
  try {
15736
15903
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
15737
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
15904
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15905
+ const input = streamBodySchema.parse(request.body);
15906
+ const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
15907
+ beginSse(reply);
15908
+ await pipeNativeWebAppStream(reply.raw, result.stream);
15909
+ } catch (error) {
15910
+ return normalizeError2(reply, error);
15911
+ }
15912
+ });
15913
+ routes.post("/api/web-apps/:webAppId/runtime/v1/threads/:threadId/messages/stream", async (request, reply) => {
15914
+ try {
15915
+ const { webAppId, threadId } = threadParamsSchema.parse(request.params);
15916
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15738
15917
  const input = streamBodySchema.parse(request.body);
15739
15918
  const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
15740
15919
  beginSse(reply);
@@ -15743,15 +15922,27 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
15743
15922
  return normalizeError2(reply, error);
15744
15923
  }
15745
15924
  });
15925
+ routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/resume", async (request, reply) => {
15926
+ try {
15927
+ const { webAppId, threadId } = threadParamsSchema.parse(request.params);
15928
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15929
+ const { messageId, projectId, modelKey: modelKey2 } = resumeSchema.parse(request.body);
15930
+ const result = await deps.service.resumeStream(webAppId, identity.userId, threadId, messageId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
15931
+ beginSse(reply);
15932
+ await pipeNativeWebAppStream(reply.raw, result.stream);
15933
+ } catch (error) {
15934
+ return normalizeError2(reply, error);
15935
+ }
15936
+ });
15746
15937
  routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/abort", async (request, reply) => handle2(reply, async () => {
15747
15938
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
15748
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
15939
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15749
15940
  await deps.service.abort(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
15750
15941
  return { data: { aborted: true } };
15751
15942
  }));
15752
15943
  routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/attachments", async (request, reply) => handle2(reply, async () => {
15753
15944
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
15754
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
15945
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15755
15946
  if (!request.isMultipart()) throw new import_zod6.ZodError([]);
15756
15947
  const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
15757
15948
  if (!part || !part.filename || part.filename.length > 255) throw new import_zod6.ZodError([]);
@@ -15767,7 +15958,7 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
15767
15958
  routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/interrupts/:interruptId/resume", async (request, reply) => {
15768
15959
  try {
15769
15960
  const { webAppId, threadId, interruptId } = interruptParamsSchema.parse(request.params);
15770
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
15961
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15771
15962
  const { projectId, modelKey: modelKey2, message, response } = resumeBodySchema.parse(request.body);
15772
15963
  const result = await deps.service.resumeInterrupt(webAppId, identity.userId, threadId, interruptId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview), { message, response });
15773
15964
  beginSse(reply);
@@ -15776,34 +15967,287 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
15776
15967
  return normalizeError2(reply, error);
15777
15968
  }
15778
15969
  });
15970
+ routes.post("/api/web-apps/:webAppId/dev/token", async (request, reply) => handle2(reply, async () => {
15971
+ if (process.env.WEB_APP_DEV_TOKEN_ENABLED !== "true") {
15972
+ throw new AgentWebAppRuntimeError("FEATURE_DISABLED", 403, "Dev token issuance is disabled (set WEB_APP_DEV_TOKEN_ENABLED=true)");
15973
+ }
15974
+ const { webAppId } = paramsSchema.parse(request.params);
15975
+ const { iss, sub, hours } = devTokenSchema.parse(request.body ?? {});
15976
+ const config = await deps.service.getIdentityConfig(webAppId);
15977
+ const issuer = (config?.issuers ?? []).find((entry) => entry.alg === "HS256" && (iss === void 0 || entry.iss === iss));
15978
+ if (!issuer || typeof issuer.secret !== "string") {
15979
+ throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "Web app has no HS256 issuer configured");
15980
+ }
15981
+ const now = Math.floor(Date.now() / 1e3);
15982
+ const expiresIn = (hours ?? 24) * 3600;
15983
+ const subject = sub ?? "dev-user";
15984
+ const b64url = (input) => Buffer.from(input).toString("base64url");
15985
+ const body = `${b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }))}.${b64url(JSON.stringify({ sub: subject, iss: issuer.iss, aud: issuer.aud, iat: now, exp: now + expiresIn }))}`;
15986
+ const token = `${body}.${(0, import_node_crypto8.createHmac)("sha256", issuer.secret).update(body).digest("base64url")}`;
15987
+ return { data: { token, tokenType: "Bearer", expiresIn, subject } };
15988
+ }));
15989
+ if (deps.taskController) {
15990
+ const tasks = deps.taskController;
15991
+ const withSubject = (handler) => async (request, reply) => {
15992
+ const { webAppId } = paramsSchema.parse(request.params);
15993
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
15994
+ const proxy = Object.create(request);
15995
+ proxy.headers = { ...request.headers, "x-user-id": identity.userId };
15996
+ return handler(proxy, reply);
15997
+ };
15998
+ routes.get("/api/web-apps/:webAppId/runtime/tasks", withSubject((request, reply) => tasks.list(request, reply)));
15999
+ routes.post("/api/web-apps/:webAppId/runtime/tasks", withSubject((request, reply) => tasks.create(request, reply)));
16000
+ routes.get("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.get(request, reply)));
16001
+ routes.put("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.update(request, reply)));
16002
+ routes.patch("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.update(request, reply)));
16003
+ routes.delete("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.remove(request, reply)));
16004
+ routes.patch("/api/web-apps/:webAppId/runtime/tasks/:taskId/complete", withSubject((request, reply) => tasks.complete(request, reply)));
16005
+ routes.get("/api/web-apps/:webAppId/runtime/tasks/:taskId/work-items", withSubject((request, reply) => tasks.workItems(request, reply)));
16006
+ }
16007
+ if (deps.workspaceController) {
16008
+ const workspace = deps.workspaceController;
16009
+ const fileScope = async (webAppId, selection) => {
16010
+ const scope = await deps.service.resolveFileScope(webAppId, selection);
16011
+ if (!scope.enabled) throw new AgentWebAppRuntimeError("FEATURE_DISABLED", 403, "Files are disabled for this web app");
16012
+ return scope;
16013
+ };
16014
+ const scopedRequest = (request, scope, params, query) => {
16015
+ const proxy = Object.create(request);
16016
+ proxy.params = params;
16017
+ proxy.query = query;
16018
+ proxy.headers = { ...request.headers, "x-tenant-id": scope.tenantId };
16019
+ return proxy;
16020
+ };
16021
+ const unwrap = (result) => ({ data: result.data });
16022
+ routes.get("/api/web-apps/:webAppId/runtime/files/listpath", async (request) => {
16023
+ const { webAppId } = paramsSchema.parse(request.params);
16024
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16025
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
16026
+ const query = { ...request.query, assistantId: scope.assistantId };
16027
+ const result = await workspace.listPath(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query));
16028
+ return unwrap(result);
16029
+ });
16030
+ routes.get("/api/web-apps/:webAppId/runtime/files/readfile", async (request) => {
16031
+ const { webAppId } = paramsSchema.parse(request.params);
16032
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16033
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
16034
+ const query = { ...request.query, assistantId: scope.assistantId };
16035
+ const result = await workspace.readFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query));
16036
+ return unwrap(result);
16037
+ });
16038
+ routes.post("/api/web-apps/:webAppId/runtime/files/uploadfile", async (request, reply) => {
16039
+ const { webAppId } = paramsSchema.parse(request.params);
16040
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16041
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
16042
+ const scoped = scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, { assistantId: scope.assistantId });
16043
+ await workspace.uploadFile(scoped, reply);
16044
+ return reply;
16045
+ });
16046
+ routes.get("/api/web-apps/:webAppId/runtime/files/viewfile", async (request, reply) => {
16047
+ const { webAppId } = paramsSchema.parse(request.params);
16048
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16049
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
16050
+ const query = { ...request.query, assistantId: scope.assistantId };
16051
+ await workspace.viewFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
16052
+ return reply;
16053
+ });
16054
+ routes.get("/api/web-apps/:webAppId/runtime/files/downloadfile", async (request, reply) => {
16055
+ const { webAppId } = paramsSchema.parse(request.params);
16056
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16057
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
16058
+ const query = { ...request.query, assistantId: scope.assistantId };
16059
+ await workspace.downloadFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
16060
+ return reply;
16061
+ });
16062
+ routes.get("/api/web-apps/:webAppId/runtime/files/downloadfolder", async (request, reply) => {
16063
+ const { webAppId } = paramsSchema.parse(request.params);
16064
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16065
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
16066
+ const query = { ...request.query, assistantId: scope.assistantId };
16067
+ await workspace.downloadFolder(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
16068
+ return reply;
16069
+ });
16070
+ const genuiSandboxParams = import_zod6.z.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema }).strict();
16071
+ const delegateGenuiFile = (method) => async (request, reply) => {
16072
+ const { webAppId } = genuiSandboxParams.parse(request.params);
16073
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16074
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
16075
+ const query = { ...request.query, assistantId: scope.assistantId };
16076
+ await method(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
16077
+ return reply;
16078
+ };
16079
+ routes.get("/api/web-apps/:webAppId/runtime/assistants/:assistantId/threads/:threadId/sandbox/downloadfile", delegateGenuiFile(workspace.downloadFile.bind(workspace)));
16080
+ routes.get("/api/web-apps/:webAppId/runtime/assistants/:assistantId/threads/:threadId/sandbox/viewfile", delegateGenuiFile(workspace.viewFile.bind(workspace)));
16081
+ }
16082
+ const mirrorThreadParams = import_zod6.z.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema }).strict();
16083
+ const mirrorBody = import_zod6.z.object({ metadata: import_zod6.z.object({ label: import_zod6.z.string().max(300).optional() }).strict().optional(), projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
16084
+ const toConsoleThread = (thread, assistantId) => ({
16085
+ id: thread.id,
16086
+ assistantId,
16087
+ metadata: thread.label ? { label: thread.label } : {},
16088
+ createdAt: thread.createdAt,
16089
+ updatedAt: thread.updatedAt
16090
+ });
16091
+ const mirrorAssistantParams = import_zod6.z.object({ webAppId: idSchema, assistantId: idSchema }).strict();
16092
+ const mirrorPendingParams = import_zod6.z.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema, messageId: idSchema }).strict();
16093
+ const registerMirror = (prefix) => {
16094
+ routes.get(`${prefix}/assistants/:assistantId/threads`, async (request, reply) => handle2(reply, async () => {
16095
+ const { webAppId, assistantId } = mirrorAssistantParams.parse(request.params);
16096
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16097
+ const threads = await deps.service.listThreads(webAppId, identity.userId, projectQueryLenientSchema.parse(request.query));
16098
+ return { data: { records: threads.map((thread) => toConsoleThread(thread, assistantId)) } };
16099
+ }));
16100
+ routes.post(`${prefix}/assistants/:assistantId/threads`, async (request, reply) => handle2(reply, async () => {
16101
+ const { webAppId, assistantId } = mirrorAssistantParams.parse(request.params);
16102
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16103
+ const body = mirrorBody.parse(request.body ?? {});
16104
+ const created = await deps.service.createThread(webAppId, identity.userId, { label: body.metadata?.label, ...body.projectId ? { projectId: body.projectId } : {} });
16105
+ return { statusCode: 201, data: { id: created.id } };
16106
+ }));
16107
+ routes.get(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
16108
+ const { webAppId, assistantId, threadId } = mirrorThreadParams.parse(request.params);
16109
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16110
+ const thread = await deps.service.getThread(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
16111
+ return { data: toConsoleThread(thread, assistantId) };
16112
+ }));
16113
+ routes.put(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
16114
+ const { webAppId, assistantId, threadId } = mirrorThreadParams.parse(request.params);
16115
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16116
+ const body = mirrorBody.parse(request.body ?? {});
16117
+ const updated = await deps.service.updateThread(webAppId, identity.userId, threadId, { label: body.metadata?.label, ...body.projectId ? { projectId: body.projectId } : {} });
16118
+ return { data: toConsoleThread(updated, assistantId) };
16119
+ }));
16120
+ routes.delete(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
16121
+ const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
16122
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16123
+ await deps.service.deleteThread(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
16124
+ return { data: { success: true, message: "Thread deleted" } };
16125
+ }));
16126
+ routes.post(`${prefix}/assistants/:assistantId/threads/:threadId/sandbox/uploadfile`, async (request, reply) => handle2(reply, async () => {
16127
+ const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
16128
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16129
+ if (!request.isMultipart()) throw new import_zod6.ZodError([]);
16130
+ const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
16131
+ if (!part || !part.filename || part.filename.length > 255) throw new import_zod6.ZodError([]);
16132
+ const bytes = await part.toBuffer();
16133
+ if (bytes.length === 0 || part.file.truncated) throw new import_zod6.ZodError([]);
16134
+ const uploaded = await deps.service.uploadAttachment(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query), {
16135
+ name: part.filename,
16136
+ mimeType: part.mimetype || "application/octet-stream",
16137
+ bytes
16138
+ });
16139
+ return { statusCode: 201, data: { id: uploaded.ref, path: uploaded.ref, name: uploaded.name, mimeType: uploaded.mimeType, size: uploaded.size } };
16140
+ }));
16141
+ routes.get(`${prefix}/assistants/:assistantId/:threadId/memory`, async (request) => {
16142
+ const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
16143
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16144
+ const messages = await deps.service.getConsoleMessages(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
16145
+ return messages;
16146
+ });
16147
+ routes.get(`${prefix}/assistants/:assistantId/:threadId/state`, async (request) => {
16148
+ const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
16149
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16150
+ return deps.service.getState(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
16151
+ });
16152
+ routes.delete(`${prefix}/assistants/:assistantId/threads/:threadId/pending-messages/:messageId`, async (request, reply) => handle2(reply, async () => {
16153
+ const { webAppId, threadId, messageId } = mirrorPendingParams.parse(request.params);
16154
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16155
+ await deps.service.removePendingMessage(webAppId, identity.userId, threadId, messageId, projectQueryLenientSchema.parse(request.query));
16156
+ return { data: { removed: true } };
16157
+ }));
16158
+ routes.post(`${prefix}/assistants/:assistantId/threads/:threadId/abort`, async (request, reply) => handle2(reply, async () => {
16159
+ const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
16160
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16161
+ await deps.service.abort(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
16162
+ return { data: { aborted: true } };
16163
+ }));
16164
+ const runsBodySchema = import_zod6.z.object({
16165
+ thread_id: idSchema.optional(),
16166
+ assistant_id: idSchema.optional(),
16167
+ messages: import_zod6.z.array(import_zod6.z.object({ content: import_zod6.z.string().min(1).max(32768) }).passthrough()).min(1).max(20),
16168
+ background: import_zod6.z.boolean().optional(),
16169
+ streaming: import_zod6.z.boolean().optional(),
16170
+ projectId: idSchema.optional(),
16171
+ modelKey: idSchema.optional()
16172
+ }).passthrough();
16173
+ routes.post(`${prefix}/runs`, async (request, reply) => {
16174
+ try {
16175
+ const { webAppId } = paramsSchema.parse(request.params);
16176
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16177
+ const body = runsBodySchema.parse(request.body ?? {});
16178
+ if (!body.background || body.streaming) {
16179
+ throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "Mirror runs supports only background enqueue (stream via resume_stream)");
16180
+ }
16181
+ const threadId = body.thread_id ?? "";
16182
+ if (!threadId) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "thread_id is required");
16183
+ const content = body.messages.map((message) => message.content).join("\n");
16184
+ const { messageId } = await deps.service.enqueueMessage(webAppId, identity.userId, threadId, content, { projectId: body.projectId, modelKey: body.modelKey });
16185
+ return reply.code(202).send({ success: true, messageId, queued: true });
16186
+ } catch (error) {
16187
+ return normalizeError2(reply, error);
16188
+ }
16189
+ });
16190
+ const resumeStreamSchema2 = import_zod6.z.object({
16191
+ assistant_id: idSchema.optional(),
16192
+ thread_id: idSchema,
16193
+ message_id: idSchema,
16194
+ known_content: import_zod6.z.string().optional(),
16195
+ poll_interval: import_zod6.z.number().optional(),
16196
+ projectId: idSchema.optional(),
16197
+ modelKey: idSchema.optional()
16198
+ }).strict();
16199
+ routes.post(`${prefix}/resume_stream`, async (request, reply) => {
16200
+ try {
16201
+ const { webAppId } = paramsSchema.parse(request.params);
16202
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
16203
+ const body = resumeStreamSchema2.parse(request.body ?? {});
16204
+ const result = await deps.service.resumeStream(webAppId, identity.userId, body.thread_id, body.message_id, { projectId: body.projectId, modelKey: body.modelKey });
16205
+ beginSse(reply);
16206
+ await pipeNativeWebAppStream(reply.raw, result.stream);
16207
+ } catch (error) {
16208
+ return normalizeError2(reply, error);
16209
+ }
16210
+ });
16211
+ };
16212
+ registerMirror("/api/web-apps/:webAppId/runtime");
16213
+ registerMirror("/api/web-apps/:webAppId/runtime/api");
15779
16214
  });
15780
16215
  }
15781
- function resolveRuntimeIdentity(request, webAppId, identities) {
15782
- if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request);
16216
+ async function resolveRuntimeIdentity(request, webAppId, identities) {
16217
+ if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request, webAppId);
15783
16218
  const user = request.user;
15784
16219
  if (!user?.id || !user.tenantId) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "Authenticated Console preview is required");
15785
- return { userId: `console-preview:${user.id}:${webAppId}`, preview: { tenantId: user.tenantId, userId: user.id } };
16220
+ return { userId: `console-preview:${user.id}:${webAppId}`, assurance: "unverified", preview: { tenantId: user.tenantId, userId: user.id } };
15786
16221
  }
15787
16222
  function withPreview(selection, preview) {
15788
16223
  return { ...selection, ...preview ? { preview } : {} };
15789
16224
  }
15790
16225
  async function pipeAgentWebAppStream(raw, stream, options = {}) {
15791
- const projection = createAgentWebAppStreamProjectionContext(options);
16226
+ const projection = (0, import_protocols12.createAgentWebAppStreamProjectionContext)(options);
15792
16227
  await pipeAsyncIterableToSse(raw, stream, {
15793
- project: (chunk) => projectAgentWebAppChunk(chunk, projection),
16228
+ project: (chunk) => (0, import_protocols12.projectAgentWebAppChunk)(chunk, projection),
15794
16229
  onError: () => [
15795
16230
  { type: "error", error: { code: "STREAM_FAILED", message: "Stream failed", retryable: true } },
15796
16231
  { type: "stream.completed" }
15797
16232
  ]
15798
16233
  });
15799
16234
  }
16235
+ async function pipeNativeWebAppStream(raw, stream) {
16236
+ await pipeAsyncIterableToSse(raw, stream, {
16237
+ onError: () => [{
16238
+ type: "error",
16239
+ data: { id: "", content: "Stream failed" }
16240
+ }]
16241
+ });
16242
+ }
15800
16243
  function beginSse(reply) {
15801
16244
  reply.hijack();
15802
16245
  reply.raw.writeHead(200, {
15803
16246
  "Content-Type": "text/event-stream; charset=utf-8",
15804
16247
  "Cache-Control": "no-cache, no-transform",
15805
16248
  Connection: "keep-alive",
15806
- "X-Accel-Buffering": "no"
16249
+ "X-Accel-Buffering": "no",
16250
+ "Access-Control-Allow-Origin": "*"
15807
16251
  });
15808
16252
  }
15809
16253
  async function handle2(reply, operation) {
@@ -15825,7 +16269,7 @@ function isMultipartRequestError(error) {
15825
16269
  }
15826
16270
 
15827
16271
  // src/services/agent-web-app/AgentWebAppAttachmentRef.ts
15828
- var import_node_crypto7 = require("crypto");
16272
+ var import_node_crypto9 = require("crypto");
15829
16273
  var TTL_MS = 15 * 6e4;
15830
16274
  function resolveAgentWebAppAttachmentSecret(consoleSecret) {
15831
16275
  return process.env.WEB_APP_ATTACHMENT_SECRET || process.env.AUTH_TOKEN_SECRET || process.env.JWT_SECRET || consoleSecret;
@@ -15834,7 +16278,7 @@ var AgentWebAppAttachmentRefService = class {
15834
16278
  constructor(secret, now = Date.now) {
15835
16279
  this.now = now;
15836
16280
  if (!secret) throw new Error("Attachment signing secret is required");
15837
- this.key = (0, import_node_crypto7.createHash)("sha256").update(secret, "utf8").digest();
16281
+ this.key = (0, import_node_crypto9.createHash)("sha256").update(secret, "utf8").digest();
15838
16282
  }
15839
16283
  sign(input) {
15840
16284
  const payload = {
@@ -15849,8 +16293,8 @@ var AgentWebAppAttachmentRefService = class {
15849
16293
  contentDigest: input.contentDigest,
15850
16294
  exp: this.now() + TTL_MS
15851
16295
  };
15852
- const iv = (0, import_node_crypto7.randomBytes)(12);
15853
- const cipher = (0, import_node_crypto7.createCipheriv)("aes-256-gcm", this.key, iv);
16296
+ const iv = (0, import_node_crypto9.randomBytes)(12);
16297
+ const cipher = (0, import_node_crypto9.createCipheriv)("aes-256-gcm", this.key, iv);
15854
16298
  const ciphertext = Buffer.concat([
15855
16299
  cipher.update(JSON.stringify(payload), "utf8"),
15856
16300
  cipher.final()
@@ -15865,7 +16309,7 @@ var AgentWebAppAttachmentRefService = class {
15865
16309
  const iv = decodeCanonical(encodedIv, 12);
15866
16310
  const ciphertext = decodeCanonical(encodedCiphertext);
15867
16311
  const tag = decodeCanonical(encodedTag, 16);
15868
- const decipher = (0, import_node_crypto7.createDecipheriv)("aes-256-gcm", this.key, iv);
16312
+ const decipher = (0, import_node_crypto9.createDecipheriv)("aes-256-gcm", this.key, iv);
15869
16313
  decipher.setAuthTag(tag);
15870
16314
  const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
15871
16315
  const value = JSON.parse(plaintext.toString("utf8"));
@@ -15896,7 +16340,7 @@ function decodeCanonical(value, expectedBytes) {
15896
16340
  return decoded;
15897
16341
  }
15898
16342
  function digestUser(userId) {
15899
- return (0, import_node_crypto7.createHash)("sha256").update(userId, "utf8").digest("hex");
16343
+ return (0, import_node_crypto9.createHash)("sha256").update(userId, "utf8").digest("hex");
15900
16344
  }
15901
16345
  function isAttachmentPayload(value) {
15902
16346
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -16260,12 +16704,12 @@ function registerCapabilityBundleRoutes(app2, controller) {
16260
16704
  }
16261
16705
 
16262
16706
  // src/services/collaboration/ProjectRoomService.ts
16263
- var import_node_crypto8 = require("crypto");
16707
+ var import_node_crypto10 = require("crypto");
16264
16708
  var ProjectRoomService = class {
16265
16709
  /** Creates a project room service from explicitly injected collaborators. */
16266
16710
  constructor(deps) {
16267
16711
  this.deps = deps;
16268
- this.idFactory = deps.idFactory ?? import_node_crypto8.randomUUID;
16712
+ this.idFactory = deps.idFactory ?? import_node_crypto10.randomUUID;
16269
16713
  }
16270
16714
  /** Authorizes read access and atomically ensures the project's main room. */
16271
16715
  async ensureMainRoom(input) {
@@ -16284,7 +16728,7 @@ var ProjectRoomService = class {
16284
16728
  };
16285
16729
 
16286
16730
  // src/services/collaboration/ProjectMembershipService.ts
16287
- var import_node_crypto9 = require("crypto");
16731
+ var import_node_crypto11 = require("crypto");
16288
16732
  var ProjectMembershipServiceError = class extends Error {
16289
16733
  constructor(code, statusCode, message) {
16290
16734
  super(message);
@@ -16300,7 +16744,7 @@ function isMembershipConflict(error) {
16300
16744
  var ProjectMembershipService = class {
16301
16745
  constructor(deps) {
16302
16746
  this.deps = deps;
16303
- this.idFactory = deps.idFactory ?? import_node_crypto9.randomUUID;
16747
+ this.idFactory = deps.idFactory ?? import_node_crypto11.randomUUID;
16304
16748
  this.mutationMutex = deps.projectMutationMutex ?? import_core41.projectMutationMutex;
16305
16749
  }
16306
16750
  async list(actor2) {
@@ -16426,7 +16870,7 @@ var ProjectMembershipService = class {
16426
16870
  };
16427
16871
 
16428
16872
  // src/services/collaboration/ProjectRosterService.ts
16429
- var import_node_crypto10 = require("crypto");
16873
+ var import_node_crypto12 = require("crypto");
16430
16874
  var ProjectRosterServiceError = class extends Error {
16431
16875
  /** Creates a stable roster service error. */
16432
16876
  constructor(code, statusCode, message) {
@@ -16443,7 +16887,7 @@ var ProjectRosterService = class {
16443
16887
  /** Creates a roster service from explicitly injected collaborators. */
16444
16888
  constructor(deps) {
16445
16889
  this.deps = deps;
16446
- this.idFactory = deps.idFactory ?? import_node_crypto10.randomUUID;
16890
+ this.idFactory = deps.idFactory ?? import_node_crypto12.randomUUID;
16447
16891
  this.mutationMutex = deps.projectMutationMutex ?? import_core41.projectMutationMutex;
16448
16892
  }
16449
16893
  /** Lists all bot memberships visible to a project reader. */
@@ -16663,7 +17107,7 @@ var ProjectRosterService = class {
16663
17107
  };
16664
17108
 
16665
17109
  // src/services/collaboration/RoomMessageService.ts
16666
- var import_node_crypto11 = require("crypto");
17110
+ var import_node_crypto13 = require("crypto");
16667
17111
 
16668
17112
  // src/services/collaboration/ProjectBotMembershipSnapshot.ts
16669
17113
  var import_protocols13 = require("@axiom-lattice/protocols");
@@ -16752,7 +17196,7 @@ var RoomMessageService = class {
16752
17196
  constructor(deps, options = {}) {
16753
17197
  this.deps = deps;
16754
17198
  this.idempotentOutcomes = /* @__PURE__ */ new Map();
16755
- this.idFactory = deps.idFactory ?? import_node_crypto11.randomUUID;
17199
+ this.idFactory = deps.idFactory ?? import_node_crypto13.randomUUID;
16756
17200
  const limit = options.idempotentOutcomeLimit ?? 1e3;
16757
17201
  if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError("idempotentOutcomeLimit must be a positive safe integer");
16758
17202
  this.idempotentOutcomeLimit = limit;
@@ -16933,7 +17377,7 @@ var RoomMessageService = class {
16933
17377
  let idempotencyKey;
16934
17378
  if (value.requestIdempotencyKey !== void 0) {
16935
17379
  if (typeof value.requestIdempotencyKey !== "string" || !/^[\x21-\x7e]{1,200}$/.test(value.requestIdempotencyKey.trim())) throw new RoomMessageServiceError("INVALID_MESSAGE", 400, "Invalid idempotency key");
16936
- idempotencyKey = `room-human:${actor2.userId}:${(0, import_node_crypto11.createHash)("sha256").update(value.requestIdempotencyKey.trim(), "utf8").digest("hex")}`;
17380
+ idempotencyKey = `room-human:${actor2.userId}:${(0, import_node_crypto13.createHash)("sha256").update(value.requestIdempotencyKey.trim(), "utf8").digest("hex")}`;
16937
17381
  }
16938
17382
  return { text, mentions, botIds, ...value.replyToMessageId === void 0 ? {} : { replyToMessageId: value.replyToMessageId }, ...idempotencyKey === void 0 ? {} : { idempotencyKey } };
16939
17383
  }
@@ -16990,7 +17434,7 @@ function cloneRoomMessage(message) {
16990
17434
  };
16991
17435
  }
16992
17436
  function fingerprintRequest(input) {
16993
- return (0, import_node_crypto11.createHash)("sha256").update(JSON.stringify({
17437
+ return (0, import_node_crypto13.createHash)("sha256").update(JSON.stringify({
16994
17438
  text: input.text,
16995
17439
  mentions: input.mentions,
16996
17440
  replyToMessageId: input.replyToMessageId ?? null
@@ -17159,7 +17603,7 @@ async function execute(reply, operation) {
17159
17603
  }
17160
17604
 
17161
17605
  // src/routes/cospace.ts
17162
- var import_node_crypto12 = require("crypto");
17606
+ var import_node_crypto14 = require("crypto");
17163
17607
  var import_zod12 = require("zod");
17164
17608
  var import_core44 = require("@axiom-lattice/core");
17165
17609
  var CoSpaceAuthError = class extends Error {
@@ -17177,7 +17621,7 @@ function getActor2(request) {
17177
17621
  return { tenantId: user.tenantId, userId: user.id };
17178
17622
  }
17179
17623
  function registerCoSpaceRoutes(app2, deps) {
17180
- const idFactory = deps.idFactory ?? (() => (0, import_node_crypto12.randomUUID)());
17624
+ const idFactory = deps.idFactory ?? (() => (0, import_node_crypto14.randomUUID)());
17181
17625
  app2.get(
17182
17626
  "/api/workspaces/:workspaceId/rooms",
17183
17627
  async (request, reply) => {
@@ -18040,7 +18484,7 @@ function exactValues(value, required, optional = []) {
18040
18484
  }
18041
18485
 
18042
18486
  // src/services/collaboration/CoordinatorWakeNotifier.ts
18043
- var import_node_crypto13 = require("crypto");
18487
+ var import_node_crypto15 = require("crypto");
18044
18488
  var import_core45 = require("@axiom-lattice/core");
18045
18489
  var import_protocols15 = require("@axiom-lattice/protocols");
18046
18490
  var SYNTHETIC_WAKE_AUTHOR = "coordinator-wake";
@@ -18055,7 +18499,7 @@ var CoordinatorWakeNotifier = class {
18055
18499
  this.accepted = /* @__PURE__ */ new Set();
18056
18500
  this.maxPending = deps.maxPending ?? 1024;
18057
18501
  if (!Number.isSafeInteger(this.maxPending) || this.maxPending < 1) throw new RangeError("maxPending must be a positive safe integer");
18058
- this.idFactory = deps.idFactory ?? import_node_crypto13.randomUUID;
18502
+ this.idFactory = deps.idFactory ?? import_node_crypto15.randomUUID;
18059
18503
  }
18060
18504
  /** Admits one notification without awaiting any store or dispatch operation. */
18061
18505
  handle(value) {
@@ -18918,7 +19362,7 @@ function validRecoveryRow(row, task, thread) {
18918
19362
  }
18919
19363
 
18920
19364
  // src/services/collaboration/ProjectTaskObserver.ts
18921
- var import_node_crypto14 = require("crypto");
19365
+ var import_node_crypto16 = require("crypto");
18922
19366
  var import_core47 = require("@axiom-lattice/core");
18923
19367
  var SYSTEM_ACTOR2 = "system";
18924
19368
  var TERMINAL_ACTIONS = ["completed", "failed", "interrupted", "cancelled"];
@@ -19455,7 +19899,7 @@ var ProjectTaskObserver = class {
19455
19899
  action: "activity",
19456
19900
  summary: "Project task recovery warning",
19457
19901
  detail: { type: "project_task_recovery_warning", phase, message },
19458
- eventKey: `project-task-recovery-warning:${task.id}:${phase}:${(0, import_node_crypto14.createHash)("sha256").update(message).digest("hex")}`
19902
+ eventKey: `project-task-recovery-warning:${task.id}:${phase}:${(0, import_node_crypto16.createHash)("sha256").update(message).digest("hex")}`
19459
19903
  });
19460
19904
  }
19461
19905
  async safeWarning(task, phase, error) {
@@ -19522,7 +19966,7 @@ var ProjectTaskObserver = class {
19522
19966
  }
19523
19967
  }
19524
19968
  async persistOverflowWarning(event) {
19525
- const scopeDigest = (0, import_node_crypto14.createHash)("sha256").update(JSON.stringify([event.tenantId, event.workspaceId, event.projectId])).digest("hex");
19969
+ const scopeDigest = (0, import_node_crypto16.createHash)("sha256").update(JSON.stringify([event.tenantId, event.workspaceId, event.projectId])).digest("hex");
19526
19970
  try {
19527
19971
  await this.deps.workItems.createIfAbsentByEventKey({
19528
19972
  tenantId: event.tenantId,
@@ -19623,7 +20067,7 @@ function combineSignals(left, right) {
19623
20067
  }
19624
20068
 
19625
20069
  // src/services/collaboration/RoomProjectionService.ts
19626
- var import_node_crypto15 = require("crypto");
20070
+ var import_node_crypto17 = require("crypto");
19627
20071
  var import_core48 = require("@axiom-lattice/core");
19628
20072
  var import_protocols18 = require("@axiom-lattice/protocols");
19629
20073
  var RoomProjectionService = class {
@@ -19792,7 +20236,7 @@ function safeRepairIdentity(value, scope) {
19792
20236
  return taskId && eventKey && tenantId === scope.tenantId && workspaceId === scope.workspaceId && projectId === scope.projectId ? { taskId, eventKey } : void 0;
19793
20237
  }
19794
20238
  function tupleKey(prefix, values) {
19795
- const digest = (0, import_node_crypto15.createHash)("sha256").update(JSON.stringify(values)).digest("hex");
20239
+ const digest = (0, import_node_crypto17.createHash)("sha256").update(JSON.stringify(values)).digest("hex");
19796
20240
  return `${prefix}:${digest}`;
19797
20241
  }
19798
20242
  function parseProjectionInput(taskValue, itemValue) {
@@ -19820,7 +20264,7 @@ function snapshotProjectableWorkItem(value) {
19820
20264
  if (!row || !createdAt || typeof row.id !== "string" || !row.id || typeof row.taskId !== "string" || !row.taskId || typeof row.tenantId !== "string" || !row.tenantId || typeof row.workspaceId !== "string" || !row.workspaceId || typeof row.projectId !== "string" || !row.projectId || typeof row.actor !== "string" || !row.actor || typeof row.eventKey !== "string" || !row.eventKey || !import_protocols18.PROJECT_TASK_LIFECYCLE_ACTIONS.includes(row.action) || row.threadId !== void 0 && typeof row.threadId !== "string" || row.summary !== void 0 && typeof row.summary !== "string" || row.attempt !== void 0 && (typeof row.attempt !== "number" || !Number.isFinite(row.attempt))) return void 0;
19821
20265
  try {
19822
20266
  const detail = row.detail === void 0 ? void 0 : snapshotJsonValue(row.detail);
19823
- if (detail !== void 0 && !isRecord4(detail)) return void 0;
20267
+ if (detail !== void 0 && !isRecord3(detail)) return void 0;
19824
20268
  return {
19825
20269
  id: row.id,
19826
20270
  taskId: row.taskId,
@@ -19967,7 +20411,7 @@ function snapshotLifecycleCursor(value) {
19967
20411
  const createdAt = cloneExactDate(row?.createdAt);
19968
20412
  return row && typeof row.id === "string" && row.id.length > 0 && createdAt ? { id: row.id, createdAt } : void 0;
19969
20413
  }
19970
- function isRecord4(value) {
20414
+ function isRecord3(value) {
19971
20415
  return typeof value === "object" && value !== null && !Array.isArray(value);
19972
20416
  }
19973
20417
 
@@ -19975,7 +20419,7 @@ function isRecord4(value) {
19975
20419
  var import_core50 = require("@axiom-lattice/core");
19976
20420
 
19977
20421
  // src/services/collaboration/ProjectRoomEventBroker.ts
19978
- var import_node_crypto16 = require("crypto");
20422
+ var import_node_crypto18 = require("crypto");
19979
20423
  var import_protocols19 = require("@axiom-lattice/protocols");
19980
20424
  var DEFAULT_MAX_EVENTS_PER_ROOM = 1e3;
19981
20425
  var DEFAULT_MAX_EVENT_AGE_MS = 10 * 60 * 1e3;
@@ -20000,7 +20444,7 @@ var ProjectRoomEventBroker = class {
20000
20444
  this.subscriberCount = 0;
20001
20445
  this.usageClock = 0;
20002
20446
  this.closed = false;
20003
- this.epoch = options.epoch ?? (0, import_node_crypto16.randomUUID)();
20447
+ this.epoch = options.epoch ?? (0, import_node_crypto18.randomUUID)();
20004
20448
  if (!UUID_PATTERN.test(this.epoch)) {
20005
20449
  throw new TypeError("Project Room event epoch must be a lowercase canonical UUID");
20006
20450
  }
@@ -20318,7 +20762,7 @@ function snapshotJson(value, ancestors = /* @__PURE__ */ new Set()) {
20318
20762
  }
20319
20763
 
20320
20764
  // src/services/collaboration/ProjectRoomRealtimePublisher.ts
20321
- var import_node_crypto17 = require("crypto");
20765
+ var import_node_crypto19 = require("crypto");
20322
20766
  var import_protocols20 = require("@axiom-lattice/protocols");
20323
20767
  var BrokerProjectRoomRealtimePublisher = class {
20324
20768
  /** Creates a publisher around a broker and structured warning logger. */
@@ -20394,7 +20838,7 @@ var BrokerProjectRoomRealtimePublisher = class {
20394
20838
  try {
20395
20839
  event = build();
20396
20840
  if (!event) throw new Error("Invalid Project Room realtime projection");
20397
- const dedupeKey = (0, import_node_crypto17.createHash)("sha256").update(JSON.stringify([type, event.scope.tenantId, event.scope.projectId, event.scope.roomId, event.sourceKey])).digest("hex");
20841
+ const dedupeKey = (0, import_node_crypto19.createHash)("sha256").update(JSON.stringify([type, event.scope.tenantId, event.scope.projectId, event.scope.roomId, event.sourceKey])).digest("hex");
20398
20842
  if (this.sourceKeys.has(dedupeKey)) return;
20399
20843
  this.deps.broker.publish(event.scope, event.draft);
20400
20844
  this.sourceKeys.set(dedupeKey, true);
@@ -21333,7 +21777,7 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
21333
21777
  });
21334
21778
  }
21335
21779
  if (projectTaskComposition) app2.addHook("onClose", async () => projectTaskComposition.lease.release());
21336
- const taskThreadService = projectTaskComposition?.lease.runtime.threads ?? new ProjectTaskThreadService({ threads: threadStore, idFactory: import_node_crypto18.randomUUID });
21780
+ const taskThreadService = projectTaskComposition?.lease.runtime.threads ?? new ProjectTaskThreadService({ threads: threadStore, idFactory: import_node_crypto20.randomUUID });
21337
21781
  const taskLifecycle = projectTaskComposition?.lease.runtime.lifecycle ?? (0, import_core51.createTaskLifecycleService)({ taskStore, workItemStore: taskWorkItemStore });
21338
21782
  const reassignmentAuthorizer = (0, import_core51.createProjectTaskReassignmentAuthorizer)(
21339
21783
  new import_core51.ProjectTaskOwnerPolicy(projectBotMembershipStore),
@@ -21416,6 +21860,46 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
21416
21860
  })
21417
21861
  });
21418
21862
  registerAgentWebAppRuntimeRoutes(app2, {
21863
+ workspaceController: new WorkspaceController(createProjectCollaborationInitializer({
21864
+ membershipStore: projectMembershipStore,
21865
+ roomStore: projectRoomStore,
21866
+ userTenantLinkStore
21867
+ })),
21868
+ taskController: createTaskController({
21869
+ access: {
21870
+ requireProjectAccess: async (input) => {
21871
+ const apps = await webAppStore.list(input.tenantId);
21872
+ const allowed = apps.some((app3) => app3.status === "active" && app3.scope.allowedProjectIds.includes(input.projectId));
21873
+ if (!allowed) throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project is not exposed through this web app");
21874
+ const project = await projectStore.getProjectById(input.tenantId, input.projectId);
21875
+ if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
21876
+ return {
21877
+ project,
21878
+ membership: {
21879
+ id: `webapp_guest_${input.projectId}_${input.userId}`,
21880
+ tenantId: input.tenantId,
21881
+ projectId: input.projectId,
21882
+ userId: input.userId,
21883
+ role: "member",
21884
+ status: "active",
21885
+ joinedAt: /* @__PURE__ */ new Date(),
21886
+ updatedAt: /* @__PURE__ */ new Date()
21887
+ }
21888
+ };
21889
+ }
21890
+ },
21891
+ tasks: taskStore,
21892
+ workItems: taskWorkItemStore,
21893
+ bots: projectBotMembershipStore,
21894
+ rooms: projectRoomStore,
21895
+ threadService: taskThreadService,
21896
+ reassignmentAuthorizer,
21897
+ lifecycle: taskLifecycle,
21898
+ projectMutationMutex: import_core51.projectMutationMutex,
21899
+ executionMutex: projectTaskComposition?.mutex ?? {
21900
+ runExclusive: (_scope, operation) => operation()
21901
+ }
21902
+ }),
21419
21903
  service: new AgentWebAppRuntimeService({
21420
21904
  webAppStore,
21421
21905
  threadStore: (0, import_core51.getStoreLattice)("default", "thread").store,
@@ -21423,7 +21907,7 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
21423
21907
  assistantResolver: {
21424
21908
  resolve: async (tenantId, assistantId) => {
21425
21909
  const stored = await assistantStore.getAssistantById(tenantId, assistantId);
21426
- if (stored) return stored;
21910
+ if (stored) return { id: stored.id, name: stored.name, description: stored.description };
21427
21911
  await import_core51.agentLatticeManager.initializeStoredAssistantsForTenant(tenantId);
21428
21912
  const config = import_core51.agentLatticeManager.getAgentConfigWithTenant(tenantId, assistantId);
21429
21913
  return config ? { id: assistantId, name: config.name, description: config.description } : null;
@@ -21837,7 +22321,7 @@ function tryCreateProjectTaskRuntime(input) {
21837
22321
  function createRuntime(input, stores, log2) {
21838
22322
  const controller = new AbortController();
21839
22323
  const mutex = new ProjectTaskExecutionMutex();
21840
- const threads = new ProjectTaskThreadService({ threads: input.threadStore, idFactory: import_node_crypto18.randomUUID });
22324
+ const threads = new ProjectTaskThreadService({ threads: input.threadStore, idFactory: import_node_crypto20.randomUUID });
21841
22325
  const lifecycle = (0, import_core51.createTaskLifecycleService)({ taskStore: input.taskStore, workItemStore: input.taskWorkItemStore });
21842
22326
  const projection = new RoomProjectionService({
21843
22327
  messages: input.projectRoomMessageStore,
@@ -21846,7 +22330,7 @@ function createRuntime(input, stores, log2) {
21846
22330
  warnings: input.taskWorkItemStore,
21847
22331
  log: log2,
21848
22332
  realtime: input.realtime,
21849
- idFactory: import_node_crypto18.randomUUID
22333
+ idFactory: import_node_crypto20.randomUUID
21850
22334
  });
21851
22335
  const agentFor = (task, thread) => import_core51.agentInstanceManager.getAgent({
21852
22336
  tenant_id: task.tenantId,