@axiom-lattice/gateway 4.1.4 → 4.1.6

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_crypto17.createHash)("sha256").update(JSON.stringify({
1515
+ const digest = (0, import_node_crypto19.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_crypto17.createHash)("sha256").update(JSON.stringify(task)).digest("hex");
1525
+ const digest = (0, import_node_crypto19.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_crypto17, import_node_util, TASK_STATUS_TO_A2A_STATE, TITLE_MAX_LENGTH, A2ATaskScopeConflictError, A2ATaskStoreAdapter;
1537
+ var import_node_crypto19, 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_crypto17 = require("crypto");
1541
+ import_node_crypto19 = require("crypto");
1542
1542
  import_node_util = require("util");
1543
1543
  TASK_STATUS_TO_A2A_STATE = {
1544
1544
  pending: "submitted",
@@ -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_crypto16 = require("crypto");
3316
+ var import_node_crypto18 = require("crypto");
3317
3317
  var import_protocols24 = require("@axiom-lattice/protocols");
3318
3318
 
3319
3319
  // src/controllers/assistant.ts
@@ -8485,6 +8485,113 @@ var import_uuid3 = require("uuid");
8485
8485
  init_mime();
8486
8486
  init_workspace_file_service();
8487
8487
  init_path_containment();
8488
+
8489
+ // src/services/collaboration/RoomCoordinatorProvisioner.ts
8490
+ var ROOM_COORDINATOR_KEY = "room-coordinator";
8491
+ var RoomCoordinatorProvisioner = class {
8492
+ /** Creates a provisioner over the tenant-scoped assistant store operations. */
8493
+ constructor(deps) {
8494
+ this.deps = deps;
8495
+ }
8496
+ /**
8497
+ * Ensures the Room Coordinator assistant exists, reusing an existing record
8498
+ * and reconciling concurrent create races by rereading the winner.
8499
+ *
8500
+ * @param tenantId Tenant to provision the coordinator for.
8501
+ * @returns The persisted Room Coordinator assistant.
8502
+ */
8503
+ async ensure(tenantId) {
8504
+ const existing = await this.deps.getAssistantById(tenantId, ROOM_COORDINATOR_KEY);
8505
+ if (existing) return existing;
8506
+ try {
8507
+ return await this.deps.createAssistant(tenantId, ROOM_COORDINATOR_KEY, {
8508
+ name: "Room Coordinator",
8509
+ description: "Coordinates project room conversations and organizes room work.",
8510
+ graphDefinition: {}
8511
+ });
8512
+ } catch (error) {
8513
+ const winner = await this.deps.getAssistantById(tenantId, ROOM_COORDINATOR_KEY);
8514
+ if (winner) return winner;
8515
+ throw error;
8516
+ }
8517
+ }
8518
+ };
8519
+
8520
+ // src/services/collaboration/WorkspacePublicRoomService.ts
8521
+ var import_node_crypto = require("crypto");
8522
+ var import_node_crypto2 = require("crypto");
8523
+ var WORKSPACE_PUBLIC_ROOM_PROJECT_PREFIX = "workspace_public_";
8524
+ var PUBLIC_ROOM_PROJECT_NAME = "General";
8525
+ var PUBLIC_ROOM_PROJECT_DESCRIPTION = "Public room for everyone in the workspace";
8526
+ function workspacePublicRoomProjectId(tenantId, workspaceId) {
8527
+ const hash = (0, import_node_crypto.createHash)("sha256").update([tenantId, workspaceId].join(":")).digest("hex");
8528
+ return `${WORKSPACE_PUBLIC_ROOM_PROJECT_PREFIX}${hash}`;
8529
+ }
8530
+ var WorkspacePublicRoomService = class {
8531
+ /** Creates the service from explicitly injected stores and provisioners. */
8532
+ constructor(deps) {
8533
+ this.deps = deps;
8534
+ this.idFactory = deps.idFactory ?? import_node_crypto2.randomUUID;
8535
+ }
8536
+ /**
8537
+ * Returns the workspace's public room project and room, creating them on first call.
8538
+ *
8539
+ * @param tenantId Tenant owning the workspace.
8540
+ * @param workspaceId Workspace hosting the public room.
8541
+ * @returns The ensured project and room.
8542
+ */
8543
+ async ensure(tenantId, workspaceId) {
8544
+ const projectId = workspacePublicRoomProjectId(tenantId, workspaceId);
8545
+ const project = await this.ensureProject(tenantId, workspaceId, projectId);
8546
+ const room = await this.deps.ensureMainRoom({
8547
+ id: this.idFactory(),
8548
+ tenantId,
8549
+ workspaceId,
8550
+ projectId,
8551
+ name: project.name
8552
+ });
8553
+ await this.ensureCoordinatorBot(tenantId, workspaceId, projectId, room.id);
8554
+ return { project, room };
8555
+ }
8556
+ async ensureProject(tenantId, workspaceId, projectId) {
8557
+ const existing = await this.deps.getProjectById(tenantId, projectId);
8558
+ if (existing && existing.kind === "public") return existing;
8559
+ try {
8560
+ return await this.deps.createProject(tenantId, workspaceId, projectId, {
8561
+ name: PUBLIC_ROOM_PROJECT_NAME,
8562
+ description: PUBLIC_ROOM_PROJECT_DESCRIPTION,
8563
+ kind: "public"
8564
+ });
8565
+ } catch (error) {
8566
+ const winner = await this.deps.getProjectById(tenantId, projectId);
8567
+ if (winner && winner.kind === "public") return winner;
8568
+ throw Object.assign(new Error("Workspace public room project could not be ensured"), { cause: error });
8569
+ }
8570
+ }
8571
+ async ensureCoordinatorBot(tenantId, workspaceId, projectId, roomId) {
8572
+ const coordinator = await this.deps.ensureCoordinator(tenantId);
8573
+ const existing = await this.deps.findBotByAssistant(tenantId, projectId, coordinator.id);
8574
+ if (existing && existing.status !== "removed") return;
8575
+ const saved = await this.deps.saveBot({
8576
+ id: this.idFactory(),
8577
+ tenantId,
8578
+ workspaceId,
8579
+ projectId,
8580
+ roomId,
8581
+ assistantId: coordinator.id,
8582
+ role: "coordinator",
8583
+ title: coordinator.name,
8584
+ mentionName: ROOM_COORDINATOR_KEY,
8585
+ status: "active",
8586
+ roomThreadId: this.idFactory()
8587
+ });
8588
+ if (saved.kind === "coordinator_conflict" || saved.kind === "mention_conflict") {
8589
+ throw new Error(`Workspace public room coordinator bot could not be ensured: ${saved.kind}`);
8590
+ }
8591
+ }
8592
+ };
8593
+
8594
+ // src/controllers/workspace.ts
8488
8595
  var MAX_ZIP_ENTRIES = 1e4;
8489
8596
  var MAX_ZIP_FILE_SIZE = 100 * 1024 * 1024;
8490
8597
  var MAX_ZIP_TOTAL_SIZE = 512 * 1024 * 1024;
@@ -8619,6 +8726,21 @@ var WorkspaceController = class {
8619
8726
  this.projectCollaboration = projectCollaboration;
8620
8727
  this.workspaceStore = (0, import_core24.getStoreLattice)("default", "workspace").store;
8621
8728
  this.projectStore = (0, import_core24.getStoreLattice)("default", "project").store;
8729
+ const roomStore = (0, import_core24.getStoreLattice)("default", "projectRoom").store;
8730
+ const botMembershipStore = (0, import_core24.getStoreLattice)("default", "projectBotMembership").store;
8731
+ const assistantStore = (0, import_core24.getStoreLattice)("default", "assistant").store;
8732
+ const coordinatorProvisioner = new RoomCoordinatorProvisioner({
8733
+ getAssistantById: assistantStore.getAssistantById.bind(assistantStore),
8734
+ createAssistant: assistantStore.createAssistant.bind(assistantStore)
8735
+ });
8736
+ this.publicRoom = new WorkspacePublicRoomService({
8737
+ getProjectById: this.projectStore.getProjectById.bind(this.projectStore),
8738
+ createProject: this.projectStore.createProject.bind(this.projectStore),
8739
+ ensureMainRoom: roomStore.ensureMainRoom.bind(roomStore),
8740
+ findBotByAssistant: botMembershipStore.findByAssistant.bind(botMembershipStore),
8741
+ saveBot: botMembershipStore.save.bind(botMembershipStore),
8742
+ ensureCoordinator: (tenantId) => coordinatorProvisioner.ensure(tenantId)
8743
+ });
8622
8744
  }
8623
8745
  getTenantId(request) {
8624
8746
  const userTenantId = request.user?.tenantId;
@@ -8692,6 +8814,7 @@ var WorkspaceController = class {
8692
8814
  const { workspaceId } = request.params;
8693
8815
  const kindFilter = request.query.kind;
8694
8816
  await this.ensureTrainingProject(tenantId, workspaceId);
8817
+ await this.publicRoom.ensure(tenantId, workspaceId);
8695
8818
  const projects = kindFilter ? await this.projectStore.getProjectsByWorkspace(tenantId, workspaceId, {
8696
8819
  kind: kindFilter
8697
8820
  }) : await this.projectStore.getProjectsByWorkspace(tenantId, workspaceId);
@@ -8735,6 +8858,12 @@ var WorkspaceController = class {
8735
8858
  message: CAPABILITY_BUNDLE_CONFIG_MESSAGE
8736
8859
  });
8737
8860
  }
8861
+ if (data.kind === "public") {
8862
+ return reply.status(409).send({
8863
+ success: false,
8864
+ error: "The public room project is managed automatically"
8865
+ });
8866
+ }
8738
8867
  if (user?.id && this.projectCollaboration) {
8739
8868
  try {
8740
8869
  const authorized = await this.projectCollaboration.authorizeCreator({ tenantId, userId: user.id });
@@ -8810,6 +8939,21 @@ var WorkspaceController = class {
8810
8939
  message: CAPABILITY_BUNDLE_CONFIG_MESSAGE
8811
8940
  });
8812
8941
  }
8942
+ if (updates.kind !== void 0) {
8943
+ const existingProject = await this.projectStore.getProjectById(tenantId, projectId);
8944
+ if (existingProject && updates.kind === "public") {
8945
+ return reply.status(409).send({
8946
+ success: false,
8947
+ error: "kind public is reserved for the workspace public room"
8948
+ });
8949
+ }
8950
+ if (existingProject?.kind === "public") {
8951
+ return reply.status(409).send({
8952
+ success: false,
8953
+ error: "The public room project classification cannot be changed"
8954
+ });
8955
+ }
8956
+ }
8813
8957
  const project = await this.projectStore.updateProject(
8814
8958
  tenantId,
8815
8959
  projectId,
@@ -8840,6 +8984,12 @@ var WorkspaceController = class {
8840
8984
  });
8841
8985
  }
8842
8986
  }
8987
+ if (project.kind === "public") {
8988
+ return reply.status(409).send({
8989
+ success: false,
8990
+ error: "The public room project cannot be deleted"
8991
+ });
8992
+ }
8843
8993
  const deleted = await this.projectStore.deleteProject(tenantId, projectId);
8844
8994
  if (!deleted) {
8845
8995
  return reply.status(404).send({ success: false, error: "Project not found" });
@@ -11595,7 +11745,7 @@ function registerTenantRoutes(app2) {
11595
11745
  // src/controllers/auth.ts
11596
11746
  var import_core35 = require("@axiom-lattice/core");
11597
11747
  var import_uuid8 = require("uuid");
11598
- var import_node_crypto = require("crypto");
11748
+ var import_node_crypto3 = require("crypto");
11599
11749
  var defaultAuthConfig = {
11600
11750
  autoApproveUsers: true,
11601
11751
  allowTenantRegistration: true,
@@ -11950,7 +12100,7 @@ var AuthController = class {
11950
12100
  function resolveConsoleTokenSecret(_authRequired) {
11951
12101
  const configured = process.env.AUTH_TOKEN_SECRET || process.env.JWT_SECRET;
11952
12102
  if (configured) return configured;
11953
- devSessionSecret ?? (devSessionSecret = (0, import_node_crypto.randomBytes)(32).toString("hex"));
12103
+ devSessionSecret ?? (devSessionSecret = (0, import_node_crypto3.randomBytes)(32).toString("hex"));
11954
12104
  console.warn(
11955
12105
  "[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."
11956
12106
  );
@@ -11959,7 +12109,7 @@ function resolveConsoleTokenSecret(_authRequired) {
11959
12109
  var devSessionSecret;
11960
12110
  function createConsoleSessionToken(payload, secret) {
11961
12111
  const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
11962
- const signature = (0, import_node_crypto.createHmac)("sha256", secret).update(encodedPayload).digest("base64url");
12112
+ const signature = (0, import_node_crypto3.createHmac)("sha256", secret).update(encodedPayload).digest("base64url");
11963
12113
  return `${encodedPayload}.${signature}`;
11964
12114
  }
11965
12115
  function isConsoleSessionPayload(value) {
@@ -11986,8 +12136,8 @@ function verifySignedConsoleSession(authHeader, secret) {
11986
12136
  try {
11987
12137
  const actualSignature = Buffer.from(encodedSignature, "base64url");
11988
12138
  if (actualSignature.toString("base64url") !== encodedSignature) return void 0;
11989
- const expectedSignature = (0, import_node_crypto.createHmac)("sha256", secret).update(encodedPayload).digest();
11990
- if (actualSignature.length !== expectedSignature.length || !(0, import_node_crypto.timingSafeEqual)(actualSignature, expectedSignature)) {
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)) {
11991
12141
  return void 0;
11992
12142
  }
11993
12143
  const payload = JSON.parse(
@@ -13568,7 +13718,7 @@ async function getLocalA2ATemplates(_request, reply) {
13568
13718
  }
13569
13719
 
13570
13720
  // src/routes/a2a-test-connection.ts
13571
- var import_node_crypto2 = require("crypto");
13721
+ var import_node_crypto4 = require("crypto");
13572
13722
  function describeTestConnectionError(error, agentCardUrl) {
13573
13723
  if (!(error instanceof Error)) {
13574
13724
  return `Failed to fetch agent card from ${agentCardUrl}: ${String(error)}`;
@@ -13608,7 +13758,7 @@ async function ensureLocalRuntimeForConnectionTest(localRuntime, agentCardUrl) {
13608
13758
  return { error: portValidation.error };
13609
13759
  }
13610
13760
  const state = await localA2AService.startProvider(
13611
- `test-${(0, import_node_crypto2.randomUUID)()}`,
13761
+ `test-${(0, import_node_crypto4.randomUUID)()}`,
13612
13762
  testRuntime
13613
13763
  );
13614
13764
  if (state.status === "running") return { runtimeId: state.runtimeId };
@@ -13686,7 +13836,7 @@ var import_protocols10 = require("@axiom-lattice/protocols");
13686
13836
  var import_core42 = require("@axiom-lattice/core");
13687
13837
 
13688
13838
  // src/services/collaboration/ProjectAccessService.ts
13689
- var import_node_crypto3 = require("crypto");
13839
+ var import_node_crypto5 = require("crypto");
13690
13840
  var ProjectCollaborationError = class extends Error {
13691
13841
  /** Creates a project collaboration error. */
13692
13842
  constructor(code, statusCode, message) {
@@ -13712,7 +13862,7 @@ var ProjectAccessService = class {
13712
13862
  /** Creates a project access service from explicitly injected stores. */
13713
13863
  constructor(deps) {
13714
13864
  this.deps = deps;
13715
- this.idFactory = deps.idFactory ?? import_node_crypto3.randomUUID;
13865
+ this.idFactory = deps.idFactory ?? import_node_crypto5.randomUUID;
13716
13866
  }
13717
13867
  /** Returns the project and active membership when the user has the requested capability. */
13718
13868
  async requireProjectAccess(input) {
@@ -13720,6 +13870,9 @@ var ProjectAccessService = class {
13720
13870
  if (!project) {
13721
13871
  throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
13722
13872
  }
13873
+ if (project.kind === "public") {
13874
+ return this.requirePublicProjectAccess(project, input);
13875
+ }
13723
13876
  const membership = await this.deps.membershipStore.findByUser(
13724
13877
  input.tenantId,
13725
13878
  input.projectId,
@@ -13764,10 +13917,13 @@ var ProjectAccessService = class {
13764
13917
  this.requireCapability(racedMembership, input.capability);
13765
13918
  return { project, membership: racedMembership };
13766
13919
  }
13767
- /** Authorizes only an already-existing active membership and never mutates persistence. */
13920
+ /** Authorizes an already-existing membership, or any tenant member for public rooms; never mutates persistence. */
13768
13921
  async requireExistingProjectAccess(input) {
13769
13922
  const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
13770
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
+ }
13771
13927
  const membership = await this.deps.membershipStore.findByUser(
13772
13928
  input.tenantId,
13773
13929
  input.projectId,
@@ -13777,6 +13933,87 @@ var ProjectAccessService = class {
13777
13933
  this.requireCapability(membership, input.capability);
13778
13934
  return { project, membership };
13779
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
+ }
13780
14017
  requireCapability(membership, capability2) {
13781
14018
  if (!allowedRoles[capability2].has(membership.role)) this.accessDenied();
13782
14019
  }
@@ -14992,7 +15229,7 @@ function invalidRequest(reply, statusCode) {
14992
15229
  var import_zod6 = require("zod");
14993
15230
 
14994
15231
  // src/services/agent-web-app/AgentWebAppRuntimeService.ts
14995
- var import_node_crypto4 = require("crypto");
15232
+ var import_node_crypto6 = require("crypto");
14996
15233
  var import_protocols11 = require("@axiom-lattice/protocols");
14997
15234
  init_task_file_refs();
14998
15235
  function isRecord2(value) {
@@ -15116,10 +15353,10 @@ ${formatFileRefsSection(refs)}` : input.content;
15116
15353
  const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, selection);
15117
15354
  this.requireNamedFeature(runtime.webApp.features.attachments, "Attachments are disabled");
15118
15355
  if (!this.deps.saveProjectFile || !this.deps.attachmentRefs) this.fail("INTERNAL_ERROR", 500, "Attachment service is unavailable");
15119
- const userDigest = (0, import_node_crypto4.createHash)("sha256").update(userId, "utf8").digest("hex");
15120
- const contentDigest = (0, import_node_crypto4.createHash)("sha256").update(file.bytes).digest("hex");
15356
+ const userDigest = (0, import_node_crypto6.createHash)("sha256").update(userId, "utf8").digest("hex");
15357
+ const contentDigest = (0, import_node_crypto6.createHash)("sha256").update(file.bytes).digest("hex");
15121
15358
  const displayName = file.name;
15122
- const physicalName = `${(0, import_node_crypto4.randomUUID)()}-${sanitizePhysicalFilename(file.name)}`;
15359
+ const physicalName = `${(0, import_node_crypto6.randomUUID)()}-${sanitizePhysicalFilename(file.name)}`;
15123
15360
  const saved = await this.deps.saveProjectFile({
15124
15361
  tenantId: runtime.webApp.tenantId,
15125
15362
  workspaceId: runtime.project.workspaceId,
@@ -15271,7 +15508,7 @@ ${formatFileRefsSection(refs)}` : input.content;
15271
15508
  const thread = await this.deps.threadStore.createThread(
15272
15509
  runtime.webApp.tenantId,
15273
15510
  runtime.webApp.assistantId,
15274
- (0, import_node_crypto4.randomUUID)(),
15511
+ (0, import_node_crypto6.randomUUID)(),
15275
15512
  { metadata: this.metadata(runtime, userId, normalizeLabel(rawLabel)) }
15276
15513
  );
15277
15514
  return toRuntimeThread(thread);
@@ -15323,7 +15560,7 @@ function implicitThreadId(runtime, userId) {
15323
15560
  userId,
15324
15561
  runtime.project.id
15325
15562
  ]);
15326
- const bytes = (0, import_node_crypto4.createHash)("sha256").update(canonicalTuple, "utf8").digest().subarray(0, 16);
15563
+ const bytes = (0, import_node_crypto6.createHash)("sha256").update(canonicalTuple, "utf8").digest().subarray(0, 16);
15327
15564
  bytes[6] = bytes[6] & 15 | 128;
15328
15565
  bytes[8] = bytes[8] & 63 | 128;
15329
15566
  const hex = bytes.toString("hex");
@@ -15588,7 +15825,7 @@ function isMultipartRequestError(error) {
15588
15825
  }
15589
15826
 
15590
15827
  // src/services/agent-web-app/AgentWebAppAttachmentRef.ts
15591
- var import_node_crypto5 = require("crypto");
15828
+ var import_node_crypto7 = require("crypto");
15592
15829
  var TTL_MS = 15 * 6e4;
15593
15830
  function resolveAgentWebAppAttachmentSecret(consoleSecret) {
15594
15831
  return process.env.WEB_APP_ATTACHMENT_SECRET || process.env.AUTH_TOKEN_SECRET || process.env.JWT_SECRET || consoleSecret;
@@ -15597,7 +15834,7 @@ var AgentWebAppAttachmentRefService = class {
15597
15834
  constructor(secret, now = Date.now) {
15598
15835
  this.now = now;
15599
15836
  if (!secret) throw new Error("Attachment signing secret is required");
15600
- this.key = (0, import_node_crypto5.createHash)("sha256").update(secret, "utf8").digest();
15837
+ this.key = (0, import_node_crypto7.createHash)("sha256").update(secret, "utf8").digest();
15601
15838
  }
15602
15839
  sign(input) {
15603
15840
  const payload = {
@@ -15612,8 +15849,8 @@ var AgentWebAppAttachmentRefService = class {
15612
15849
  contentDigest: input.contentDigest,
15613
15850
  exp: this.now() + TTL_MS
15614
15851
  };
15615
- const iv = (0, import_node_crypto5.randomBytes)(12);
15616
- const cipher = (0, import_node_crypto5.createCipheriv)("aes-256-gcm", this.key, iv);
15852
+ const iv = (0, import_node_crypto7.randomBytes)(12);
15853
+ const cipher = (0, import_node_crypto7.createCipheriv)("aes-256-gcm", this.key, iv);
15617
15854
  const ciphertext = Buffer.concat([
15618
15855
  cipher.update(JSON.stringify(payload), "utf8"),
15619
15856
  cipher.final()
@@ -15628,7 +15865,7 @@ var AgentWebAppAttachmentRefService = class {
15628
15865
  const iv = decodeCanonical(encodedIv, 12);
15629
15866
  const ciphertext = decodeCanonical(encodedCiphertext);
15630
15867
  const tag = decodeCanonical(encodedTag, 16);
15631
- const decipher = (0, import_node_crypto5.createDecipheriv)("aes-256-gcm", this.key, iv);
15868
+ const decipher = (0, import_node_crypto7.createDecipheriv)("aes-256-gcm", this.key, iv);
15632
15869
  decipher.setAuthTag(tag);
15633
15870
  const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
15634
15871
  const value = JSON.parse(plaintext.toString("utf8"));
@@ -15659,7 +15896,7 @@ function decodeCanonical(value, expectedBytes) {
15659
15896
  return decoded;
15660
15897
  }
15661
15898
  function digestUser(userId) {
15662
- return (0, import_node_crypto5.createHash)("sha256").update(userId, "utf8").digest("hex");
15899
+ return (0, import_node_crypto7.createHash)("sha256").update(userId, "utf8").digest("hex");
15663
15900
  }
15664
15901
  function isAttachmentPayload(value) {
15665
15902
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -16023,12 +16260,12 @@ function registerCapabilityBundleRoutes(app2, controller) {
16023
16260
  }
16024
16261
 
16025
16262
  // src/services/collaboration/ProjectRoomService.ts
16026
- var import_node_crypto6 = require("crypto");
16263
+ var import_node_crypto8 = require("crypto");
16027
16264
  var ProjectRoomService = class {
16028
16265
  /** Creates a project room service from explicitly injected collaborators. */
16029
16266
  constructor(deps) {
16030
16267
  this.deps = deps;
16031
- this.idFactory = deps.idFactory ?? import_node_crypto6.randomUUID;
16268
+ this.idFactory = deps.idFactory ?? import_node_crypto8.randomUUID;
16032
16269
  }
16033
16270
  /** Authorizes read access and atomically ensures the project's main room. */
16034
16271
  async ensureMainRoom(input) {
@@ -16047,7 +16284,7 @@ var ProjectRoomService = class {
16047
16284
  };
16048
16285
 
16049
16286
  // src/services/collaboration/ProjectMembershipService.ts
16050
- var import_node_crypto7 = require("crypto");
16287
+ var import_node_crypto9 = require("crypto");
16051
16288
  var ProjectMembershipServiceError = class extends Error {
16052
16289
  constructor(code, statusCode, message) {
16053
16290
  super(message);
@@ -16063,11 +16300,13 @@ function isMembershipConflict(error) {
16063
16300
  var ProjectMembershipService = class {
16064
16301
  constructor(deps) {
16065
16302
  this.deps = deps;
16066
- this.idFactory = deps.idFactory ?? import_node_crypto7.randomUUID;
16303
+ this.idFactory = deps.idFactory ?? import_node_crypto9.randomUUID;
16067
16304
  this.mutationMutex = deps.projectMutationMutex ?? import_core41.projectMutationMutex;
16068
16305
  }
16069
16306
  async list(actor2) {
16070
- await this.authorize(actor2);
16307
+ await this.deps.accessService.requireRosterAccess(actor2);
16308
+ const membership = await this.deps.membershipStore.findByUser(actor2.tenantId, actor2.projectId, actor2.userId);
16309
+ if (!membership || membership.status !== "active") this.accessDenied();
16071
16310
  return this.deps.membershipStore.list(actor2.tenantId, actor2.projectId);
16072
16311
  }
16073
16312
  async add(actor2, input) {
@@ -16187,7 +16426,7 @@ var ProjectMembershipService = class {
16187
16426
  };
16188
16427
 
16189
16428
  // src/services/collaboration/ProjectRosterService.ts
16190
- var import_node_crypto8 = require("crypto");
16429
+ var import_node_crypto10 = require("crypto");
16191
16430
  var ProjectRosterServiceError = class extends Error {
16192
16431
  /** Creates a stable roster service error. */
16193
16432
  constructor(code, statusCode, message) {
@@ -16204,7 +16443,7 @@ var ProjectRosterService = class {
16204
16443
  /** Creates a roster service from explicitly injected collaborators. */
16205
16444
  constructor(deps) {
16206
16445
  this.deps = deps;
16207
- this.idFactory = deps.idFactory ?? import_node_crypto8.randomUUID;
16446
+ this.idFactory = deps.idFactory ?? import_node_crypto10.randomUUID;
16208
16447
  this.mutationMutex = deps.projectMutationMutex ?? import_core41.projectMutationMutex;
16209
16448
  }
16210
16449
  /** Lists all bot memberships visible to a project reader. */
@@ -16424,7 +16663,7 @@ var ProjectRosterService = class {
16424
16663
  };
16425
16664
 
16426
16665
  // src/services/collaboration/RoomMessageService.ts
16427
- var import_node_crypto9 = require("crypto");
16666
+ var import_node_crypto11 = require("crypto");
16428
16667
 
16429
16668
  // src/services/collaboration/ProjectBotMembershipSnapshot.ts
16430
16669
  var import_protocols13 = require("@axiom-lattice/protocols");
@@ -16513,7 +16752,7 @@ var RoomMessageService = class {
16513
16752
  constructor(deps, options = {}) {
16514
16753
  this.deps = deps;
16515
16754
  this.idempotentOutcomes = /* @__PURE__ */ new Map();
16516
- this.idFactory = deps.idFactory ?? import_node_crypto9.randomUUID;
16755
+ this.idFactory = deps.idFactory ?? import_node_crypto11.randomUUID;
16517
16756
  const limit = options.idempotentOutcomeLimit ?? 1e3;
16518
16757
  if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError("idempotentOutcomeLimit must be a positive safe integer");
16519
16758
  this.idempotentOutcomeLimit = limit;
@@ -16694,7 +16933,7 @@ var RoomMessageService = class {
16694
16933
  let idempotencyKey;
16695
16934
  if (value.requestIdempotencyKey !== void 0) {
16696
16935
  if (typeof value.requestIdempotencyKey !== "string" || !/^[\x21-\x7e]{1,200}$/.test(value.requestIdempotencyKey.trim())) throw new RoomMessageServiceError("INVALID_MESSAGE", 400, "Invalid idempotency key");
16697
- idempotencyKey = `room-human:${actor2.userId}:${(0, import_node_crypto9.createHash)("sha256").update(value.requestIdempotencyKey.trim(), "utf8").digest("hex")}`;
16936
+ idempotencyKey = `room-human:${actor2.userId}:${(0, import_node_crypto11.createHash)("sha256").update(value.requestIdempotencyKey.trim(), "utf8").digest("hex")}`;
16698
16937
  }
16699
16938
  return { text, mentions, botIds, ...value.replyToMessageId === void 0 ? {} : { replyToMessageId: value.replyToMessageId }, ...idempotencyKey === void 0 ? {} : { idempotencyKey } };
16700
16939
  }
@@ -16751,7 +16990,7 @@ function cloneRoomMessage(message) {
16751
16990
  };
16752
16991
  }
16753
16992
  function fingerprintRequest(input) {
16754
- return (0, import_node_crypto9.createHash)("sha256").update(JSON.stringify({
16993
+ return (0, import_node_crypto11.createHash)("sha256").update(JSON.stringify({
16755
16994
  text: input.text,
16756
16995
  mentions: input.mentions,
16757
16996
  replyToMessageId: input.replyToMessageId ?? null
@@ -16920,42 +17159,9 @@ async function execute(reply, operation) {
16920
17159
  }
16921
17160
 
16922
17161
  // src/routes/cospace.ts
16923
- var import_node_crypto10 = require("crypto");
17162
+ var import_node_crypto12 = require("crypto");
16924
17163
  var import_zod12 = require("zod");
16925
17164
  var import_core44 = require("@axiom-lattice/core");
16926
-
16927
- // src/services/collaboration/RoomCoordinatorProvisioner.ts
16928
- var ROOM_COORDINATOR_KEY = "room-coordinator";
16929
- var RoomCoordinatorProvisioner = class {
16930
- /** Creates a provisioner over the tenant-scoped assistant store operations. */
16931
- constructor(deps) {
16932
- this.deps = deps;
16933
- }
16934
- /**
16935
- * Ensures the Room Coordinator assistant exists, reusing an existing record
16936
- * and reconciling concurrent create races by rereading the winner.
16937
- *
16938
- * @param tenantId Tenant to provision the coordinator for.
16939
- * @returns The persisted Room Coordinator assistant.
16940
- */
16941
- async ensure(tenantId) {
16942
- const existing = await this.deps.getAssistantById(tenantId, ROOM_COORDINATOR_KEY);
16943
- if (existing) return existing;
16944
- try {
16945
- return await this.deps.createAssistant(tenantId, ROOM_COORDINATOR_KEY, {
16946
- name: "Room Coordinator",
16947
- description: "Coordinates project room conversations and organizes room work.",
16948
- graphDefinition: {}
16949
- });
16950
- } catch (error) {
16951
- const winner = await this.deps.getAssistantById(tenantId, ROOM_COORDINATOR_KEY);
16952
- if (winner) return winner;
16953
- throw error;
16954
- }
16955
- }
16956
- };
16957
-
16958
- // src/routes/cospace.ts
16959
17165
  var CoSpaceAuthError = class extends Error {
16960
17166
  constructor() {
16961
17167
  super(...arguments);
@@ -16971,7 +17177,7 @@ function getActor2(request) {
16971
17177
  return { tenantId: user.tenantId, userId: user.id };
16972
17178
  }
16973
17179
  function registerCoSpaceRoutes(app2, deps) {
16974
- const idFactory = deps.idFactory ?? (() => (0, import_node_crypto10.randomUUID)());
17180
+ const idFactory = deps.idFactory ?? (() => (0, import_node_crypto12.randomUUID)());
16975
17181
  app2.get(
16976
17182
  "/api/workspaces/:workspaceId/rooms",
16977
17183
  async (request, reply) => {
@@ -16996,6 +17202,7 @@ function registerCoSpaceRoutes(app2, deps) {
16996
17202
  );
16997
17203
  const accessibleProjects = [];
16998
17204
  for (const project of allProjects) {
17205
+ if (project.kind === "public") continue;
16999
17206
  const membership = await deps.membershipStore.findByUser(
17000
17207
  actor2.tenantId,
17001
17208
  project.id,
@@ -17049,6 +17256,34 @@ function registerCoSpaceRoutes(app2, deps) {
17049
17256
  const bTime = b.lastMessage?.createdAt?.getTime() ?? 0;
17050
17257
  return bTime - aTime;
17051
17258
  });
17259
+ const publicRoom = await deps.publicRoom.ensure(actor2.tenantId, params.workspaceId);
17260
+ if (publicRoom) {
17261
+ const publicMessages = await deps.messageStore.list({
17262
+ tenantId: actor2.tenantId,
17263
+ roomId: publicRoom.room.id,
17264
+ limit: 1
17265
+ });
17266
+ const publicMemberships = await deps.membershipStore.list(
17267
+ actor2.tenantId,
17268
+ publicRoom.project.id
17269
+ );
17270
+ const publicParticipantNames = [];
17271
+ for (const m of publicMemberships) {
17272
+ if (m.status !== "active") continue;
17273
+ const user = await deps.userStore.getUserById(m.userId);
17274
+ if (user) {
17275
+ publicParticipantNames.push(user.name);
17276
+ }
17277
+ }
17278
+ rooms.unshift({
17279
+ project: publicRoom.project,
17280
+ room: publicRoom.room,
17281
+ lastMessage: publicMessages[0],
17282
+ participantCount: publicMemberships.filter((m) => m.status === "active").length,
17283
+ participantNames: publicParticipantNames,
17284
+ isPublic: true
17285
+ });
17286
+ }
17052
17287
  return { data: { rooms } };
17053
17288
  });
17054
17289
  }
@@ -17805,7 +18040,7 @@ function exactValues(value, required, optional = []) {
17805
18040
  }
17806
18041
 
17807
18042
  // src/services/collaboration/CoordinatorWakeNotifier.ts
17808
- var import_node_crypto11 = require("crypto");
18043
+ var import_node_crypto13 = require("crypto");
17809
18044
  var import_core45 = require("@axiom-lattice/core");
17810
18045
  var import_protocols15 = require("@axiom-lattice/protocols");
17811
18046
  var SYNTHETIC_WAKE_AUTHOR = "coordinator-wake";
@@ -17820,7 +18055,7 @@ var CoordinatorWakeNotifier = class {
17820
18055
  this.accepted = /* @__PURE__ */ new Set();
17821
18056
  this.maxPending = deps.maxPending ?? 1024;
17822
18057
  if (!Number.isSafeInteger(this.maxPending) || this.maxPending < 1) throw new RangeError("maxPending must be a positive safe integer");
17823
- this.idFactory = deps.idFactory ?? import_node_crypto11.randomUUID;
18058
+ this.idFactory = deps.idFactory ?? import_node_crypto13.randomUUID;
17824
18059
  }
17825
18060
  /** Admits one notification without awaiting any store or dispatch operation. */
17826
18061
  handle(value) {
@@ -18683,7 +18918,7 @@ function validRecoveryRow(row, task, thread) {
18683
18918
  }
18684
18919
 
18685
18920
  // src/services/collaboration/ProjectTaskObserver.ts
18686
- var import_node_crypto12 = require("crypto");
18921
+ var import_node_crypto14 = require("crypto");
18687
18922
  var import_core47 = require("@axiom-lattice/core");
18688
18923
  var SYSTEM_ACTOR2 = "system";
18689
18924
  var TERMINAL_ACTIONS = ["completed", "failed", "interrupted", "cancelled"];
@@ -19220,7 +19455,7 @@ var ProjectTaskObserver = class {
19220
19455
  action: "activity",
19221
19456
  summary: "Project task recovery warning",
19222
19457
  detail: { type: "project_task_recovery_warning", phase, message },
19223
- eventKey: `project-task-recovery-warning:${task.id}:${phase}:${(0, import_node_crypto12.createHash)("sha256").update(message).digest("hex")}`
19458
+ eventKey: `project-task-recovery-warning:${task.id}:${phase}:${(0, import_node_crypto14.createHash)("sha256").update(message).digest("hex")}`
19224
19459
  });
19225
19460
  }
19226
19461
  async safeWarning(task, phase, error) {
@@ -19287,7 +19522,7 @@ var ProjectTaskObserver = class {
19287
19522
  }
19288
19523
  }
19289
19524
  async persistOverflowWarning(event) {
19290
- const scopeDigest = (0, import_node_crypto12.createHash)("sha256").update(JSON.stringify([event.tenantId, event.workspaceId, event.projectId])).digest("hex");
19525
+ const scopeDigest = (0, import_node_crypto14.createHash)("sha256").update(JSON.stringify([event.tenantId, event.workspaceId, event.projectId])).digest("hex");
19291
19526
  try {
19292
19527
  await this.deps.workItems.createIfAbsentByEventKey({
19293
19528
  tenantId: event.tenantId,
@@ -19388,7 +19623,7 @@ function combineSignals(left, right) {
19388
19623
  }
19389
19624
 
19390
19625
  // src/services/collaboration/RoomProjectionService.ts
19391
- var import_node_crypto13 = require("crypto");
19626
+ var import_node_crypto15 = require("crypto");
19392
19627
  var import_core48 = require("@axiom-lattice/core");
19393
19628
  var import_protocols18 = require("@axiom-lattice/protocols");
19394
19629
  var RoomProjectionService = class {
@@ -19557,7 +19792,7 @@ function safeRepairIdentity(value, scope) {
19557
19792
  return taskId && eventKey && tenantId === scope.tenantId && workspaceId === scope.workspaceId && projectId === scope.projectId ? { taskId, eventKey } : void 0;
19558
19793
  }
19559
19794
  function tupleKey(prefix, values) {
19560
- const digest = (0, import_node_crypto13.createHash)("sha256").update(JSON.stringify(values)).digest("hex");
19795
+ const digest = (0, import_node_crypto15.createHash)("sha256").update(JSON.stringify(values)).digest("hex");
19561
19796
  return `${prefix}:${digest}`;
19562
19797
  }
19563
19798
  function parseProjectionInput(taskValue, itemValue) {
@@ -19740,7 +19975,7 @@ function isRecord4(value) {
19740
19975
  var import_core50 = require("@axiom-lattice/core");
19741
19976
 
19742
19977
  // src/services/collaboration/ProjectRoomEventBroker.ts
19743
- var import_node_crypto14 = require("crypto");
19978
+ var import_node_crypto16 = require("crypto");
19744
19979
  var import_protocols19 = require("@axiom-lattice/protocols");
19745
19980
  var DEFAULT_MAX_EVENTS_PER_ROOM = 1e3;
19746
19981
  var DEFAULT_MAX_EVENT_AGE_MS = 10 * 60 * 1e3;
@@ -19765,7 +20000,7 @@ var ProjectRoomEventBroker = class {
19765
20000
  this.subscriberCount = 0;
19766
20001
  this.usageClock = 0;
19767
20002
  this.closed = false;
19768
- this.epoch = options.epoch ?? (0, import_node_crypto14.randomUUID)();
20003
+ this.epoch = options.epoch ?? (0, import_node_crypto16.randomUUID)();
19769
20004
  if (!UUID_PATTERN.test(this.epoch)) {
19770
20005
  throw new TypeError("Project Room event epoch must be a lowercase canonical UUID");
19771
20006
  }
@@ -20083,7 +20318,7 @@ function snapshotJson(value, ancestors = /* @__PURE__ */ new Set()) {
20083
20318
  }
20084
20319
 
20085
20320
  // src/services/collaboration/ProjectRoomRealtimePublisher.ts
20086
- var import_node_crypto15 = require("crypto");
20321
+ var import_node_crypto17 = require("crypto");
20087
20322
  var import_protocols20 = require("@axiom-lattice/protocols");
20088
20323
  var BrokerProjectRoomRealtimePublisher = class {
20089
20324
  /** Creates a publisher around a broker and structured warning logger. */
@@ -20159,7 +20394,7 @@ var BrokerProjectRoomRealtimePublisher = class {
20159
20394
  try {
20160
20395
  event = build();
20161
20396
  if (!event) throw new Error("Invalid Project Room realtime projection");
20162
- const dedupeKey = (0, import_node_crypto15.createHash)("sha256").update(JSON.stringify([type, event.scope.tenantId, event.scope.projectId, event.scope.roomId, event.sourceKey])).digest("hex");
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");
20163
20398
  if (this.sourceKeys.has(dedupeKey)) return;
20164
20399
  this.deps.broker.publish(event.scope, event.draft);
20165
20400
  this.sourceKeys.set(dedupeKey, true);
@@ -21098,12 +21333,24 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
21098
21333
  });
21099
21334
  }
21100
21335
  if (projectTaskComposition) app2.addHook("onClose", async () => projectTaskComposition.lease.release());
21101
- const taskThreadService = projectTaskComposition?.lease.runtime.threads ?? new ProjectTaskThreadService({ threads: threadStore, idFactory: import_node_crypto16.randomUUID });
21336
+ const taskThreadService = projectTaskComposition?.lease.runtime.threads ?? new ProjectTaskThreadService({ threads: threadStore, idFactory: import_node_crypto18.randomUUID });
21102
21337
  const taskLifecycle = projectTaskComposition?.lease.runtime.lifecycle ?? (0, import_core51.createTaskLifecycleService)({ taskStore, workItemStore: taskWorkItemStore });
21103
21338
  const reassignmentAuthorizer = (0, import_core51.createProjectTaskReassignmentAuthorizer)(
21104
21339
  new import_core51.ProjectTaskOwnerPolicy(projectBotMembershipStore),
21105
21340
  (input) => validateReassignmentThread(threadStore, input)
21106
21341
  );
21342
+ const coordinatorProvisioner = new RoomCoordinatorProvisioner({
21343
+ getAssistantById: assistantStore.getAssistantById.bind(assistantStore),
21344
+ createAssistant: assistantStore.createAssistant.bind(assistantStore)
21345
+ });
21346
+ const publicRoomService = new WorkspacePublicRoomService({
21347
+ getProjectById: projectStore.getProjectById.bind(projectStore),
21348
+ createProject: projectStore.createProject.bind(projectStore),
21349
+ ensureMainRoom: projectRoomStore.ensureMainRoom.bind(projectRoomStore),
21350
+ findBotByAssistant: projectBotMembershipStore.findByAssistant.bind(projectBotMembershipStore),
21351
+ saveBot: projectBotMembershipStore.save.bind(projectBotMembershipStore),
21352
+ ensureCoordinator: (tenantId) => coordinatorProvisioner.ensure(tenantId)
21353
+ });
21107
21354
  registerProjectRoomRoutes(app2, { roomService, membershipService, messageService });
21108
21355
  registerProjectRosterRoutes(app2, { service: rosterService });
21109
21356
  registerCoSpaceRoutes(app2, {
@@ -21142,14 +21389,14 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
21142
21389
  findByAssistant: projectBotMembershipStore.findByAssistant.bind(projectBotMembershipStore),
21143
21390
  save: projectBotMembershipStore.save.bind(projectBotMembershipStore)
21144
21391
  },
21145
- coordinatorProvisioner: new RoomCoordinatorProvisioner({
21146
- getAssistantById: assistantStore.getAssistantById.bind(assistantStore),
21147
- createAssistant: assistantStore.createAssistant.bind(assistantStore)
21148
- }),
21392
+ coordinatorProvisioner,
21149
21393
  rosterService: {
21150
21394
  addBot: async (actor2, input) => {
21151
21395
  await rosterService.add(actor2, input);
21152
21396
  }
21397
+ },
21398
+ publicRoom: {
21399
+ ensure: (tenantId, workspaceId) => publicRoomService.ensure(tenantId, workspaceId)
21153
21400
  }
21154
21401
  });
21155
21402
  registerCapabilityBundleRoutes(app2, new CapabilityBundleController({
@@ -21590,7 +21837,7 @@ function tryCreateProjectTaskRuntime(input) {
21590
21837
  function createRuntime(input, stores, log2) {
21591
21838
  const controller = new AbortController();
21592
21839
  const mutex = new ProjectTaskExecutionMutex();
21593
- const threads = new ProjectTaskThreadService({ threads: input.threadStore, idFactory: import_node_crypto16.randomUUID });
21840
+ const threads = new ProjectTaskThreadService({ threads: input.threadStore, idFactory: import_node_crypto18.randomUUID });
21594
21841
  const lifecycle = (0, import_core51.createTaskLifecycleService)({ taskStore: input.taskStore, workItemStore: input.taskWorkItemStore });
21595
21842
  const projection = new RoomProjectionService({
21596
21843
  messages: input.projectRoomMessageStore,
@@ -21599,7 +21846,7 @@ function createRuntime(input, stores, log2) {
21599
21846
  warnings: input.taskWorkItemStore,
21600
21847
  log: log2,
21601
21848
  realtime: input.realtime,
21602
- idFactory: import_node_crypto16.randomUUID
21849
+ idFactory: import_node_crypto18.randomUUID
21603
21850
  });
21604
21851
  const agentFor = (task, thread) => import_core51.agentInstanceManager.getAgent({
21605
21852
  tenant_id: task.tenantId,
@@ -24036,7 +24283,11 @@ function initializeLogger(config) {
24036
24283
  }
24037
24284
  var app = (0, import_fastify.default)({
24038
24285
  logger: false,
24039
- bodyLimit: Number(process.env.BODY_LIMIT) || 50 * 1024 * 1024
24286
+ bodyLimit: Number(process.env.BODY_LIMIT) || 50 * 1024 * 1024,
24287
+ // Composite subagent thread IDs ({mainThread}____{assistant}-general-purpose_{toolCallId})
24288
+ // can exceed the 100-char default, which makes the router 404 before any
24289
+ // handler runs. Allow the longest IDs the conventions can produce.
24290
+ routerOptions: { maxParamLength: 512 }
24040
24291
  });
24041
24292
  app.addContentTypeParser("application/json", { parseAs: "string" }, function(request, body, done) {
24042
24293
  if (request.method === "DELETE" || !body || body.length === 0) {