@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.mjs CHANGED
@@ -6424,14 +6424,202 @@ function registerWorkspaceRoutes(app2, projectCollaboration) {
6424
6424
  "/api/filesystem/browse",
6425
6425
  controller.browseFilesystem.bind(controller)
6426
6426
  );
6427
+ return controller;
6427
6428
  }
6428
6429
 
6430
+ // src/services/collaboration/ProjectAccessService.ts
6431
+ import { randomUUID as randomUUID6 } from "crypto";
6432
+ var ProjectCollaborationError = class extends Error {
6433
+ /** Creates a project collaboration error. */
6434
+ constructor(code, statusCode, message) {
6435
+ super(message);
6436
+ this.code = code;
6437
+ this.statusCode = statusCode;
6438
+ this.name = "ProjectCollaborationError";
6439
+ }
6440
+ };
6441
+ var allowedRoles = {
6442
+ read: /* @__PURE__ */ new Set(["owner", "admin", "member", "viewer"]),
6443
+ send: /* @__PURE__ */ new Set(["owner", "admin", "member"]),
6444
+ manage: /* @__PURE__ */ new Set(["owner", "admin"])
6445
+ };
6446
+ var membershipConflictNames = /* @__PURE__ */ new Set([
6447
+ "DuplicateProjectMembershipError",
6448
+ "ProjectMembershipIdConflictError"
6449
+ ]);
6450
+ function isProjectMembershipConflict(error) {
6451
+ return error instanceof Error && membershipConflictNames.has(error.name);
6452
+ }
6453
+ var ProjectAccessService = class {
6454
+ /** Creates a project access service from explicitly injected stores. */
6455
+ constructor(deps) {
6456
+ this.deps = deps;
6457
+ this.idFactory = deps.idFactory ?? randomUUID6;
6458
+ }
6459
+ /** Returns the project and active membership when the user has the requested capability. */
6460
+ async requireProjectAccess(input) {
6461
+ const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
6462
+ if (!project) {
6463
+ throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
6464
+ }
6465
+ if (project.kind === "public") {
6466
+ return this.requirePublicProjectAccess(project, input);
6467
+ }
6468
+ const membership = await this.deps.membershipStore.findByUser(
6469
+ input.tenantId,
6470
+ input.projectId,
6471
+ input.userId
6472
+ );
6473
+ if (membership?.status === "active") {
6474
+ this.requireCapability(membership, input.capability);
6475
+ return { project, membership };
6476
+ }
6477
+ const memberships = await this.deps.membershipStore.list(input.tenantId, input.projectId);
6478
+ if (memberships.length !== 0) this.accessDenied();
6479
+ if (project.kind === "personal") this.accessDenied();
6480
+ const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
6481
+ if (tenantLink?.role !== "owner" && tenantLink?.role !== "admin") this.accessDenied();
6482
+ let claim;
6483
+ try {
6484
+ claim = await this.deps.membershipStore.createInitialOwner({
6485
+ id: this.idFactory(),
6486
+ tenantId: input.tenantId,
6487
+ projectId: input.projectId,
6488
+ userId: input.userId
6489
+ });
6490
+ } catch (error) {
6491
+ if (isProjectMembershipConflict(error)) {
6492
+ throw new ProjectCollaborationError(
6493
+ "PROJECT_MEMBERSHIP_CONFLICT",
6494
+ 409,
6495
+ "Project membership could not be initialized"
6496
+ );
6497
+ }
6498
+ throw error;
6499
+ }
6500
+ if (claim.kind === "created" || claim.kind === "existing") {
6501
+ return { project, membership: claim.membership };
6502
+ }
6503
+ const racedMembership = await this.deps.membershipStore.findByUser(
6504
+ input.tenantId,
6505
+ input.projectId,
6506
+ input.userId
6507
+ );
6508
+ if (racedMembership?.status !== "active") this.accessDenied();
6509
+ this.requireCapability(racedMembership, input.capability);
6510
+ return { project, membership: racedMembership };
6511
+ }
6512
+ /** Authorizes an already-existing membership, or any tenant member for public rooms; never mutates persistence. */
6513
+ async requireExistingProjectAccess(input) {
6514
+ const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
6515
+ if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
6516
+ if (project.kind === "public") {
6517
+ return this.requireExistingPublicProjectAccess(project, input);
6518
+ }
6519
+ const membership = await this.deps.membershipStore.findByUser(
6520
+ input.tenantId,
6521
+ input.projectId,
6522
+ input.userId
6523
+ );
6524
+ if (!membership || membership.tenantId !== input.tenantId || membership.projectId !== input.projectId || membership.userId !== input.userId || membership.status !== "active") this.accessDenied();
6525
+ this.requireCapability(membership, input.capability);
6526
+ return { project, membership };
6527
+ }
6528
+ /** Authorizes roster visibility: read for public rooms, manage elsewhere. */
6529
+ async requireRosterAccess(input) {
6530
+ const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
6531
+ if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
6532
+ return this.requireProjectAccess({
6533
+ ...input,
6534
+ capability: project.kind === "public" ? "read" : "manage"
6535
+ });
6536
+ }
6537
+ async requirePublicProjectAccess(project, input) {
6538
+ const membership = await this.deps.membershipStore.findByUser(
6539
+ input.tenantId,
6540
+ input.projectId,
6541
+ input.userId
6542
+ );
6543
+ if (membership?.status === "active") {
6544
+ this.requireCapability(membership, input.capability);
6545
+ return { project, membership };
6546
+ }
6547
+ const materialized = await this.materializePublicMembership(input);
6548
+ this.requireCapability(materialized, input.capability);
6549
+ return { project, membership: materialized };
6550
+ }
6551
+ async requireExistingPublicProjectAccess(project, input) {
6552
+ const membership = await this.deps.membershipStore.findByUser(
6553
+ input.tenantId,
6554
+ input.projectId,
6555
+ input.userId
6556
+ );
6557
+ if (membership?.status === "active") {
6558
+ this.requireCapability(membership, input.capability);
6559
+ return { project, membership };
6560
+ }
6561
+ if (membership) this.accessDenied();
6562
+ const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
6563
+ if (!tenantLink) this.accessDenied();
6564
+ const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
6565
+ const synthetic = {
6566
+ id: `public:${input.userId}`,
6567
+ tenantId: input.tenantId,
6568
+ projectId: input.projectId,
6569
+ userId: input.userId,
6570
+ role,
6571
+ status: "active",
6572
+ joinedAt: project.createdAt,
6573
+ updatedAt: project.updatedAt
6574
+ };
6575
+ this.requireCapability(synthetic, input.capability);
6576
+ return { project, membership: synthetic };
6577
+ }
6578
+ async materializePublicMembership(input) {
6579
+ const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
6580
+ if (!tenantLink) this.accessDenied();
6581
+ const existing = await this.deps.membershipStore.findByUser(
6582
+ input.tenantId,
6583
+ input.projectId,
6584
+ input.userId
6585
+ );
6586
+ if (existing) this.accessDenied();
6587
+ const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
6588
+ try {
6589
+ return await this.deps.membershipStore.create({
6590
+ id: this.idFactory(),
6591
+ tenantId: input.tenantId,
6592
+ projectId: input.projectId,
6593
+ userId: input.userId,
6594
+ role,
6595
+ status: "active"
6596
+ });
6597
+ } catch (error) {
6598
+ if (isProjectMembershipConflict(error)) {
6599
+ const raced = await this.deps.membershipStore.findByUser(
6600
+ input.tenantId,
6601
+ input.projectId,
6602
+ input.userId
6603
+ );
6604
+ if (raced?.status === "active") return raced;
6605
+ }
6606
+ throw error;
6607
+ }
6608
+ }
6609
+ requireCapability(membership, capability2) {
6610
+ if (!allowedRoles[capability2].has(membership.role)) this.accessDenied();
6611
+ }
6612
+ accessDenied() {
6613
+ throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project access denied");
6614
+ }
6615
+ };
6616
+
6429
6617
  // src/controllers/database-configs.ts
6430
6618
  import {
6431
6619
  getStoreLattice as getStoreLattice7,
6432
6620
  sqlDatabaseManager
6433
6621
  } from "@axiom-lattice/core";
6434
- import { randomUUID as randomUUID6 } from "crypto";
6622
+ import { randomUUID as randomUUID7 } from "crypto";
6435
6623
  function getTenantId9(request) {
6436
6624
  const userTenantId = request.user?.tenantId;
6437
6625
  if (userTenantId) {
@@ -6509,7 +6697,7 @@ async function createDatabaseConfig(request, reply) {
6509
6697
  message: "Database configuration with this key already exists"
6510
6698
  };
6511
6699
  }
6512
- const id3 = body.id || randomUUID6();
6700
+ const id3 = body.id || randomUUID7();
6513
6701
  const config = await store.createConfig(tenantId, id3, body);
6514
6702
  try {
6515
6703
  sqlDatabaseManager.registerDatabase(tenantId, config.key, config.config);
@@ -6714,7 +6902,7 @@ import {
6714
6902
  metricsServerManager as metricsServerManager2,
6715
6903
  SemanticMetricsClient as SemanticMetricsClient2
6716
6904
  } from "@axiom-lattice/core";
6717
- import { randomUUID as randomUUID7 } from "crypto";
6905
+ import { randomUUID as randomUUID8 } from "crypto";
6718
6906
  function getTenantId10(request) {
6719
6907
  const userTenantId = request.user?.tenantId;
6720
6908
  if (userTenantId) {
@@ -6795,7 +6983,7 @@ async function createMetricsServerConfig(request, reply) {
6795
6983
  message: "selectedDataSources is required for semantic metrics servers"
6796
6984
  };
6797
6985
  }
6798
- const id3 = body.id || randomUUID7();
6986
+ const id3 = body.id || randomUUID8();
6799
6987
  const configData = {
6800
6988
  key: body.key,
6801
6989
  name: body.name,
@@ -9302,7 +9490,7 @@ function registerChannelRoutes(app2, dependencies) {
9302
9490
  }
9303
9491
 
9304
9492
  // src/controllers/channel-installations.ts
9305
- import { randomUUID as randomUUID8 } from "crypto";
9493
+ import { randomUUID as randomUUID9 } from "crypto";
9306
9494
  import {
9307
9495
  descriptorDataValue
9308
9496
  } from "@axiom-lattice/protocols";
@@ -9506,7 +9694,7 @@ async function createChannelInstallation(request, reply) {
9506
9694
  };
9507
9695
  }
9508
9696
  const store = await getInstallationStore();
9509
- const installationId = randomUUID8();
9697
+ const installationId = randomUUID9();
9510
9698
  const createInput = {
9511
9699
  channel: body.channel,
9512
9700
  name: body.name,
@@ -10466,7 +10654,7 @@ async function getLocalA2ATemplates(_request, reply) {
10466
10654
  }
10467
10655
 
10468
10656
  // src/routes/a2a-test-connection.ts
10469
- import { randomUUID as randomUUID9 } from "crypto";
10657
+ import { randomUUID as randomUUID10 } from "crypto";
10470
10658
  function describeTestConnectionError(error, agentCardUrl) {
10471
10659
  if (!(error instanceof Error)) {
10472
10660
  return `Failed to fetch agent card from ${agentCardUrl}: ${String(error)}`;
@@ -10506,7 +10694,7 @@ async function ensureLocalRuntimeForConnectionTest(localRuntime, agentCardUrl) {
10506
10694
  return { error: portValidation.error };
10507
10695
  }
10508
10696
  const state = await localA2AService.startProvider(
10509
- `test-${randomUUID9()}`,
10697
+ `test-${randomUUID10()}`,
10510
10698
  testRuntime
10511
10699
  );
10512
10700
  if (state.status === "running") return { runtimeId: state.runtimeId };
@@ -10523,264 +10711,77 @@ function registerA2ATestConnectionRoute(app2) {
10523
10711
  const { agentCardUrl, apiKey, localRuntime } = request.body ?? {};
10524
10712
  const normalizedAgentCardUrl = normalizeA2AAgentCardUrl(
10525
10713
  agentCardUrl,
10526
- localRuntime ? "http://" : "https://"
10527
- );
10528
- if (!normalizedAgentCardUrl) {
10529
- return reply.status(400).send({ success: false, error: "agentCardUrl is required" });
10530
- }
10531
- let testRuntimeId = null;
10532
- try {
10533
- const headers = {};
10534
- if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
10535
- if (localRuntime) {
10536
- const localStartResult = await ensureLocalRuntimeForConnectionTest(localRuntime, normalizedAgentCardUrl);
10537
- if ("error" in localStartResult) {
10538
- return reply.send({ success: true, data: { reachable: false, error: localStartResult.error } });
10539
- }
10540
- testRuntimeId = localStartResult.runtimeId;
10541
- }
10542
- const resp = await fetch(normalizedAgentCardUrl, { headers, signal: AbortSignal.timeout(8e3) });
10543
- if (!resp.ok) {
10544
- const body = await readShortResponseBody(resp);
10545
- const suffix = body ? `: ${body}` : "";
10546
- return reply.send({ success: true, data: { reachable: false, error: `HTTP ${resp.status} ${resp.statusText}${suffix}` } });
10547
- }
10548
- const card = await resp.json();
10549
- return reply.send({ success: true, data: { reachable: true, agentName: card.name, agentCard: card } });
10550
- } catch (error) {
10551
- return reply.send({
10552
- success: true,
10553
- data: {
10554
- reachable: false,
10555
- error: describeTestConnectionError(error, normalizedAgentCardUrl)
10556
- }
10557
- });
10558
- } finally {
10559
- if (testRuntimeId) {
10560
- localA2AService.removeState(testRuntimeId);
10561
- }
10562
- }
10563
- });
10564
- }
10565
-
10566
- // src/routes/task-routes.ts
10567
- function registerTaskRoutes(app2, controller) {
10568
- app2.get("/api/tasks", controller.list);
10569
- app2.get("/api/tasks/:id", controller.get);
10570
- app2.post("/api/tasks", controller.create);
10571
- app2.put("/api/tasks/:id", controller.update);
10572
- app2.patch("/api/tasks/:id", controller.update);
10573
- app2.delete("/api/tasks/:id", controller.remove);
10574
- app2.patch("/api/tasks/:id/complete", controller.complete);
10575
- app2.get("/api/tasks/:id/work-items", controller.workItems);
10576
- app2.post("/api/projects/:projectId/tasks/:id/reassign", controller.reassign);
10577
- }
10578
-
10579
- // src/routes/index.ts
10580
- import {
10581
- agentInstanceManager as agentInstanceManager6,
10582
- agentLatticeManager as agentLatticeManager4,
10583
- createProjectTaskReassignmentAuthorizer,
10584
- createTaskLifecycleService,
10585
- createTrustedAgentMessageDispatcher,
10586
- getStoreLattice as getStoreLattice16,
10587
- modelLatticeManager as modelLatticeManager3,
10588
- ProjectTaskOwnerPolicy,
10589
- projectMutationMutex as projectMutationMutex2,
10590
- projectTaskAdmissionNotifier as projectTaskAdmissionNotifier2,
10591
- taskLifecycleNotifier as taskLifecycleNotifier2
10592
- } from "@axiom-lattice/core";
10593
-
10594
- // src/controllers/tasks.ts
10595
- import { snapshotExactRecord as snapshotExactRecord3 } from "@axiom-lattice/protocols";
10596
- import { ProjectTaskOwnerPolicyError, projectTaskAdmissionNotifier } from "@axiom-lattice/core";
10597
-
10598
- // src/services/collaboration/ProjectAccessService.ts
10599
- import { randomUUID as randomUUID10 } from "crypto";
10600
- var ProjectCollaborationError = class extends Error {
10601
- /** Creates a project collaboration error. */
10602
- constructor(code, statusCode, message) {
10603
- super(message);
10604
- this.code = code;
10605
- this.statusCode = statusCode;
10606
- this.name = "ProjectCollaborationError";
10607
- }
10608
- };
10609
- var allowedRoles = {
10610
- read: /* @__PURE__ */ new Set(["owner", "admin", "member", "viewer"]),
10611
- send: /* @__PURE__ */ new Set(["owner", "admin", "member"]),
10612
- manage: /* @__PURE__ */ new Set(["owner", "admin"])
10613
- };
10614
- var membershipConflictNames = /* @__PURE__ */ new Set([
10615
- "DuplicateProjectMembershipError",
10616
- "ProjectMembershipIdConflictError"
10617
- ]);
10618
- function isProjectMembershipConflict(error) {
10619
- return error instanceof Error && membershipConflictNames.has(error.name);
10620
- }
10621
- var ProjectAccessService = class {
10622
- /** Creates a project access service from explicitly injected stores. */
10623
- constructor(deps) {
10624
- this.deps = deps;
10625
- this.idFactory = deps.idFactory ?? randomUUID10;
10626
- }
10627
- /** Returns the project and active membership when the user has the requested capability. */
10628
- async requireProjectAccess(input) {
10629
- const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
10630
- if (!project) {
10631
- throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
10632
- }
10633
- if (project.kind === "public") {
10634
- return this.requirePublicProjectAccess(project, input);
10635
- }
10636
- const membership = await this.deps.membershipStore.findByUser(
10637
- input.tenantId,
10638
- input.projectId,
10639
- input.userId
10640
- );
10641
- if (membership?.status === "active") {
10642
- this.requireCapability(membership, input.capability);
10643
- return { project, membership };
10644
- }
10645
- const memberships = await this.deps.membershipStore.list(input.tenantId, input.projectId);
10646
- if (memberships.length !== 0) this.accessDenied();
10647
- if (project.kind === "personal") this.accessDenied();
10648
- const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
10649
- if (tenantLink?.role !== "owner" && tenantLink?.role !== "admin") this.accessDenied();
10650
- let claim;
10651
- try {
10652
- claim = await this.deps.membershipStore.createInitialOwner({
10653
- id: this.idFactory(),
10654
- tenantId: input.tenantId,
10655
- projectId: input.projectId,
10656
- userId: input.userId
10657
- });
10658
- } catch (error) {
10659
- if (isProjectMembershipConflict(error)) {
10660
- throw new ProjectCollaborationError(
10661
- "PROJECT_MEMBERSHIP_CONFLICT",
10662
- 409,
10663
- "Project membership could not be initialized"
10664
- );
10665
- }
10666
- throw error;
10667
- }
10668
- if (claim.kind === "created" || claim.kind === "existing") {
10669
- return { project, membership: claim.membership };
10670
- }
10671
- const racedMembership = await this.deps.membershipStore.findByUser(
10672
- input.tenantId,
10673
- input.projectId,
10674
- input.userId
10675
- );
10676
- if (racedMembership?.status !== "active") this.accessDenied();
10677
- this.requireCapability(racedMembership, input.capability);
10678
- return { project, membership: racedMembership };
10679
- }
10680
- /** Authorizes an already-existing membership, or any tenant member for public rooms; never mutates persistence. */
10681
- async requireExistingProjectAccess(input) {
10682
- const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
10683
- if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
10684
- if (project.kind === "public") {
10685
- return this.requireExistingPublicProjectAccess(project, input);
10686
- }
10687
- const membership = await this.deps.membershipStore.findByUser(
10688
- input.tenantId,
10689
- input.projectId,
10690
- input.userId
10691
- );
10692
- if (!membership || membership.tenantId !== input.tenantId || membership.projectId !== input.projectId || membership.userId !== input.userId || membership.status !== "active") this.accessDenied();
10693
- this.requireCapability(membership, input.capability);
10694
- return { project, membership };
10695
- }
10696
- /** Authorizes roster visibility: read for public rooms, manage elsewhere. */
10697
- async requireRosterAccess(input) {
10698
- const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
10699
- if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
10700
- return this.requireProjectAccess({
10701
- ...input,
10702
- capability: project.kind === "public" ? "read" : "manage"
10703
- });
10704
- }
10705
- async requirePublicProjectAccess(project, input) {
10706
- const membership = await this.deps.membershipStore.findByUser(
10707
- input.tenantId,
10708
- input.projectId,
10709
- input.userId
10710
- );
10711
- if (membership?.status === "active") {
10712
- this.requireCapability(membership, input.capability);
10713
- return { project, membership };
10714
- }
10715
- const materialized = await this.materializePublicMembership(input);
10716
- this.requireCapability(materialized, input.capability);
10717
- return { project, membership: materialized };
10718
- }
10719
- async requireExistingPublicProjectAccess(project, input) {
10720
- const membership = await this.deps.membershipStore.findByUser(
10721
- input.tenantId,
10722
- input.projectId,
10723
- input.userId
10724
- );
10725
- if (membership?.status === "active") {
10726
- this.requireCapability(membership, input.capability);
10727
- return { project, membership };
10728
- }
10729
- if (membership) this.accessDenied();
10730
- const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
10731
- if (!tenantLink) this.accessDenied();
10732
- const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
10733
- const synthetic = {
10734
- id: `public:${input.userId}`,
10735
- tenantId: input.tenantId,
10736
- projectId: input.projectId,
10737
- userId: input.userId,
10738
- role,
10739
- status: "active",
10740
- joinedAt: project.createdAt,
10741
- updatedAt: project.updatedAt
10742
- };
10743
- this.requireCapability(synthetic, input.capability);
10744
- return { project, membership: synthetic };
10745
- }
10746
- async materializePublicMembership(input) {
10747
- const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
10748
- if (!tenantLink) this.accessDenied();
10749
- const existing = await this.deps.membershipStore.findByUser(
10750
- input.tenantId,
10751
- input.projectId,
10752
- input.userId
10753
- );
10754
- if (existing) this.accessDenied();
10755
- const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
10756
- try {
10757
- return await this.deps.membershipStore.create({
10758
- id: this.idFactory(),
10759
- tenantId: input.tenantId,
10760
- projectId: input.projectId,
10761
- userId: input.userId,
10762
- role,
10763
- status: "active"
10764
- });
10714
+ localRuntime ? "http://" : "https://"
10715
+ );
10716
+ if (!normalizedAgentCardUrl) {
10717
+ return reply.status(400).send({ success: false, error: "agentCardUrl is required" });
10718
+ }
10719
+ let testRuntimeId = null;
10720
+ try {
10721
+ const headers = {};
10722
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
10723
+ if (localRuntime) {
10724
+ const localStartResult = await ensureLocalRuntimeForConnectionTest(localRuntime, normalizedAgentCardUrl);
10725
+ if ("error" in localStartResult) {
10726
+ return reply.send({ success: true, data: { reachable: false, error: localStartResult.error } });
10727
+ }
10728
+ testRuntimeId = localStartResult.runtimeId;
10729
+ }
10730
+ const resp = await fetch(normalizedAgentCardUrl, { headers, signal: AbortSignal.timeout(8e3) });
10731
+ if (!resp.ok) {
10732
+ const body = await readShortResponseBody(resp);
10733
+ const suffix = body ? `: ${body}` : "";
10734
+ return reply.send({ success: true, data: { reachable: false, error: `HTTP ${resp.status} ${resp.statusText}${suffix}` } });
10735
+ }
10736
+ const card = await resp.json();
10737
+ return reply.send({ success: true, data: { reachable: true, agentName: card.name, agentCard: card } });
10765
10738
  } catch (error) {
10766
- if (isProjectMembershipConflict(error)) {
10767
- const raced = await this.deps.membershipStore.findByUser(
10768
- input.tenantId,
10769
- input.projectId,
10770
- input.userId
10771
- );
10772
- if (raced?.status === "active") return raced;
10739
+ return reply.send({
10740
+ success: true,
10741
+ data: {
10742
+ reachable: false,
10743
+ error: describeTestConnectionError(error, normalizedAgentCardUrl)
10744
+ }
10745
+ });
10746
+ } finally {
10747
+ if (testRuntimeId) {
10748
+ localA2AService.removeState(testRuntimeId);
10773
10749
  }
10774
- throw error;
10775
10750
  }
10776
- }
10777
- requireCapability(membership, capability2) {
10778
- if (!allowedRoles[capability2].has(membership.role)) this.accessDenied();
10779
- }
10780
- accessDenied() {
10781
- throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project access denied");
10782
- }
10783
- };
10751
+ });
10752
+ }
10753
+
10754
+ // src/routes/task-routes.ts
10755
+ function registerTaskRoutes(app2, controller) {
10756
+ app2.get("/api/tasks", controller.list);
10757
+ app2.get("/api/tasks/:id", controller.get);
10758
+ app2.post("/api/tasks", controller.create);
10759
+ app2.put("/api/tasks/:id", controller.update);
10760
+ app2.patch("/api/tasks/:id", controller.update);
10761
+ app2.delete("/api/tasks/:id", controller.remove);
10762
+ app2.patch("/api/tasks/:id/complete", controller.complete);
10763
+ app2.get("/api/tasks/:id/work-items", controller.workItems);
10764
+ app2.post("/api/projects/:projectId/tasks/:id/reassign", controller.reassign);
10765
+ }
10766
+
10767
+ // src/routes/index.ts
10768
+ import {
10769
+ agentInstanceManager as agentInstanceManager6,
10770
+ agentLatticeManager as agentLatticeManager4,
10771
+ createProjectTaskReassignmentAuthorizer,
10772
+ createTaskLifecycleService,
10773
+ createTrustedAgentMessageDispatcher,
10774
+ getStoreLattice as getStoreLattice16,
10775
+ modelLatticeManager as modelLatticeManager3,
10776
+ ProjectTaskOwnerPolicy,
10777
+ projectMutationMutex as projectMutationMutex2,
10778
+ projectTaskAdmissionNotifier as projectTaskAdmissionNotifier2,
10779
+ taskLifecycleNotifier as taskLifecycleNotifier2
10780
+ } from "@axiom-lattice/core";
10781
+
10782
+ // src/controllers/tasks.ts
10783
+ import { snapshotExactRecord as snapshotExactRecord3 } from "@axiom-lattice/protocols";
10784
+ import { ProjectTaskOwnerPolicyError, projectTaskAdmissionNotifier } from "@axiom-lattice/core";
10784
10785
 
10785
10786
  // src/services/collaboration/ProjectTaskThreadService.ts
10786
10787
  import { isProjectLifecycleTask } from "@axiom-lattice/core";
@@ -11320,9 +11321,6 @@ function governedUpdate(task, body) {
11320
11321
  if (has("ownerType") && body.ownerType !== task.ownerType || has("ownerId") && body.ownerId !== task.ownerId) {
11321
11322
  return "TASK_OWNERSHIP_IMMUTABLE";
11322
11323
  }
11323
- if (task.ownerType !== "agent") return void 0;
11324
- if (has("parentId") && body.parentId !== task.parentId || has("dependencies") || has("requireReview") || has("result") || has("failureReason") || has("status")) return "TASK_LIFECYCLE_TRANSITION_REQUIRED";
11325
- if (has("context") || has("description")) return "COMPLETION_CONTEXT_MUTATION";
11326
11324
  return void 0;
11327
11325
  }
11328
11326
  function lifecycleStatus(code) {
@@ -11763,7 +11761,8 @@ var AgentWebAppService = class {
11763
11761
  integration: current.integration,
11764
11762
  scope: { ...current.scope, ...patch.scope },
11765
11763
  features: { ...current.features, ...patch.features },
11766
- appearance: { ...current.appearance, ...patch.appearance }
11764
+ appearance: { ...current.appearance, ...patch.appearance },
11765
+ identity: patch.identity ?? current.identity
11767
11766
  };
11768
11767
  await this.validateAssistant(context, current.assistantId);
11769
11768
  const validated = await this.validateConfig(context.tenantId, effective);
@@ -11772,7 +11771,8 @@ var AgentWebAppService = class {
11772
11771
  ...Object.prototype.hasOwnProperty.call(patch, "description") ? { description: validated.description } : {},
11773
11772
  ...patch.scope ? { scope: validated.scope } : {},
11774
11773
  ...patch.features ? { features: validated.features } : {},
11775
- ...patch.appearance ? { appearance: validated.appearance } : {}
11774
+ ...patch.appearance ? { appearance: validated.appearance } : {},
11775
+ ...patch.identity !== void 0 ? { identity: patch.identity } : {}
11776
11776
  };
11777
11777
  return this.writeSnapshot(context, current, update);
11778
11778
  }
@@ -11885,13 +11885,28 @@ var agentWebAppFeaturesSchema = z4.object({
11885
11885
  threadManagement: z4.boolean(),
11886
11886
  attachments: z4.boolean(),
11887
11887
  hitl: z4.boolean(),
11888
- genUI: z4.boolean()
11888
+ genUI: z4.boolean(),
11889
+ files: z4.boolean().optional()
11889
11890
  }).strict();
11890
11891
  var agentWebAppAppearanceSchema = z4.object({
11891
11892
  title: z4.string().max(120).optional(),
11892
11893
  welcomeMessage: z4.string().max(1e3).optional(),
11893
11894
  primaryColor: z4.string().regex(/^#[0-9A-Fa-f]{6}$/).optional()
11894
11895
  }).strict();
11896
+ var agentWebAppIssuerSchema = z4.object({
11897
+ iss: z4.string().min(1).max(255),
11898
+ aud: z4.string().min(1).max(255),
11899
+ alg: z4.enum(["HS256", "RS256"]),
11900
+ secret: z4.string().min(1).max(512).optional(),
11901
+ jwksUrl: z4.string().url().max(2048).optional()
11902
+ }).strict().refine(
11903
+ (value) => value.alg === "HS256" ? value.secret !== void 0 : value.jwksUrl !== void 0,
11904
+ { message: "HS256 requires secret; RS256 requires jwksUrl" }
11905
+ );
11906
+ var agentWebAppIdentitySchema = z4.object({
11907
+ policy: z4.enum(["unverified", "verified", "both"]),
11908
+ issuers: z4.array(agentWebAppIssuerSchema).min(1).max(10)
11909
+ }).strict();
11895
11910
  var createAgentWebAppSchema = z4.object({
11896
11911
  assistantId: externalId,
11897
11912
  name: z4.string().min(1).max(120),
@@ -11899,14 +11914,16 @@ var createAgentWebAppSchema = z4.object({
11899
11914
  integration: z4.object({ type: z4.literal("react_sdk") }).strict(),
11900
11915
  scope: agentWebAppScopeSchema,
11901
11916
  features: agentWebAppFeaturesSchema,
11902
- appearance: agentWebAppAppearanceSchema
11917
+ appearance: agentWebAppAppearanceSchema,
11918
+ identity: agentWebAppIdentitySchema.optional()
11903
11919
  }).strict();
11904
11920
  var updateAgentWebAppSchema = z4.object({
11905
11921
  name: z4.string().min(1).max(120).optional(),
11906
11922
  description: z4.string().max(1e3).optional(),
11907
11923
  scope: agentWebAppScopeSchema.partial().strict().optional(),
11908
11924
  features: agentWebAppFeaturesSchema.partial().strict().optional(),
11909
- appearance: agentWebAppAppearanceSchema.partial().strict().optional()
11925
+ appearance: agentWebAppAppearanceSchema.partial().strict().optional(),
11926
+ identity: agentWebAppIdentitySchema.optional()
11910
11927
  }).strict().refine((value) => Object.keys(value).length > 0, "Patch must not be empty");
11911
11928
  var listAgentWebAppsQuerySchema = z4.object({
11912
11929
  assistantId: externalId.optional()
@@ -11993,6 +12010,7 @@ function invalidRequest(reply, statusCode) {
11993
12010
  }
11994
12011
 
11995
12012
  // src/routes/agent-web-app-runtime.ts
12013
+ import { createHmac as createHmac3 } from "crypto";
11996
12014
  import { z as z5, ZodError as ZodError2 } from "zod";
11997
12015
 
11998
12016
  // src/services/agent-web-app/AgentWebAppRuntimeService.ts
@@ -12013,7 +12031,16 @@ var AgentWebAppRuntimeService = class {
12013
12031
  constructor(deps) {
12014
12032
  this.deps = deps;
12015
12033
  }
12016
- async bootstrap(webAppId, userId, selection) {
12034
+ /** Identity policy configuration for the web app, or null when the app is missing/unconfigured. */
12035
+ async getIdentityConfig(webAppId) {
12036
+ try {
12037
+ const runtime = await this.resolveRuntime(webAppId, {});
12038
+ return runtime.webApp.identity ?? null;
12039
+ } catch {
12040
+ return null;
12041
+ }
12042
+ }
12043
+ async bootstrap(webAppId, userId, selection, identity) {
12017
12044
  const runtime = await this.resolveRuntime(webAppId, selection);
12018
12045
  const assistant = await this.deps.assistantResolver.resolve(
12019
12046
  runtime.webApp.tenantId,
@@ -12046,7 +12073,7 @@ var AgentWebAppRuntimeService = class {
12046
12073
  defaultModelKey: runtime.webApp.scope.defaultModelKey,
12047
12074
  features: runtime.webApp.features,
12048
12075
  appearance: runtime.webApp.appearance,
12049
- identityAssurance: "unverified"
12076
+ identityAssurance: identity?.assurance ?? "unverified"
12050
12077
  },
12051
12078
  projects,
12052
12079
  models
@@ -12067,6 +12094,14 @@ var AgentWebAppRuntimeService = class {
12067
12094
  this.requireFeature(runtime.webApp.features.threadManagement);
12068
12095
  return this.createOwnedThread(runtime, userId, input.label);
12069
12096
  }
12097
+ /** Fetch one owned thread. */
12098
+ async getThread(webAppId, userId, threadId, selection) {
12099
+ const runtime = await this.resolveRuntime(webAppId, selection);
12100
+ await this.requireOwnedThread(runtime, userId, threadId);
12101
+ const thread = await this.deps.threadStore.getThreadById(runtime.webApp.tenantId, threadId);
12102
+ if (!thread) this.threadNotFound();
12103
+ return toRuntimeThread(thread);
12104
+ }
12070
12105
  /** Update a thread label; omitted or blank labels preserve the existing label. */
12071
12106
  async updateThread(webAppId, userId, threadId, input) {
12072
12107
  const runtime = await this.resolveRuntime(webAppId, input);
@@ -12101,6 +12136,60 @@ var AgentWebAppRuntimeService = class {
12101
12136
  return [{ id: message.id, role: message.role, ...content && (typeof content === "string" || content.length > 0) ? { content } : {} }];
12102
12137
  });
12103
12138
  }
12139
+ async getState(webAppId, userId, threadId, selection) {
12140
+ const runtime = await this.resolveRuntime(webAppId, selection);
12141
+ await this.requireOwnedThread(runtime, userId, threadId);
12142
+ const agent = this.resolveAgent(runtime, threadId);
12143
+ const state = await agent.getCurrentState();
12144
+ const pendingMessages = await agent.getPendingMessages();
12145
+ const normalized = pendingMessages.map((message) => ({
12146
+ ...message,
12147
+ content: typeof message.content === "object" && message.content !== null ? "message" in message.content ? message.content.message : JSON.stringify(message.content) : String(message.content ?? "")
12148
+ }));
12149
+ return { ...state && typeof state === "object" ? state : {}, pendingMessages: normalized };
12150
+ }
12151
+ /** Raw stored messages (Console memory contract, tool/interrupt preserved). */
12152
+ async getConsoleMessages(webAppId, userId, threadId, selection) {
12153
+ const runtime = await this.resolveRuntime(webAppId, selection);
12154
+ await this.requireOwnedThread(runtime, userId, threadId);
12155
+ return this.deps.messageReader.getCurrentMessages({
12156
+ tenantId: runtime.webApp.tenantId,
12157
+ assistantId: runtime.webApp.assistantId,
12158
+ threadId,
12159
+ workspaceId: runtime.project.workspaceId,
12160
+ projectId: runtime.project.id
12161
+ });
12162
+ }
12163
+ /** Background enqueue only (ADR-026): returns the message id; delivery happens over resume_stream. */
12164
+ async enqueueMessage(webAppId, userId, threadId, content, selection) {
12165
+ const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, selection);
12166
+ const agent = this.resolveAgent(runtime, threadId);
12167
+ const execution = await this.execute(agent, runtime, { input: { message: content } });
12168
+ return { messageId: execution.messageId };
12169
+ }
12170
+ async removePendingMessage(webAppId, userId, threadId, messageId, selection) {
12171
+ const runtime = await this.resolveRuntime(webAppId, selection);
12172
+ await this.requireOwnedThread(runtime, userId, threadId);
12173
+ const agent = this.resolveAgent(runtime, threadId);
12174
+ if (!await agent.removePendingMessage(messageId)) this.threadNotFound();
12175
+ }
12176
+ /** Thread-lifetime SSE resume from the last known client message id. */
12177
+ async resumeStream(webAppId, userId, threadId, messageId, selection) {
12178
+ const runtime = await this.resolveRuntime(webAppId, selection);
12179
+ await this.requireOwnedThread(runtime, userId, threadId);
12180
+ const agent = this.resolveAgent(runtime, threadId);
12181
+ return { stream: agent.chunkStream(messageId, []) };
12182
+ }
12183
+ async resolveFileScope(webAppId, selection) {
12184
+ const runtime = await this.resolveRuntime(webAppId, selection);
12185
+ return {
12186
+ tenantId: runtime.webApp.tenantId,
12187
+ workspaceId: runtime.project.workspaceId,
12188
+ projectId: runtime.project.id,
12189
+ assistantId: runtime.webApp.assistantId,
12190
+ enabled: runtime.webApp.features.files === true
12191
+ };
12192
+ }
12104
12193
  async streamMessage(webAppId, userId, threadId, input) {
12105
12194
  const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, input);
12106
12195
  const refs = this.resolveAttachmentRefs(runtime, userId, threadId, input.attachmentRefs ?? []);
@@ -12345,96 +12434,102 @@ function toRuntimeThread(thread) {
12345
12434
  };
12346
12435
  }
12347
12436
 
12348
- // src/services/agent-web-app/AgentWebAppStreamProjector.ts
12349
- import {
12350
- MessageChunkTypes as MessageChunkTypes4,
12351
- parseAgentWebAppGenUIBlock as parseAgentWebAppGenUIBlock2
12352
- } from "@axiom-lattice/protocols";
12353
- function createAgentWebAppStreamProjectionContext(options = {}) {
12354
- return { startedToolIds: /* @__PURE__ */ new Set(), allowInterrupts: options.allowInterrupts ?? true, allowGenUI: options.allowGenUI ?? false };
12355
- }
12356
- function projectAgentWebAppChunk(chunk, context) {
12357
- if (chunk.type === MessageChunkTypes4.AI) {
12358
- const events = [];
12359
- const startedToolIds = context?.startedToolIds ?? /* @__PURE__ */ new Set();
12360
- if (typeof chunk.data.content === "string" && chunk.data.content) events.push({ type: "message.delta", text: chunk.data.content });
12361
- if (context?.allowGenUI && Array.isArray(chunk.data.content)) {
12362
- for (const value of chunk.data.content) {
12363
- const block = parseAgentWebAppGenUIBlock2(value);
12364
- if (block) events.push({ type: "genui.render", block });
12365
- }
12366
- }
12367
- for (const call of chunk.data.tool_call_chunks ?? []) {
12368
- if (call.id && call.name && !startedToolIds.has(call.id)) {
12369
- startedToolIds.add(call.id);
12370
- events.push({ type: "tool.started", id: call.id, name: call.name });
12371
- }
12372
- }
12373
- for (const call of chunk.data.tool_calls ?? []) {
12374
- if (!startedToolIds.has(call.id)) {
12375
- startedToolIds.add(call.id);
12376
- events.push({ type: "tool.started", id: call.id, name: call.name });
12377
- }
12378
- }
12379
- return events;
12380
- }
12381
- if (chunk.type === MessageChunkTypes4.TOOL && chunk.data.tool_call_id) {
12382
- return [{ type: "tool.completed", id: chunk.data.tool_call_id }];
12383
- }
12384
- if (chunk.type === MessageChunkTypes4.INTERRUPT) {
12385
- if (context && !context.allowInterrupts) return [{ type: "stream.completed" }];
12386
- const interrupt = publicInterrupt(chunk);
12387
- return interrupt ? [{ type: "interrupt.created", interrupt }, { type: "stream.completed" }] : [{ type: "stream.completed" }];
12388
- }
12389
- if (chunk.type === MessageChunkTypes4.MESSAGE_COMPLETED) {
12390
- return [
12391
- { type: "message.completed", messageId: chunk.data.id },
12392
- { type: "stream.completed" }
12393
- ];
12394
- }
12395
- if (chunk.type === MessageChunkTypes4.MESSAGE_FAILED) {
12396
- return [
12397
- { type: "error", error: { code: "STREAM_FAILED", message: "Stream failed", retryable: true } },
12398
- { type: "stream.completed" }
12399
- ];
12437
+ // src/services/agent-web-app/AgentWebAppTokenVerifier.ts
12438
+ import { createHmac as createHmac2, createPublicKey, timingSafeEqual as timingSafeEqual2, verify as cryptoVerify } from "crypto";
12439
+ var WebAppTokenError = class extends Error {
12440
+ constructor(message) {
12441
+ super(message);
12442
+ this.name = "WebAppTokenError";
12400
12443
  }
12401
- return [];
12402
- }
12403
- function publicInterrupt(chunk) {
12404
- const value = parseInterruptContent(chunk.data.content);
12405
- if (!value) return void 0;
12406
- const topLevelId = chunk.id;
12407
- const id3 = typeof topLevelId === "string" ? topLevelId : chunk.data.id;
12408
- if (!id3) return void 0;
12409
- return {
12410
- id: id3,
12411
- type: typeof value.type === "string" ? value.type : "input",
12412
- prompt: typeof value.prompt === "string" ? value.prompt : typeof value.message === "string" ? value.message : "Input required",
12413
- ...isRecord3(value.data) ? { data: value.data } : {}
12414
- };
12415
- }
12416
- function parseInterruptContent(content) {
12417
- if (!content) return {};
12418
- if (isRecord3(content)) return content;
12419
- if (typeof content !== "string") return void 0;
12444
+ };
12445
+ var MAX_TOKEN_BYTES = 8 * 1024;
12446
+ var CLOCK_SKEW_SECONDS = 60;
12447
+ var JWKS_TTL_MS = 10 * 60 * 1e3;
12448
+ var jwksCache = /* @__PURE__ */ new Map();
12449
+ function decodeSegment(segment) {
12450
+ const json = Buffer.from(segment, "base64url").toString("utf8");
12451
+ let value;
12420
12452
  try {
12421
- const parsed = JSON.parse(content);
12422
- return isRecord3(parsed) ? parsed : { prompt: content };
12453
+ value = JSON.parse(json);
12423
12454
  } catch {
12424
- return { prompt: content };
12455
+ throw new WebAppTokenError("Malformed token segment");
12425
12456
  }
12457
+ if (typeof value !== "object" || value === null) throw new WebAppTokenError("Malformed token segment");
12458
+ return value;
12426
12459
  }
12427
- function isRecord3(value) {
12428
- return typeof value === "object" && value !== null && !Array.isArray(value);
12460
+ async function resolveKey(issuer, kid, fetchImpl) {
12461
+ const cacheKey = issuer.jwksUrl ?? "";
12462
+ const load = async () => {
12463
+ const response = await fetchImpl(issuer.jwksUrl ?? "");
12464
+ if (!response.ok) throw new WebAppTokenError("JWKS fetch failed");
12465
+ const document = await response.json();
12466
+ const keys2 = document.keys;
12467
+ const entry2 = { keys: Array.isArray(keys2) ? keys2 : [], fetchedAt: Date.now() };
12468
+ jwksCache.set(cacheKey, entry2);
12469
+ return entry2;
12470
+ };
12471
+ const cached = jwksCache.get(cacheKey);
12472
+ let entry = cached && Date.now() - cached.fetchedAt < JWKS_TTL_MS ? cached : await load();
12473
+ let jwk = entry.keys.find((key) => kid === void 0 ? true : key.kid === kid);
12474
+ if (!jwk && kid !== void 0) {
12475
+ entry = await load();
12476
+ jwk = entry.keys.find((key) => key.kid === kid);
12477
+ }
12478
+ if (!jwk) throw new WebAppTokenError("No matching JWKS key");
12479
+ return createPublicKey({ key: jwk, format: "jwk" });
12480
+ }
12481
+ async function verifyWebAppToken(token, issuer, options = {}) {
12482
+ const nowMs = options.now ? options.now() : Date.now();
12483
+ const now = Math.floor(nowMs / 1e3);
12484
+ if (token.length === 0 || Buffer.byteLength(token) > MAX_TOKEN_BYTES) throw new WebAppTokenError("Token size invalid");
12485
+ const parts = token.split(".");
12486
+ if (parts.length !== 3) throw new WebAppTokenError("Token structure invalid");
12487
+ const [rawHeader, rawPayload, rawSignature] = parts;
12488
+ const header2 = decodeSegment(rawHeader);
12489
+ const payload = decodeSegment(rawPayload);
12490
+ if (header2.alg !== issuer.alg) throw new WebAppTokenError("Token algorithm mismatch");
12491
+ if (payload.iss !== issuer.iss) throw new WebAppTokenError("Token issuer mismatch");
12492
+ if (payload.aud !== issuer.aud) throw new WebAppTokenError("Token audience mismatch");
12493
+ const exp = typeof payload.exp === "number" ? payload.exp : void 0;
12494
+ if (exp === void 0 || exp + CLOCK_SKEW_SECONDS < now) throw new WebAppTokenError("Token expired");
12495
+ const nbf = typeof payload.nbf === "number" ? payload.nbf : void 0;
12496
+ if (nbf !== void 0 && nbf - CLOCK_SKEW_SECONDS > now) throw new WebAppTokenError("Token not yet valid");
12497
+ const signature = Buffer.from(rawSignature, "base64url");
12498
+ const data = Buffer.from(`${rawHeader}.${rawPayload}`);
12499
+ if (issuer.alg === "HS256") {
12500
+ if (typeof issuer.secret !== "string" || issuer.secret.length === 0) throw new WebAppTokenError("Issuer secret missing");
12501
+ const expected = createHmac2("sha256", issuer.secret).update(data).digest();
12502
+ if (signature.length !== expected.length || !timingSafeEqual2(signature, expected)) {
12503
+ throw new WebAppTokenError("Token signature invalid");
12504
+ }
12505
+ } else {
12506
+ if (typeof issuer.jwksUrl !== "string") throw new WebAppTokenError("Issuer JWKS URL missing");
12507
+ const key = await resolveKey(issuer, typeof header2.kid === "string" ? header2.kid : void 0, options.fetchImpl ?? fetch);
12508
+ let valid = false;
12509
+ try {
12510
+ valid = cryptoVerify("RSA-SHA256", data, key, signature);
12511
+ } catch {
12512
+ valid = false;
12513
+ }
12514
+ if (!valid) throw new WebAppTokenError("Token signature invalid");
12515
+ }
12516
+ const sub = payload.sub;
12517
+ if (typeof sub !== "string" || sub.length === 0 || sub.length > 128 || !/^[A-Za-z0-9._:@-]+$/.test(sub)) {
12518
+ throw new WebAppTokenError("Token subject invalid");
12519
+ }
12520
+ return sub;
12429
12521
  }
12430
12522
 
12431
12523
  // src/routes/agent-web-app-runtime.ts
12524
+ import { createAgentWebAppStreamProjectionContext, projectAgentWebAppChunk } from "@axiom-lattice/protocols";
12432
12525
  var idSchema = z5.string().min(1).max(128);
12433
12526
  var paramsSchema = z5.object({ webAppId: idSchema }).strict();
12434
12527
  var threadParamsSchema = z5.object({ webAppId: idSchema, threadId: idSchema }).strict();
12435
12528
  var interruptParamsSchema = z5.object({ webAppId: idSchema, threadId: idSchema, interruptId: idSchema }).strict();
12529
+ var pendingMessageParamsSchema = z5.object({ webAppId: idSchema, threadId: idSchema, messageId: idSchema }).strict();
12436
12530
  var bootstrapQuerySchema = z5.object({ projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
12437
12531
  var projectQuerySchema = z5.object({ projectId: idSchema.optional() }).strict();
12532
+ var projectQueryLenientSchema = projectQuerySchema.passthrough();
12438
12533
  var threadBodySchema = z5.object({ projectId: idSchema.optional(), label: z5.string().max(128).optional() }).strict();
12439
12534
  var streamBodySchema = z5.object({
12440
12535
  content: z5.string().trim().min(1).max(32768),
@@ -12453,8 +12548,27 @@ var resumeBodySchema = z5.object({
12453
12548
  ])
12454
12549
  }).strict();
12455
12550
  var MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
12551
+ var stateQuerySchema = z5.object({
12552
+ threadId: idSchema,
12553
+ projectId: idSchema.optional(),
12554
+ modelKey: idSchema.optional()
12555
+ }).strict();
12556
+ var resumeSchema = z5.object({
12557
+ messageId: idSchema,
12558
+ projectId: idSchema.optional(),
12559
+ modelKey: idSchema.optional()
12560
+ }).strict();
12561
+ var devTokenSchema = z5.object({
12562
+ iss: z5.string().min(1).max(255).optional(),
12563
+ sub: z5.string().regex(/^[A-Za-z0-9._:@-]+$/).max(128).optional(),
12564
+ hours: z5.number().gt(0).max(720).optional()
12565
+ }).strict();
12566
+ var fileQuerySchema = z5.object({
12567
+ projectId: idSchema.optional(),
12568
+ modelKey: idSchema.optional()
12569
+ }).passthrough();
12456
12570
  function createExternalHeaderIdentityResolver() {
12457
- return { resolve(request) {
12571
+ return { async resolve(request) {
12458
12572
  const raw = request.headers["x-axiom-external-user-id"];
12459
12573
  if (raw === void 0) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "External user id is required");
12460
12574
  if (Array.isArray(raw) || typeof raw !== "string") throw new AgentWebAppRuntimeError("INVALID_USER_ID", 400, "External user id is invalid");
@@ -12463,47 +12577,109 @@ function createExternalHeaderIdentityResolver() {
12463
12577
  return { userId, assurance: "unverified" };
12464
12578
  } };
12465
12579
  }
12580
+ function createWebAppIdentityResolver(deps) {
12581
+ const headerResolver = createExternalHeaderIdentityResolver();
12582
+ return {
12583
+ async resolve(request, webAppId) {
12584
+ let config;
12585
+ try {
12586
+ config = await deps.getIdentityConfig(webAppId);
12587
+ } catch {
12588
+ config = void 0;
12589
+ }
12590
+ const policy = config?.policy ?? "unverified";
12591
+ const authHeader = request.headers.authorization;
12592
+ const bearer = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : void 0;
12593
+ if (policy !== "unverified" && bearer !== void 0) {
12594
+ const issuers = config?.issuers ?? [];
12595
+ if (issuers.length === 0) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 401, "No trusted issuer configured");
12596
+ let lastError = new AgentWebAppRuntimeError("INVALID_REQUEST", 401, "Invalid identity token");
12597
+ for (const issuer of issuers) {
12598
+ try {
12599
+ const sub = await verifyWebAppToken(bearer, issuer);
12600
+ return { userId: sub, assurance: "verified" };
12601
+ } catch (error) {
12602
+ if (error instanceof WebAppTokenError) {
12603
+ lastError = error;
12604
+ continue;
12605
+ }
12606
+ throw error;
12607
+ }
12608
+ }
12609
+ const message = lastError instanceof WebAppTokenError ? `Invalid identity token: ${lastError.message}` : "Invalid identity token";
12610
+ throw new AgentWebAppRuntimeError("INVALID_REQUEST", 401, message);
12611
+ }
12612
+ if (policy === "verified") throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "A verified identity token is required");
12613
+ return headerResolver.resolve(request, webAppId);
12614
+ }
12615
+ };
12616
+ }
12466
12617
  function registerAgentWebAppRuntimeRoutes(app2, deps) {
12467
- const identities = deps.identityResolver ?? createExternalHeaderIdentityResolver();
12618
+ const identities = deps.identityResolver ?? createWebAppIdentityResolver({ getIdentityConfig: (webAppId) => deps.service.getIdentityConfig(webAppId) });
12468
12619
  app2.register(async (routes) => {
12469
12620
  routes.setErrorHandler((error, _request, reply) => normalizeError2(reply, error));
12470
12621
  routes.get("/api/web-apps/:webAppId/runtime/bootstrap", async (request, reply) => handle2(reply, async () => {
12471
12622
  const { webAppId } = paramsSchema.parse(request.params);
12472
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
12473
- return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview)) };
12623
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12624
+ return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview), { assurance: identity.assurance ?? "unverified" }) };
12474
12625
  }));
12475
12626
  routes.get("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
12476
12627
  const { webAppId } = paramsSchema.parse(request.params);
12477
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
12628
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12478
12629
  const records = await deps.service.listThreads(webAppId, identity.userId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
12479
12630
  return { data: { records, total: records.length } };
12480
12631
  }));
12481
12632
  routes.post("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
12482
12633
  const { webAppId } = paramsSchema.parse(request.params);
12483
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
12634
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12484
12635
  return { statusCode: 201, data: await deps.service.createThread(webAppId, identity.userId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
12485
12636
  }));
12486
12637
  routes.patch("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
12487
12638
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
12488
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
12639
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12489
12640
  return { data: await deps.service.updateThread(webAppId, identity.userId, threadId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
12490
12641
  }));
12491
12642
  routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
12492
12643
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
12493
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
12644
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12494
12645
  await deps.service.deleteThread(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
12495
12646
  return { data: { id: threadId } };
12496
12647
  }));
12497
12648
  routes.get("/api/web-apps/:webAppId/runtime/threads/:threadId/messages", async (request, reply) => handle2(reply, async () => {
12498
12649
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
12499
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
12650
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12500
12651
  const messages = await deps.service.getMessages(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
12501
12652
  return { data: { messages } };
12502
12653
  }));
12654
+ routes.get("/api/web-apps/:webAppId/runtime/state", async (request, reply) => handle2(reply, async () => {
12655
+ const { webAppId } = paramsSchema.parse(request.params);
12656
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12657
+ const { threadId, projectId, modelKey: modelKey2 } = stateQuerySchema.parse(request.query);
12658
+ const state = await deps.service.getState(webAppId, identity.userId, threadId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
12659
+ return { data: state };
12660
+ }));
12661
+ routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId/pending-messages/:messageId", async (request, reply) => handle2(reply, async () => {
12662
+ const { webAppId, threadId, messageId } = pendingMessageParamsSchema.parse(request.params);
12663
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12664
+ await deps.service.removePendingMessage(webAppId, identity.userId, threadId, messageId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
12665
+ return { data: { removed: true } };
12666
+ }));
12503
12667
  routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/messages/stream", async (request, reply) => {
12504
12668
  try {
12505
12669
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
12506
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
12670
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12671
+ const input = streamBodySchema.parse(request.body);
12672
+ const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
12673
+ beginSse(reply);
12674
+ await pipeNativeWebAppStream(reply.raw, result.stream);
12675
+ } catch (error) {
12676
+ return normalizeError2(reply, error);
12677
+ }
12678
+ });
12679
+ routes.post("/api/web-apps/:webAppId/runtime/v1/threads/:threadId/messages/stream", async (request, reply) => {
12680
+ try {
12681
+ const { webAppId, threadId } = threadParamsSchema.parse(request.params);
12682
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12507
12683
  const input = streamBodySchema.parse(request.body);
12508
12684
  const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
12509
12685
  beginSse(reply);
@@ -12512,15 +12688,27 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
12512
12688
  return normalizeError2(reply, error);
12513
12689
  }
12514
12690
  });
12691
+ routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/resume", async (request, reply) => {
12692
+ try {
12693
+ const { webAppId, threadId } = threadParamsSchema.parse(request.params);
12694
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12695
+ const { messageId, projectId, modelKey: modelKey2 } = resumeSchema.parse(request.body);
12696
+ const result = await deps.service.resumeStream(webAppId, identity.userId, threadId, messageId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
12697
+ beginSse(reply);
12698
+ await pipeNativeWebAppStream(reply.raw, result.stream);
12699
+ } catch (error) {
12700
+ return normalizeError2(reply, error);
12701
+ }
12702
+ });
12515
12703
  routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/abort", async (request, reply) => handle2(reply, async () => {
12516
12704
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
12517
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
12705
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12518
12706
  await deps.service.abort(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
12519
12707
  return { data: { aborted: true } };
12520
12708
  }));
12521
12709
  routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/attachments", async (request, reply) => handle2(reply, async () => {
12522
12710
  const { webAppId, threadId } = threadParamsSchema.parse(request.params);
12523
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
12711
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12524
12712
  if (!request.isMultipart()) throw new ZodError2([]);
12525
12713
  const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
12526
12714
  if (!part || !part.filename || part.filename.length > 255) throw new ZodError2([]);
@@ -12536,7 +12724,7 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
12536
12724
  routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/interrupts/:interruptId/resume", async (request, reply) => {
12537
12725
  try {
12538
12726
  const { webAppId, threadId, interruptId } = interruptParamsSchema.parse(request.params);
12539
- const identity = resolveRuntimeIdentity(request, webAppId, identities);
12727
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12540
12728
  const { projectId, modelKey: modelKey2, message, response } = resumeBodySchema.parse(request.body);
12541
12729
  const result = await deps.service.resumeInterrupt(webAppId, identity.userId, threadId, interruptId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview), { message, response });
12542
12730
  beginSse(reply);
@@ -12545,13 +12733,257 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
12545
12733
  return normalizeError2(reply, error);
12546
12734
  }
12547
12735
  });
12736
+ routes.post("/api/web-apps/:webAppId/dev/token", async (request, reply) => handle2(reply, async () => {
12737
+ if (process.env.WEB_APP_DEV_TOKEN_ENABLED !== "true") {
12738
+ throw new AgentWebAppRuntimeError("FEATURE_DISABLED", 403, "Dev token issuance is disabled (set WEB_APP_DEV_TOKEN_ENABLED=true)");
12739
+ }
12740
+ const { webAppId } = paramsSchema.parse(request.params);
12741
+ const { iss, sub, hours } = devTokenSchema.parse(request.body ?? {});
12742
+ const config = await deps.service.getIdentityConfig(webAppId);
12743
+ const issuer = (config?.issuers ?? []).find((entry) => entry.alg === "HS256" && (iss === void 0 || entry.iss === iss));
12744
+ if (!issuer || typeof issuer.secret !== "string") {
12745
+ throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "Web app has no HS256 issuer configured");
12746
+ }
12747
+ const now = Math.floor(Date.now() / 1e3);
12748
+ const expiresIn = (hours ?? 24) * 3600;
12749
+ const subject = sub ?? "dev-user";
12750
+ const b64url = (input) => Buffer.from(input).toString("base64url");
12751
+ 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 }))}`;
12752
+ const token = `${body}.${createHmac3("sha256", issuer.secret).update(body).digest("base64url")}`;
12753
+ return { data: { token, tokenType: "Bearer", expiresIn, subject } };
12754
+ }));
12755
+ if (deps.taskController) {
12756
+ const tasks = deps.taskController;
12757
+ const withSubject = (handler) => async (request, reply) => {
12758
+ const { webAppId } = paramsSchema.parse(request.params);
12759
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12760
+ const proxy = Object.create(request);
12761
+ proxy.headers = { ...request.headers, "x-user-id": identity.userId };
12762
+ return handler(proxy, reply);
12763
+ };
12764
+ routes.get("/api/web-apps/:webAppId/runtime/tasks", withSubject((request, reply) => tasks.list(request, reply)));
12765
+ routes.post("/api/web-apps/:webAppId/runtime/tasks", withSubject((request, reply) => tasks.create(request, reply)));
12766
+ routes.get("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.get(request, reply)));
12767
+ routes.put("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.update(request, reply)));
12768
+ routes.patch("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.update(request, reply)));
12769
+ routes.delete("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.remove(request, reply)));
12770
+ routes.patch("/api/web-apps/:webAppId/runtime/tasks/:taskId/complete", withSubject((request, reply) => tasks.complete(request, reply)));
12771
+ routes.get("/api/web-apps/:webAppId/runtime/tasks/:taskId/work-items", withSubject((request, reply) => tasks.workItems(request, reply)));
12772
+ }
12773
+ if (deps.workspaceController) {
12774
+ const workspace = deps.workspaceController;
12775
+ const fileScope = async (webAppId, selection) => {
12776
+ const scope = await deps.service.resolveFileScope(webAppId, selection);
12777
+ if (!scope.enabled) throw new AgentWebAppRuntimeError("FEATURE_DISABLED", 403, "Files are disabled for this web app");
12778
+ return scope;
12779
+ };
12780
+ const scopedRequest = (request, scope, params, query) => {
12781
+ const proxy = Object.create(request);
12782
+ proxy.params = params;
12783
+ proxy.query = query;
12784
+ proxy.headers = { ...request.headers, "x-tenant-id": scope.tenantId };
12785
+ return proxy;
12786
+ };
12787
+ const unwrap = (result) => ({ data: result.data });
12788
+ routes.get("/api/web-apps/:webAppId/runtime/files/listpath", async (request) => {
12789
+ const { webAppId } = paramsSchema.parse(request.params);
12790
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12791
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
12792
+ const query = { ...request.query, assistantId: scope.assistantId };
12793
+ const result = await workspace.listPath(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query));
12794
+ return unwrap(result);
12795
+ });
12796
+ routes.get("/api/web-apps/:webAppId/runtime/files/readfile", async (request) => {
12797
+ const { webAppId } = paramsSchema.parse(request.params);
12798
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12799
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
12800
+ const query = { ...request.query, assistantId: scope.assistantId };
12801
+ const result = await workspace.readFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query));
12802
+ return unwrap(result);
12803
+ });
12804
+ routes.post("/api/web-apps/:webAppId/runtime/files/uploadfile", async (request, reply) => {
12805
+ const { webAppId } = paramsSchema.parse(request.params);
12806
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12807
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
12808
+ const scoped = scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, { assistantId: scope.assistantId });
12809
+ await workspace.uploadFile(scoped, reply);
12810
+ return reply;
12811
+ });
12812
+ routes.get("/api/web-apps/:webAppId/runtime/files/viewfile", async (request, reply) => {
12813
+ const { webAppId } = paramsSchema.parse(request.params);
12814
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12815
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
12816
+ const query = { ...request.query, assistantId: scope.assistantId };
12817
+ await workspace.viewFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
12818
+ return reply;
12819
+ });
12820
+ routes.get("/api/web-apps/:webAppId/runtime/files/downloadfile", async (request, reply) => {
12821
+ const { webAppId } = paramsSchema.parse(request.params);
12822
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12823
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
12824
+ const query = { ...request.query, assistantId: scope.assistantId };
12825
+ await workspace.downloadFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
12826
+ return reply;
12827
+ });
12828
+ routes.get("/api/web-apps/:webAppId/runtime/files/downloadfolder", async (request, reply) => {
12829
+ const { webAppId } = paramsSchema.parse(request.params);
12830
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12831
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
12832
+ const query = { ...request.query, assistantId: scope.assistantId };
12833
+ await workspace.downloadFolder(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
12834
+ return reply;
12835
+ });
12836
+ const genuiSandboxParams = z5.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema }).strict();
12837
+ const delegateGenuiFile = (method) => async (request, reply) => {
12838
+ const { webAppId } = genuiSandboxParams.parse(request.params);
12839
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12840
+ const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
12841
+ const query = { ...request.query, assistantId: scope.assistantId };
12842
+ await method(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
12843
+ return reply;
12844
+ };
12845
+ routes.get("/api/web-apps/:webAppId/runtime/assistants/:assistantId/threads/:threadId/sandbox/downloadfile", delegateGenuiFile(workspace.downloadFile.bind(workspace)));
12846
+ routes.get("/api/web-apps/:webAppId/runtime/assistants/:assistantId/threads/:threadId/sandbox/viewfile", delegateGenuiFile(workspace.viewFile.bind(workspace)));
12847
+ }
12848
+ const mirrorThreadParams = z5.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema }).strict();
12849
+ const mirrorBody = z5.object({ metadata: z5.object({ label: z5.string().max(300).optional() }).strict().optional(), projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
12850
+ const toConsoleThread = (thread, assistantId) => ({
12851
+ id: thread.id,
12852
+ assistantId,
12853
+ metadata: thread.label ? { label: thread.label } : {},
12854
+ createdAt: thread.createdAt,
12855
+ updatedAt: thread.updatedAt
12856
+ });
12857
+ const mirrorAssistantParams = z5.object({ webAppId: idSchema, assistantId: idSchema }).strict();
12858
+ const mirrorPendingParams = z5.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema, messageId: idSchema }).strict();
12859
+ const registerMirror = (prefix) => {
12860
+ routes.get(`${prefix}/assistants/:assistantId/threads`, async (request, reply) => handle2(reply, async () => {
12861
+ const { webAppId, assistantId } = mirrorAssistantParams.parse(request.params);
12862
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12863
+ const threads = await deps.service.listThreads(webAppId, identity.userId, projectQueryLenientSchema.parse(request.query));
12864
+ return { data: { records: threads.map((thread) => toConsoleThread(thread, assistantId)) } };
12865
+ }));
12866
+ routes.post(`${prefix}/assistants/:assistantId/threads`, async (request, reply) => handle2(reply, async () => {
12867
+ const { webAppId, assistantId } = mirrorAssistantParams.parse(request.params);
12868
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12869
+ const body = mirrorBody.parse(request.body ?? {});
12870
+ const created = await deps.service.createThread(webAppId, identity.userId, { label: body.metadata?.label, ...body.projectId ? { projectId: body.projectId } : {} });
12871
+ return { statusCode: 201, data: { id: created.id } };
12872
+ }));
12873
+ routes.get(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
12874
+ const { webAppId, assistantId, threadId } = mirrorThreadParams.parse(request.params);
12875
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12876
+ const thread = await deps.service.getThread(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
12877
+ return { data: toConsoleThread(thread, assistantId) };
12878
+ }));
12879
+ routes.put(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
12880
+ const { webAppId, assistantId, threadId } = mirrorThreadParams.parse(request.params);
12881
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12882
+ const body = mirrorBody.parse(request.body ?? {});
12883
+ const updated = await deps.service.updateThread(webAppId, identity.userId, threadId, { label: body.metadata?.label, ...body.projectId ? { projectId: body.projectId } : {} });
12884
+ return { data: toConsoleThread(updated, assistantId) };
12885
+ }));
12886
+ routes.delete(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
12887
+ const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
12888
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12889
+ await deps.service.deleteThread(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
12890
+ return { data: { success: true, message: "Thread deleted" } };
12891
+ }));
12892
+ routes.post(`${prefix}/assistants/:assistantId/threads/:threadId/sandbox/uploadfile`, async (request, reply) => handle2(reply, async () => {
12893
+ const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
12894
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12895
+ if (!request.isMultipart()) throw new ZodError2([]);
12896
+ const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
12897
+ if (!part || !part.filename || part.filename.length > 255) throw new ZodError2([]);
12898
+ const bytes = await part.toBuffer();
12899
+ if (bytes.length === 0 || part.file.truncated) throw new ZodError2([]);
12900
+ const uploaded = await deps.service.uploadAttachment(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query), {
12901
+ name: part.filename,
12902
+ mimeType: part.mimetype || "application/octet-stream",
12903
+ bytes
12904
+ });
12905
+ return { statusCode: 201, data: { id: uploaded.ref, path: uploaded.ref, name: uploaded.name, mimeType: uploaded.mimeType, size: uploaded.size } };
12906
+ }));
12907
+ routes.get(`${prefix}/assistants/:assistantId/:threadId/memory`, async (request) => {
12908
+ const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
12909
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12910
+ const messages = await deps.service.getConsoleMessages(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
12911
+ return messages;
12912
+ });
12913
+ routes.get(`${prefix}/assistants/:assistantId/:threadId/state`, async (request) => {
12914
+ const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
12915
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12916
+ return deps.service.getState(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
12917
+ });
12918
+ routes.delete(`${prefix}/assistants/:assistantId/threads/:threadId/pending-messages/:messageId`, async (request, reply) => handle2(reply, async () => {
12919
+ const { webAppId, threadId, messageId } = mirrorPendingParams.parse(request.params);
12920
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12921
+ await deps.service.removePendingMessage(webAppId, identity.userId, threadId, messageId, projectQueryLenientSchema.parse(request.query));
12922
+ return { data: { removed: true } };
12923
+ }));
12924
+ routes.post(`${prefix}/assistants/:assistantId/threads/:threadId/abort`, async (request, reply) => handle2(reply, async () => {
12925
+ const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
12926
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12927
+ await deps.service.abort(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
12928
+ return { data: { aborted: true } };
12929
+ }));
12930
+ const runsBodySchema = z5.object({
12931
+ thread_id: idSchema.optional(),
12932
+ assistant_id: idSchema.optional(),
12933
+ messages: z5.array(z5.object({ content: z5.string().min(1).max(32768) }).passthrough()).min(1).max(20),
12934
+ background: z5.boolean().optional(),
12935
+ streaming: z5.boolean().optional(),
12936
+ projectId: idSchema.optional(),
12937
+ modelKey: idSchema.optional()
12938
+ }).passthrough();
12939
+ routes.post(`${prefix}/runs`, async (request, reply) => {
12940
+ try {
12941
+ const { webAppId } = paramsSchema.parse(request.params);
12942
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12943
+ const body = runsBodySchema.parse(request.body ?? {});
12944
+ if (!body.background || body.streaming) {
12945
+ throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "Mirror runs supports only background enqueue (stream via resume_stream)");
12946
+ }
12947
+ const threadId = body.thread_id ?? "";
12948
+ if (!threadId) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "thread_id is required");
12949
+ const content = body.messages.map((message) => message.content).join("\n");
12950
+ const { messageId } = await deps.service.enqueueMessage(webAppId, identity.userId, threadId, content, { projectId: body.projectId, modelKey: body.modelKey });
12951
+ return reply.code(202).send({ success: true, messageId, queued: true });
12952
+ } catch (error) {
12953
+ return normalizeError2(reply, error);
12954
+ }
12955
+ });
12956
+ const resumeStreamSchema2 = z5.object({
12957
+ assistant_id: idSchema.optional(),
12958
+ thread_id: idSchema,
12959
+ message_id: idSchema,
12960
+ known_content: z5.string().optional(),
12961
+ poll_interval: z5.number().optional(),
12962
+ projectId: idSchema.optional(),
12963
+ modelKey: idSchema.optional()
12964
+ }).strict();
12965
+ routes.post(`${prefix}/resume_stream`, async (request, reply) => {
12966
+ try {
12967
+ const { webAppId } = paramsSchema.parse(request.params);
12968
+ const identity = await resolveRuntimeIdentity(request, webAppId, identities);
12969
+ const body = resumeStreamSchema2.parse(request.body ?? {});
12970
+ const result = await deps.service.resumeStream(webAppId, identity.userId, body.thread_id, body.message_id, { projectId: body.projectId, modelKey: body.modelKey });
12971
+ beginSse(reply);
12972
+ await pipeNativeWebAppStream(reply.raw, result.stream);
12973
+ } catch (error) {
12974
+ return normalizeError2(reply, error);
12975
+ }
12976
+ });
12977
+ };
12978
+ registerMirror("/api/web-apps/:webAppId/runtime");
12979
+ registerMirror("/api/web-apps/:webAppId/runtime/api");
12548
12980
  });
12549
12981
  }
12550
- function resolveRuntimeIdentity(request, webAppId, identities) {
12551
- if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request);
12982
+ async function resolveRuntimeIdentity(request, webAppId, identities) {
12983
+ if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request, webAppId);
12552
12984
  const user = request.user;
12553
12985
  if (!user?.id || !user.tenantId) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "Authenticated Console preview is required");
12554
- return { userId: `console-preview:${user.id}:${webAppId}`, preview: { tenantId: user.tenantId, userId: user.id } };
12986
+ return { userId: `console-preview:${user.id}:${webAppId}`, assurance: "unverified", preview: { tenantId: user.tenantId, userId: user.id } };
12555
12987
  }
12556
12988
  function withPreview(selection, preview) {
12557
12989
  return { ...selection, ...preview ? { preview } : {} };
@@ -12566,13 +12998,22 @@ async function pipeAgentWebAppStream(raw, stream, options = {}) {
12566
12998
  ]
12567
12999
  });
12568
13000
  }
13001
+ async function pipeNativeWebAppStream(raw, stream) {
13002
+ await pipeAsyncIterableToSse(raw, stream, {
13003
+ onError: () => [{
13004
+ type: "error",
13005
+ data: { id: "", content: "Stream failed" }
13006
+ }]
13007
+ });
13008
+ }
12569
13009
  function beginSse(reply) {
12570
13010
  reply.hijack();
12571
13011
  reply.raw.writeHead(200, {
12572
13012
  "Content-Type": "text/event-stream; charset=utf-8",
12573
13013
  "Cache-Control": "no-cache, no-transform",
12574
13014
  Connection: "keep-alive",
12575
- "X-Accel-Buffering": "no"
13015
+ "X-Accel-Buffering": "no",
13016
+ "Access-Control-Allow-Origin": "*"
12576
13017
  });
12577
13018
  }
12578
13019
  async function handle2(reply, operation) {
@@ -16604,7 +17045,7 @@ function snapshotProjectableWorkItem(value) {
16604
17045
  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 || !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;
16605
17046
  try {
16606
17047
  const detail = row.detail === void 0 ? void 0 : snapshotJsonValue(row.detail);
16607
- if (detail !== void 0 && !isRecord4(detail)) return void 0;
17048
+ if (detail !== void 0 && !isRecord3(detail)) return void 0;
16608
17049
  return {
16609
17050
  id: row.id,
16610
17051
  taskId: row.taskId,
@@ -16751,7 +17192,7 @@ function snapshotLifecycleCursor(value) {
16751
17192
  const createdAt = cloneExactDate(row?.createdAt);
16752
17193
  return row && typeof row.id === "string" && row.id.length > 0 && createdAt ? { id: row.id, createdAt } : void 0;
16753
17194
  }
16754
- function isRecord4(value) {
17195
+ function isRecord3(value) {
16755
17196
  return typeof value === "object" && value !== null && !Array.isArray(value);
16756
17197
  }
16757
17198
 
@@ -18209,6 +18650,46 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
18209
18650
  })
18210
18651
  });
18211
18652
  registerAgentWebAppRuntimeRoutes(app2, {
18653
+ workspaceController: new WorkspaceController(createProjectCollaborationInitializer({
18654
+ membershipStore: projectMembershipStore,
18655
+ roomStore: projectRoomStore,
18656
+ userTenantLinkStore
18657
+ })),
18658
+ taskController: createTaskController({
18659
+ access: {
18660
+ requireProjectAccess: async (input) => {
18661
+ const apps = await webAppStore.list(input.tenantId);
18662
+ const allowed = apps.some((app3) => app3.status === "active" && app3.scope.allowedProjectIds.includes(input.projectId));
18663
+ if (!allowed) throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project is not exposed through this web app");
18664
+ const project = await projectStore.getProjectById(input.tenantId, input.projectId);
18665
+ if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
18666
+ return {
18667
+ project,
18668
+ membership: {
18669
+ id: `webapp_guest_${input.projectId}_${input.userId}`,
18670
+ tenantId: input.tenantId,
18671
+ projectId: input.projectId,
18672
+ userId: input.userId,
18673
+ role: "member",
18674
+ status: "active",
18675
+ joinedAt: /* @__PURE__ */ new Date(),
18676
+ updatedAt: /* @__PURE__ */ new Date()
18677
+ }
18678
+ };
18679
+ }
18680
+ },
18681
+ tasks: taskStore,
18682
+ workItems: taskWorkItemStore,
18683
+ bots: projectBotMembershipStore,
18684
+ rooms: projectRoomStore,
18685
+ threadService: taskThreadService,
18686
+ reassignmentAuthorizer,
18687
+ lifecycle: taskLifecycle,
18688
+ projectMutationMutex: projectMutationMutex2,
18689
+ executionMutex: projectTaskComposition?.mutex ?? {
18690
+ runExclusive: (_scope, operation) => operation()
18691
+ }
18692
+ }),
18212
18693
  service: new AgentWebAppRuntimeService({
18213
18694
  webAppStore,
18214
18695
  threadStore: getStoreLattice16("default", "thread").store,
@@ -18216,7 +18697,7 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
18216
18697
  assistantResolver: {
18217
18698
  resolve: async (tenantId, assistantId) => {
18218
18699
  const stored = await assistantStore.getAssistantById(tenantId, assistantId);
18219
- if (stored) return stored;
18700
+ if (stored) return { id: stored.id, name: stored.name, description: stored.description };
18220
18701
  await agentLatticeManager4.initializeStoredAssistantsForTenant(tenantId);
18221
18702
  const config = agentLatticeManager4.getAgentConfigWithTenant(tenantId, assistantId);
18222
18703
  return config ? { id: assistantId, name: config.name, description: config.description } : null;
@@ -21329,7 +21810,7 @@ var start = async (config) => {
21329
21810
  });
21330
21811
  try {
21331
21812
  const { A2AAuthService, parseEnvKeys } = await import("./A2AAuthService-DK5X6VIL.mjs");
21332
- const { registerA2AStandardRoutes } = await import("./a2a-standard-G3J5ZHQ7.mjs");
21813
+ const { registerA2AStandardRoutes } = await import("./a2a-standard-WC2BSOFV.mjs");
21333
21814
  const a2a = await import("./a2a-7N3WDAOP.mjs");
21334
21815
  const a2aKeyStore = getStoreLattice27("default", "a2aApiKey").store;
21335
21816
  const assistantStore = getStoreLattice27("default", "assistant").store;