@axiom-lattice/gateway 4.1.5 → 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/{a2a-standard-G3J5ZHQ7.mjs → a2a-standard-WC2BSOFV.mjs} +2 -2
- package/dist/a2a-standard-WC2BSOFV.mjs.map +1 -0
- package/dist/index.js +1025 -294
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1022 -294
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/dist/a2a-standard-G3J5ZHQ7.mjs.map +0 -1
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,
|
|
1515
|
+
const digest = (0, import_node_crypto21.createHash)("sha256").update(JSON.stringify({
|
|
1516
1516
|
state: task.status.state,
|
|
1517
1517
|
timestamp: task.status.timestamp,
|
|
1518
1518
|
message: task.status.message,
|
|
@@ -1522,7 +1522,7 @@ function observationEventKey(task, result, failureReason) {
|
|
|
1522
1522
|
return `a2a-status-observation:${task.id}:${digest}`;
|
|
1523
1523
|
}
|
|
1524
1524
|
function snapshotEventKey(task) {
|
|
1525
|
-
const digest = (0,
|
|
1525
|
+
const digest = (0, import_node_crypto21.createHash)("sha256").update(JSON.stringify(task)).digest("hex");
|
|
1526
1526
|
return `a2a-task-snapshot:${task.id}:${digest}`;
|
|
1527
1527
|
}
|
|
1528
1528
|
function isGovernedObservation(state) {
|
|
@@ -1534,11 +1534,11 @@ function isA2ATaskSnapshot(value) {
|
|
|
1534
1534
|
function snapshotSequence(value) {
|
|
1535
1535
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
1536
1536
|
}
|
|
1537
|
-
var
|
|
1537
|
+
var import_node_crypto21, import_node_util, TASK_STATUS_TO_A2A_STATE, TITLE_MAX_LENGTH, A2ATaskScopeConflictError, A2ATaskStoreAdapter;
|
|
1538
1538
|
var init_A2ATaskStoreAdapter = __esm({
|
|
1539
1539
|
"src/services/a2a/A2ATaskStoreAdapter.ts"() {
|
|
1540
1540
|
"use strict";
|
|
1541
|
-
|
|
1541
|
+
import_node_crypto21 = require("crypto");
|
|
1542
1542
|
import_node_util = require("util");
|
|
1543
1543
|
TASK_STATUS_TO_A2A_STATE = {
|
|
1544
1544
|
pending: "submitted",
|
|
@@ -1999,7 +1999,7 @@ ${formatFileRefsSection(inputFiles)}` : text;
|
|
|
1999
1999
|
const messageText = [
|
|
2000
2000
|
`A2A governed task instruction: get and update the existing TaskItem ${ctx.taskId}; do not create a replacement task.`,
|
|
2001
2001
|
"Before doing the requested work, initialize its canonical description through manage_task with ## Objective, ## Acceptance Criteria, and ## Belief State sections.",
|
|
2002
|
-
"Keep lifecycle status truthful.
|
|
2002
|
+
"Keep lifecycle status truthful. Change status only through manage_task set_status; complete with a nonblank result and structured beliefImpact, and use interrupted, failed, or cancelled when appropriate. Use manage_task update for content fields only.",
|
|
2003
2003
|
"",
|
|
2004
2004
|
requestText
|
|
2005
2005
|
].join("\n");
|
|
@@ -3313,7 +3313,7 @@ var import_static = __toESM(require("@fastify/static"));
|
|
|
3313
3313
|
var import_path = __toESM(require("path"));
|
|
3314
3314
|
|
|
3315
3315
|
// src/routes/index.ts
|
|
3316
|
-
var
|
|
3316
|
+
var import_node_crypto20 = 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" });
|
|
@@ -9538,7 +9688,195 @@ function registerWorkspaceRoutes(app2, projectCollaboration) {
|
|
|
9538
9688
|
"/api/filesystem/browse",
|
|
9539
9689
|
controller.browseFilesystem.bind(controller)
|
|
9540
9690
|
);
|
|
9691
|
+
return controller;
|
|
9692
|
+
}
|
|
9693
|
+
|
|
9694
|
+
// src/services/collaboration/ProjectAccessService.ts
|
|
9695
|
+
var import_node_crypto3 = require("crypto");
|
|
9696
|
+
var ProjectCollaborationError = class extends Error {
|
|
9697
|
+
/** Creates a project collaboration error. */
|
|
9698
|
+
constructor(code, statusCode, message) {
|
|
9699
|
+
super(message);
|
|
9700
|
+
this.code = code;
|
|
9701
|
+
this.statusCode = statusCode;
|
|
9702
|
+
this.name = "ProjectCollaborationError";
|
|
9703
|
+
}
|
|
9704
|
+
};
|
|
9705
|
+
var allowedRoles = {
|
|
9706
|
+
read: /* @__PURE__ */ new Set(["owner", "admin", "member", "viewer"]),
|
|
9707
|
+
send: /* @__PURE__ */ new Set(["owner", "admin", "member"]),
|
|
9708
|
+
manage: /* @__PURE__ */ new Set(["owner", "admin"])
|
|
9709
|
+
};
|
|
9710
|
+
var membershipConflictNames = /* @__PURE__ */ new Set([
|
|
9711
|
+
"DuplicateProjectMembershipError",
|
|
9712
|
+
"ProjectMembershipIdConflictError"
|
|
9713
|
+
]);
|
|
9714
|
+
function isProjectMembershipConflict(error) {
|
|
9715
|
+
return error instanceof Error && membershipConflictNames.has(error.name);
|
|
9541
9716
|
}
|
|
9717
|
+
var ProjectAccessService = class {
|
|
9718
|
+
/** Creates a project access service from explicitly injected stores. */
|
|
9719
|
+
constructor(deps) {
|
|
9720
|
+
this.deps = deps;
|
|
9721
|
+
this.idFactory = deps.idFactory ?? import_node_crypto3.randomUUID;
|
|
9722
|
+
}
|
|
9723
|
+
/** Returns the project and active membership when the user has the requested capability. */
|
|
9724
|
+
async requireProjectAccess(input) {
|
|
9725
|
+
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
9726
|
+
if (!project) {
|
|
9727
|
+
throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
9728
|
+
}
|
|
9729
|
+
if (project.kind === "public") {
|
|
9730
|
+
return this.requirePublicProjectAccess(project, input);
|
|
9731
|
+
}
|
|
9732
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
9733
|
+
input.tenantId,
|
|
9734
|
+
input.projectId,
|
|
9735
|
+
input.userId
|
|
9736
|
+
);
|
|
9737
|
+
if (membership?.status === "active") {
|
|
9738
|
+
this.requireCapability(membership, input.capability);
|
|
9739
|
+
return { project, membership };
|
|
9740
|
+
}
|
|
9741
|
+
const memberships = await this.deps.membershipStore.list(input.tenantId, input.projectId);
|
|
9742
|
+
if (memberships.length !== 0) this.accessDenied();
|
|
9743
|
+
if (project.kind === "personal") this.accessDenied();
|
|
9744
|
+
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
9745
|
+
if (tenantLink?.role !== "owner" && tenantLink?.role !== "admin") this.accessDenied();
|
|
9746
|
+
let claim;
|
|
9747
|
+
try {
|
|
9748
|
+
claim = await this.deps.membershipStore.createInitialOwner({
|
|
9749
|
+
id: this.idFactory(),
|
|
9750
|
+
tenantId: input.tenantId,
|
|
9751
|
+
projectId: input.projectId,
|
|
9752
|
+
userId: input.userId
|
|
9753
|
+
});
|
|
9754
|
+
} catch (error) {
|
|
9755
|
+
if (isProjectMembershipConflict(error)) {
|
|
9756
|
+
throw new ProjectCollaborationError(
|
|
9757
|
+
"PROJECT_MEMBERSHIP_CONFLICT",
|
|
9758
|
+
409,
|
|
9759
|
+
"Project membership could not be initialized"
|
|
9760
|
+
);
|
|
9761
|
+
}
|
|
9762
|
+
throw error;
|
|
9763
|
+
}
|
|
9764
|
+
if (claim.kind === "created" || claim.kind === "existing") {
|
|
9765
|
+
return { project, membership: claim.membership };
|
|
9766
|
+
}
|
|
9767
|
+
const racedMembership = await this.deps.membershipStore.findByUser(
|
|
9768
|
+
input.tenantId,
|
|
9769
|
+
input.projectId,
|
|
9770
|
+
input.userId
|
|
9771
|
+
);
|
|
9772
|
+
if (racedMembership?.status !== "active") this.accessDenied();
|
|
9773
|
+
this.requireCapability(racedMembership, input.capability);
|
|
9774
|
+
return { project, membership: racedMembership };
|
|
9775
|
+
}
|
|
9776
|
+
/** Authorizes an already-existing membership, or any tenant member for public rooms; never mutates persistence. */
|
|
9777
|
+
async requireExistingProjectAccess(input) {
|
|
9778
|
+
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
9779
|
+
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
9780
|
+
if (project.kind === "public") {
|
|
9781
|
+
return this.requireExistingPublicProjectAccess(project, input);
|
|
9782
|
+
}
|
|
9783
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
9784
|
+
input.tenantId,
|
|
9785
|
+
input.projectId,
|
|
9786
|
+
input.userId
|
|
9787
|
+
);
|
|
9788
|
+
if (!membership || membership.tenantId !== input.tenantId || membership.projectId !== input.projectId || membership.userId !== input.userId || membership.status !== "active") this.accessDenied();
|
|
9789
|
+
this.requireCapability(membership, input.capability);
|
|
9790
|
+
return { project, membership };
|
|
9791
|
+
}
|
|
9792
|
+
/** Authorizes roster visibility: read for public rooms, manage elsewhere. */
|
|
9793
|
+
async requireRosterAccess(input) {
|
|
9794
|
+
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
9795
|
+
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
9796
|
+
return this.requireProjectAccess({
|
|
9797
|
+
...input,
|
|
9798
|
+
capability: project.kind === "public" ? "read" : "manage"
|
|
9799
|
+
});
|
|
9800
|
+
}
|
|
9801
|
+
async requirePublicProjectAccess(project, input) {
|
|
9802
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
9803
|
+
input.tenantId,
|
|
9804
|
+
input.projectId,
|
|
9805
|
+
input.userId
|
|
9806
|
+
);
|
|
9807
|
+
if (membership?.status === "active") {
|
|
9808
|
+
this.requireCapability(membership, input.capability);
|
|
9809
|
+
return { project, membership };
|
|
9810
|
+
}
|
|
9811
|
+
const materialized = await this.materializePublicMembership(input);
|
|
9812
|
+
this.requireCapability(materialized, input.capability);
|
|
9813
|
+
return { project, membership: materialized };
|
|
9814
|
+
}
|
|
9815
|
+
async requireExistingPublicProjectAccess(project, input) {
|
|
9816
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
9817
|
+
input.tenantId,
|
|
9818
|
+
input.projectId,
|
|
9819
|
+
input.userId
|
|
9820
|
+
);
|
|
9821
|
+
if (membership?.status === "active") {
|
|
9822
|
+
this.requireCapability(membership, input.capability);
|
|
9823
|
+
return { project, membership };
|
|
9824
|
+
}
|
|
9825
|
+
if (membership) this.accessDenied();
|
|
9826
|
+
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
9827
|
+
if (!tenantLink) this.accessDenied();
|
|
9828
|
+
const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
|
|
9829
|
+
const synthetic = {
|
|
9830
|
+
id: `public:${input.userId}`,
|
|
9831
|
+
tenantId: input.tenantId,
|
|
9832
|
+
projectId: input.projectId,
|
|
9833
|
+
userId: input.userId,
|
|
9834
|
+
role,
|
|
9835
|
+
status: "active",
|
|
9836
|
+
joinedAt: project.createdAt,
|
|
9837
|
+
updatedAt: project.updatedAt
|
|
9838
|
+
};
|
|
9839
|
+
this.requireCapability(synthetic, input.capability);
|
|
9840
|
+
return { project, membership: synthetic };
|
|
9841
|
+
}
|
|
9842
|
+
async materializePublicMembership(input) {
|
|
9843
|
+
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
9844
|
+
if (!tenantLink) this.accessDenied();
|
|
9845
|
+
const existing = await this.deps.membershipStore.findByUser(
|
|
9846
|
+
input.tenantId,
|
|
9847
|
+
input.projectId,
|
|
9848
|
+
input.userId
|
|
9849
|
+
);
|
|
9850
|
+
if (existing) this.accessDenied();
|
|
9851
|
+
const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
|
|
9852
|
+
try {
|
|
9853
|
+
return await this.deps.membershipStore.create({
|
|
9854
|
+
id: this.idFactory(),
|
|
9855
|
+
tenantId: input.tenantId,
|
|
9856
|
+
projectId: input.projectId,
|
|
9857
|
+
userId: input.userId,
|
|
9858
|
+
role,
|
|
9859
|
+
status: "active"
|
|
9860
|
+
});
|
|
9861
|
+
} catch (error) {
|
|
9862
|
+
if (isProjectMembershipConflict(error)) {
|
|
9863
|
+
const raced = await this.deps.membershipStore.findByUser(
|
|
9864
|
+
input.tenantId,
|
|
9865
|
+
input.projectId,
|
|
9866
|
+
input.userId
|
|
9867
|
+
);
|
|
9868
|
+
if (raced?.status === "active") return raced;
|
|
9869
|
+
}
|
|
9870
|
+
throw error;
|
|
9871
|
+
}
|
|
9872
|
+
}
|
|
9873
|
+
requireCapability(membership, capability2) {
|
|
9874
|
+
if (!allowedRoles[capability2].has(membership.role)) this.accessDenied();
|
|
9875
|
+
}
|
|
9876
|
+
accessDenied() {
|
|
9877
|
+
throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project access denied");
|
|
9878
|
+
}
|
|
9879
|
+
};
|
|
9542
9880
|
|
|
9543
9881
|
// src/controllers/database-configs.ts
|
|
9544
9882
|
var import_core27 = require("@axiom-lattice/core");
|
|
@@ -11595,7 +11933,7 @@ function registerTenantRoutes(app2) {
|
|
|
11595
11933
|
// src/controllers/auth.ts
|
|
11596
11934
|
var import_core35 = require("@axiom-lattice/core");
|
|
11597
11935
|
var import_uuid8 = require("uuid");
|
|
11598
|
-
var
|
|
11936
|
+
var import_node_crypto4 = require("crypto");
|
|
11599
11937
|
var defaultAuthConfig = {
|
|
11600
11938
|
autoApproveUsers: true,
|
|
11601
11939
|
allowTenantRegistration: true,
|
|
@@ -11950,7 +12288,7 @@ var AuthController = class {
|
|
|
11950
12288
|
function resolveConsoleTokenSecret(_authRequired) {
|
|
11951
12289
|
const configured = process.env.AUTH_TOKEN_SECRET || process.env.JWT_SECRET;
|
|
11952
12290
|
if (configured) return configured;
|
|
11953
|
-
devSessionSecret ?? (devSessionSecret = (0,
|
|
12291
|
+
devSessionSecret ?? (devSessionSecret = (0, import_node_crypto4.randomBytes)(32).toString("hex"));
|
|
11954
12292
|
console.warn(
|
|
11955
12293
|
"[auth] AUTH_TOKEN_SECRET/JWT_SECRET is not set; using an in-process generated signing secret (sessions are invalidated on restart). Set AUTH_TOKEN_SECRET for production."
|
|
11956
12294
|
);
|
|
@@ -11959,7 +12297,7 @@ function resolveConsoleTokenSecret(_authRequired) {
|
|
|
11959
12297
|
var devSessionSecret;
|
|
11960
12298
|
function createConsoleSessionToken(payload, secret) {
|
|
11961
12299
|
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
11962
|
-
const signature = (0,
|
|
12300
|
+
const signature = (0, import_node_crypto4.createHmac)("sha256", secret).update(encodedPayload).digest("base64url");
|
|
11963
12301
|
return `${encodedPayload}.${signature}`;
|
|
11964
12302
|
}
|
|
11965
12303
|
function isConsoleSessionPayload(value) {
|
|
@@ -11986,8 +12324,8 @@ function verifySignedConsoleSession(authHeader, secret) {
|
|
|
11986
12324
|
try {
|
|
11987
12325
|
const actualSignature = Buffer.from(encodedSignature, "base64url");
|
|
11988
12326
|
if (actualSignature.toString("base64url") !== encodedSignature) return void 0;
|
|
11989
|
-
const expectedSignature = (0,
|
|
11990
|
-
if (actualSignature.length !== expectedSignature.length || !(0,
|
|
12327
|
+
const expectedSignature = (0, import_node_crypto4.createHmac)("sha256", secret).update(encodedPayload).digest();
|
|
12328
|
+
if (actualSignature.length !== expectedSignature.length || !(0, import_node_crypto4.timingSafeEqual)(actualSignature, expectedSignature)) {
|
|
11991
12329
|
return void 0;
|
|
11992
12330
|
}
|
|
11993
12331
|
const payload = JSON.parse(
|
|
@@ -13568,7 +13906,7 @@ async function getLocalA2ATemplates(_request, reply) {
|
|
|
13568
13906
|
}
|
|
13569
13907
|
|
|
13570
13908
|
// src/routes/a2a-test-connection.ts
|
|
13571
|
-
var
|
|
13909
|
+
var import_node_crypto5 = require("crypto");
|
|
13572
13910
|
function describeTestConnectionError(error, agentCardUrl) {
|
|
13573
13911
|
if (!(error instanceof Error)) {
|
|
13574
13912
|
return `Failed to fetch agent card from ${agentCardUrl}: ${String(error)}`;
|
|
@@ -13608,7 +13946,7 @@ async function ensureLocalRuntimeForConnectionTest(localRuntime, agentCardUrl) {
|
|
|
13608
13946
|
return { error: portValidation.error };
|
|
13609
13947
|
}
|
|
13610
13948
|
const state = await localA2AService.startProvider(
|
|
13611
|
-
`test-${(0,
|
|
13949
|
+
`test-${(0, import_node_crypto5.randomUUID)()}`,
|
|
13612
13950
|
testRuntime
|
|
13613
13951
|
);
|
|
13614
13952
|
if (state.status === "running") return { runtimeId: state.runtimeId };
|
|
@@ -13685,106 +14023,6 @@ var import_core51 = require("@axiom-lattice/core");
|
|
|
13685
14023
|
var import_protocols10 = require("@axiom-lattice/protocols");
|
|
13686
14024
|
var import_core42 = require("@axiom-lattice/core");
|
|
13687
14025
|
|
|
13688
|
-
// src/services/collaboration/ProjectAccessService.ts
|
|
13689
|
-
var import_node_crypto3 = require("crypto");
|
|
13690
|
-
var ProjectCollaborationError = class extends Error {
|
|
13691
|
-
/** Creates a project collaboration error. */
|
|
13692
|
-
constructor(code, statusCode, message) {
|
|
13693
|
-
super(message);
|
|
13694
|
-
this.code = code;
|
|
13695
|
-
this.statusCode = statusCode;
|
|
13696
|
-
this.name = "ProjectCollaborationError";
|
|
13697
|
-
}
|
|
13698
|
-
};
|
|
13699
|
-
var allowedRoles = {
|
|
13700
|
-
read: /* @__PURE__ */ new Set(["owner", "admin", "member", "viewer"]),
|
|
13701
|
-
send: /* @__PURE__ */ new Set(["owner", "admin", "member"]),
|
|
13702
|
-
manage: /* @__PURE__ */ new Set(["owner", "admin"])
|
|
13703
|
-
};
|
|
13704
|
-
var membershipConflictNames = /* @__PURE__ */ new Set([
|
|
13705
|
-
"DuplicateProjectMembershipError",
|
|
13706
|
-
"ProjectMembershipIdConflictError"
|
|
13707
|
-
]);
|
|
13708
|
-
function isProjectMembershipConflict(error) {
|
|
13709
|
-
return error instanceof Error && membershipConflictNames.has(error.name);
|
|
13710
|
-
}
|
|
13711
|
-
var ProjectAccessService = class {
|
|
13712
|
-
/** Creates a project access service from explicitly injected stores. */
|
|
13713
|
-
constructor(deps) {
|
|
13714
|
-
this.deps = deps;
|
|
13715
|
-
this.idFactory = deps.idFactory ?? import_node_crypto3.randomUUID;
|
|
13716
|
-
}
|
|
13717
|
-
/** Returns the project and active membership when the user has the requested capability. */
|
|
13718
|
-
async requireProjectAccess(input) {
|
|
13719
|
-
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
13720
|
-
if (!project) {
|
|
13721
|
-
throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
13722
|
-
}
|
|
13723
|
-
const membership = await this.deps.membershipStore.findByUser(
|
|
13724
|
-
input.tenantId,
|
|
13725
|
-
input.projectId,
|
|
13726
|
-
input.userId
|
|
13727
|
-
);
|
|
13728
|
-
if (membership?.status === "active") {
|
|
13729
|
-
this.requireCapability(membership, input.capability);
|
|
13730
|
-
return { project, membership };
|
|
13731
|
-
}
|
|
13732
|
-
const memberships = await this.deps.membershipStore.list(input.tenantId, input.projectId);
|
|
13733
|
-
if (memberships.length !== 0) this.accessDenied();
|
|
13734
|
-
if (project.kind === "personal") this.accessDenied();
|
|
13735
|
-
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
13736
|
-
if (tenantLink?.role !== "owner" && tenantLink?.role !== "admin") this.accessDenied();
|
|
13737
|
-
let claim;
|
|
13738
|
-
try {
|
|
13739
|
-
claim = await this.deps.membershipStore.createInitialOwner({
|
|
13740
|
-
id: this.idFactory(),
|
|
13741
|
-
tenantId: input.tenantId,
|
|
13742
|
-
projectId: input.projectId,
|
|
13743
|
-
userId: input.userId
|
|
13744
|
-
});
|
|
13745
|
-
} catch (error) {
|
|
13746
|
-
if (isProjectMembershipConflict(error)) {
|
|
13747
|
-
throw new ProjectCollaborationError(
|
|
13748
|
-
"PROJECT_MEMBERSHIP_CONFLICT",
|
|
13749
|
-
409,
|
|
13750
|
-
"Project membership could not be initialized"
|
|
13751
|
-
);
|
|
13752
|
-
}
|
|
13753
|
-
throw error;
|
|
13754
|
-
}
|
|
13755
|
-
if (claim.kind === "created" || claim.kind === "existing") {
|
|
13756
|
-
return { project, membership: claim.membership };
|
|
13757
|
-
}
|
|
13758
|
-
const racedMembership = await this.deps.membershipStore.findByUser(
|
|
13759
|
-
input.tenantId,
|
|
13760
|
-
input.projectId,
|
|
13761
|
-
input.userId
|
|
13762
|
-
);
|
|
13763
|
-
if (racedMembership?.status !== "active") this.accessDenied();
|
|
13764
|
-
this.requireCapability(racedMembership, input.capability);
|
|
13765
|
-
return { project, membership: racedMembership };
|
|
13766
|
-
}
|
|
13767
|
-
/** Authorizes only an already-existing active membership and never mutates persistence. */
|
|
13768
|
-
async requireExistingProjectAccess(input) {
|
|
13769
|
-
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
13770
|
-
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
13771
|
-
const membership = await this.deps.membershipStore.findByUser(
|
|
13772
|
-
input.tenantId,
|
|
13773
|
-
input.projectId,
|
|
13774
|
-
input.userId
|
|
13775
|
-
);
|
|
13776
|
-
if (!membership || membership.tenantId !== input.tenantId || membership.projectId !== input.projectId || membership.userId !== input.userId || membership.status !== "active") this.accessDenied();
|
|
13777
|
-
this.requireCapability(membership, input.capability);
|
|
13778
|
-
return { project, membership };
|
|
13779
|
-
}
|
|
13780
|
-
requireCapability(membership, capability2) {
|
|
13781
|
-
if (!allowedRoles[capability2].has(membership.role)) this.accessDenied();
|
|
13782
|
-
}
|
|
13783
|
-
accessDenied() {
|
|
13784
|
-
throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project access denied");
|
|
13785
|
-
}
|
|
13786
|
-
};
|
|
13787
|
-
|
|
13788
14026
|
// src/services/collaboration/ProjectTaskThreadService.ts
|
|
13789
14027
|
var import_core40 = require("@axiom-lattice/core");
|
|
13790
14028
|
var import_protocols9 = require("@axiom-lattice/protocols");
|
|
@@ -14316,9 +14554,6 @@ function governedUpdate(task, body) {
|
|
|
14316
14554
|
if (has("ownerType") && body.ownerType !== task.ownerType || has("ownerId") && body.ownerId !== task.ownerId) {
|
|
14317
14555
|
return "TASK_OWNERSHIP_IMMUTABLE";
|
|
14318
14556
|
}
|
|
14319
|
-
if (task.ownerType !== "agent") return void 0;
|
|
14320
|
-
if (has("parentId") && body.parentId !== task.parentId || has("dependencies") || has("requireReview") || has("result") || has("failureReason") || has("status")) return "TASK_LIFECYCLE_TRANSITION_REQUIRED";
|
|
14321
|
-
if (has("context") || has("description")) return "COMPLETION_CONTEXT_MUTATION";
|
|
14322
14557
|
return void 0;
|
|
14323
14558
|
}
|
|
14324
14559
|
function lifecycleStatus(code) {
|
|
@@ -14759,7 +14994,8 @@ var AgentWebAppService = class {
|
|
|
14759
14994
|
integration: current.integration,
|
|
14760
14995
|
scope: { ...current.scope, ...patch.scope },
|
|
14761
14996
|
features: { ...current.features, ...patch.features },
|
|
14762
|
-
appearance: { ...current.appearance, ...patch.appearance }
|
|
14997
|
+
appearance: { ...current.appearance, ...patch.appearance },
|
|
14998
|
+
identity: patch.identity ?? current.identity
|
|
14763
14999
|
};
|
|
14764
15000
|
await this.validateAssistant(context, current.assistantId);
|
|
14765
15001
|
const validated = await this.validateConfig(context.tenantId, effective);
|
|
@@ -14768,7 +15004,8 @@ var AgentWebAppService = class {
|
|
|
14768
15004
|
...Object.prototype.hasOwnProperty.call(patch, "description") ? { description: validated.description } : {},
|
|
14769
15005
|
...patch.scope ? { scope: validated.scope } : {},
|
|
14770
15006
|
...patch.features ? { features: validated.features } : {},
|
|
14771
|
-
...patch.appearance ? { appearance: validated.appearance } : {}
|
|
15007
|
+
...patch.appearance ? { appearance: validated.appearance } : {},
|
|
15008
|
+
...patch.identity !== void 0 ? { identity: patch.identity } : {}
|
|
14772
15009
|
};
|
|
14773
15010
|
return this.writeSnapshot(context, current, update);
|
|
14774
15011
|
}
|
|
@@ -14881,13 +15118,28 @@ var agentWebAppFeaturesSchema = import_zod4.z.object({
|
|
|
14881
15118
|
threadManagement: import_zod4.z.boolean(),
|
|
14882
15119
|
attachments: import_zod4.z.boolean(),
|
|
14883
15120
|
hitl: import_zod4.z.boolean(),
|
|
14884
|
-
genUI: import_zod4.z.boolean()
|
|
15121
|
+
genUI: import_zod4.z.boolean(),
|
|
15122
|
+
files: import_zod4.z.boolean().optional()
|
|
14885
15123
|
}).strict();
|
|
14886
15124
|
var agentWebAppAppearanceSchema = import_zod4.z.object({
|
|
14887
15125
|
title: import_zod4.z.string().max(120).optional(),
|
|
14888
15126
|
welcomeMessage: import_zod4.z.string().max(1e3).optional(),
|
|
14889
15127
|
primaryColor: import_zod4.z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional()
|
|
14890
15128
|
}).strict();
|
|
15129
|
+
var agentWebAppIssuerSchema = import_zod4.z.object({
|
|
15130
|
+
iss: import_zod4.z.string().min(1).max(255),
|
|
15131
|
+
aud: import_zod4.z.string().min(1).max(255),
|
|
15132
|
+
alg: import_zod4.z.enum(["HS256", "RS256"]),
|
|
15133
|
+
secret: import_zod4.z.string().min(1).max(512).optional(),
|
|
15134
|
+
jwksUrl: import_zod4.z.string().url().max(2048).optional()
|
|
15135
|
+
}).strict().refine(
|
|
15136
|
+
(value) => value.alg === "HS256" ? value.secret !== void 0 : value.jwksUrl !== void 0,
|
|
15137
|
+
{ message: "HS256 requires secret; RS256 requires jwksUrl" }
|
|
15138
|
+
);
|
|
15139
|
+
var agentWebAppIdentitySchema = import_zod4.z.object({
|
|
15140
|
+
policy: import_zod4.z.enum(["unverified", "verified", "both"]),
|
|
15141
|
+
issuers: import_zod4.z.array(agentWebAppIssuerSchema).min(1).max(10)
|
|
15142
|
+
}).strict();
|
|
14891
15143
|
var createAgentWebAppSchema = import_zod4.z.object({
|
|
14892
15144
|
assistantId: externalId,
|
|
14893
15145
|
name: import_zod4.z.string().min(1).max(120),
|
|
@@ -14895,14 +15147,16 @@ var createAgentWebAppSchema = import_zod4.z.object({
|
|
|
14895
15147
|
integration: import_zod4.z.object({ type: import_zod4.z.literal("react_sdk") }).strict(),
|
|
14896
15148
|
scope: agentWebAppScopeSchema,
|
|
14897
15149
|
features: agentWebAppFeaturesSchema,
|
|
14898
|
-
appearance: agentWebAppAppearanceSchema
|
|
15150
|
+
appearance: agentWebAppAppearanceSchema,
|
|
15151
|
+
identity: agentWebAppIdentitySchema.optional()
|
|
14899
15152
|
}).strict();
|
|
14900
15153
|
var updateAgentWebAppSchema = import_zod4.z.object({
|
|
14901
15154
|
name: import_zod4.z.string().min(1).max(120).optional(),
|
|
14902
15155
|
description: import_zod4.z.string().max(1e3).optional(),
|
|
14903
15156
|
scope: agentWebAppScopeSchema.partial().strict().optional(),
|
|
14904
15157
|
features: agentWebAppFeaturesSchema.partial().strict().optional(),
|
|
14905
|
-
appearance: agentWebAppAppearanceSchema.partial().strict().optional()
|
|
15158
|
+
appearance: agentWebAppAppearanceSchema.partial().strict().optional(),
|
|
15159
|
+
identity: agentWebAppIdentitySchema.optional()
|
|
14906
15160
|
}).strict().refine((value) => Object.keys(value).length > 0, "Patch must not be empty");
|
|
14907
15161
|
var listAgentWebAppsQuerySchema = import_zod4.z.object({
|
|
14908
15162
|
assistantId: externalId.optional()
|
|
@@ -14989,10 +15243,11 @@ function invalidRequest(reply, statusCode) {
|
|
|
14989
15243
|
}
|
|
14990
15244
|
|
|
14991
15245
|
// src/routes/agent-web-app-runtime.ts
|
|
15246
|
+
var import_node_crypto8 = require("crypto");
|
|
14992
15247
|
var import_zod6 = require("zod");
|
|
14993
15248
|
|
|
14994
15249
|
// src/services/agent-web-app/AgentWebAppRuntimeService.ts
|
|
14995
|
-
var
|
|
15250
|
+
var import_node_crypto6 = require("crypto");
|
|
14996
15251
|
var import_protocols11 = require("@axiom-lattice/protocols");
|
|
14997
15252
|
init_task_file_refs();
|
|
14998
15253
|
function isRecord2(value) {
|
|
@@ -15010,7 +15265,16 @@ var AgentWebAppRuntimeService = class {
|
|
|
15010
15265
|
constructor(deps) {
|
|
15011
15266
|
this.deps = deps;
|
|
15012
15267
|
}
|
|
15013
|
-
|
|
15268
|
+
/** Identity policy configuration for the web app, or null when the app is missing/unconfigured. */
|
|
15269
|
+
async getIdentityConfig(webAppId) {
|
|
15270
|
+
try {
|
|
15271
|
+
const runtime = await this.resolveRuntime(webAppId, {});
|
|
15272
|
+
return runtime.webApp.identity ?? null;
|
|
15273
|
+
} catch {
|
|
15274
|
+
return null;
|
|
15275
|
+
}
|
|
15276
|
+
}
|
|
15277
|
+
async bootstrap(webAppId, userId, selection, identity) {
|
|
15014
15278
|
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
15015
15279
|
const assistant = await this.deps.assistantResolver.resolve(
|
|
15016
15280
|
runtime.webApp.tenantId,
|
|
@@ -15043,7 +15307,7 @@ var AgentWebAppRuntimeService = class {
|
|
|
15043
15307
|
defaultModelKey: runtime.webApp.scope.defaultModelKey,
|
|
15044
15308
|
features: runtime.webApp.features,
|
|
15045
15309
|
appearance: runtime.webApp.appearance,
|
|
15046
|
-
identityAssurance: "unverified"
|
|
15310
|
+
identityAssurance: identity?.assurance ?? "unverified"
|
|
15047
15311
|
},
|
|
15048
15312
|
projects,
|
|
15049
15313
|
models
|
|
@@ -15064,6 +15328,14 @@ var AgentWebAppRuntimeService = class {
|
|
|
15064
15328
|
this.requireFeature(runtime.webApp.features.threadManagement);
|
|
15065
15329
|
return this.createOwnedThread(runtime, userId, input.label);
|
|
15066
15330
|
}
|
|
15331
|
+
/** Fetch one owned thread. */
|
|
15332
|
+
async getThread(webAppId, userId, threadId, selection) {
|
|
15333
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
15334
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
15335
|
+
const thread = await this.deps.threadStore.getThreadById(runtime.webApp.tenantId, threadId);
|
|
15336
|
+
if (!thread) this.threadNotFound();
|
|
15337
|
+
return toRuntimeThread(thread);
|
|
15338
|
+
}
|
|
15067
15339
|
/** Update a thread label; omitted or blank labels preserve the existing label. */
|
|
15068
15340
|
async updateThread(webAppId, userId, threadId, input) {
|
|
15069
15341
|
const runtime = await this.resolveRuntime(webAppId, input);
|
|
@@ -15098,6 +15370,60 @@ var AgentWebAppRuntimeService = class {
|
|
|
15098
15370
|
return [{ id: message.id, role: message.role, ...content && (typeof content === "string" || content.length > 0) ? { content } : {} }];
|
|
15099
15371
|
});
|
|
15100
15372
|
}
|
|
15373
|
+
async getState(webAppId, userId, threadId, selection) {
|
|
15374
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
15375
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
15376
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
15377
|
+
const state = await agent.getCurrentState();
|
|
15378
|
+
const pendingMessages = await agent.getPendingMessages();
|
|
15379
|
+
const normalized = pendingMessages.map((message) => ({
|
|
15380
|
+
...message,
|
|
15381
|
+
content: typeof message.content === "object" && message.content !== null ? "message" in message.content ? message.content.message : JSON.stringify(message.content) : String(message.content ?? "")
|
|
15382
|
+
}));
|
|
15383
|
+
return { ...state && typeof state === "object" ? state : {}, pendingMessages: normalized };
|
|
15384
|
+
}
|
|
15385
|
+
/** Raw stored messages (Console memory contract, tool/interrupt preserved). */
|
|
15386
|
+
async getConsoleMessages(webAppId, userId, threadId, selection) {
|
|
15387
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
15388
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
15389
|
+
return this.deps.messageReader.getCurrentMessages({
|
|
15390
|
+
tenantId: runtime.webApp.tenantId,
|
|
15391
|
+
assistantId: runtime.webApp.assistantId,
|
|
15392
|
+
threadId,
|
|
15393
|
+
workspaceId: runtime.project.workspaceId,
|
|
15394
|
+
projectId: runtime.project.id
|
|
15395
|
+
});
|
|
15396
|
+
}
|
|
15397
|
+
/** Background enqueue only (ADR-026): returns the message id; delivery happens over resume_stream. */
|
|
15398
|
+
async enqueueMessage(webAppId, userId, threadId, content, selection) {
|
|
15399
|
+
const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, selection);
|
|
15400
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
15401
|
+
const execution = await this.execute(agent, runtime, { input: { message: content } });
|
|
15402
|
+
return { messageId: execution.messageId };
|
|
15403
|
+
}
|
|
15404
|
+
async removePendingMessage(webAppId, userId, threadId, messageId, selection) {
|
|
15405
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
15406
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
15407
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
15408
|
+
if (!await agent.removePendingMessage(messageId)) this.threadNotFound();
|
|
15409
|
+
}
|
|
15410
|
+
/** Thread-lifetime SSE resume from the last known client message id. */
|
|
15411
|
+
async resumeStream(webAppId, userId, threadId, messageId, selection) {
|
|
15412
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
15413
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
15414
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
15415
|
+
return { stream: agent.chunkStream(messageId, []) };
|
|
15416
|
+
}
|
|
15417
|
+
async resolveFileScope(webAppId, selection) {
|
|
15418
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
15419
|
+
return {
|
|
15420
|
+
tenantId: runtime.webApp.tenantId,
|
|
15421
|
+
workspaceId: runtime.project.workspaceId,
|
|
15422
|
+
projectId: runtime.project.id,
|
|
15423
|
+
assistantId: runtime.webApp.assistantId,
|
|
15424
|
+
enabled: runtime.webApp.features.files === true
|
|
15425
|
+
};
|
|
15426
|
+
}
|
|
15101
15427
|
async streamMessage(webAppId, userId, threadId, input) {
|
|
15102
15428
|
const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, input);
|
|
15103
15429
|
const refs = this.resolveAttachmentRefs(runtime, userId, threadId, input.attachmentRefs ?? []);
|
|
@@ -15116,10 +15442,10 @@ ${formatFileRefsSection(refs)}` : input.content;
|
|
|
15116
15442
|
const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, selection);
|
|
15117
15443
|
this.requireNamedFeature(runtime.webApp.features.attachments, "Attachments are disabled");
|
|
15118
15444
|
if (!this.deps.saveProjectFile || !this.deps.attachmentRefs) this.fail("INTERNAL_ERROR", 500, "Attachment service is unavailable");
|
|
15119
|
-
const userDigest = (0,
|
|
15120
|
-
const contentDigest = (0,
|
|
15445
|
+
const userDigest = (0, import_node_crypto6.createHash)("sha256").update(userId, "utf8").digest("hex");
|
|
15446
|
+
const contentDigest = (0, import_node_crypto6.createHash)("sha256").update(file.bytes).digest("hex");
|
|
15121
15447
|
const displayName = file.name;
|
|
15122
|
-
const physicalName = `${(0,
|
|
15448
|
+
const physicalName = `${(0, import_node_crypto6.randomUUID)()}-${sanitizePhysicalFilename(file.name)}`;
|
|
15123
15449
|
const saved = await this.deps.saveProjectFile({
|
|
15124
15450
|
tenantId: runtime.webApp.tenantId,
|
|
15125
15451
|
workspaceId: runtime.project.workspaceId,
|
|
@@ -15271,7 +15597,7 @@ ${formatFileRefsSection(refs)}` : input.content;
|
|
|
15271
15597
|
const thread = await this.deps.threadStore.createThread(
|
|
15272
15598
|
runtime.webApp.tenantId,
|
|
15273
15599
|
runtime.webApp.assistantId,
|
|
15274
|
-
(0,
|
|
15600
|
+
(0, import_node_crypto6.randomUUID)(),
|
|
15275
15601
|
{ metadata: this.metadata(runtime, userId, normalizeLabel(rawLabel)) }
|
|
15276
15602
|
);
|
|
15277
15603
|
return toRuntimeThread(thread);
|
|
@@ -15323,7 +15649,7 @@ function implicitThreadId(runtime, userId) {
|
|
|
15323
15649
|
userId,
|
|
15324
15650
|
runtime.project.id
|
|
15325
15651
|
]);
|
|
15326
|
-
const bytes = (0,
|
|
15652
|
+
const bytes = (0, import_node_crypto6.createHash)("sha256").update(canonicalTuple, "utf8").digest().subarray(0, 16);
|
|
15327
15653
|
bytes[6] = bytes[6] & 15 | 128;
|
|
15328
15654
|
bytes[8] = bytes[8] & 63 | 128;
|
|
15329
15655
|
const hex = bytes.toString("hex");
|
|
@@ -15342,93 +15668,102 @@ function toRuntimeThread(thread) {
|
|
|
15342
15668
|
};
|
|
15343
15669
|
}
|
|
15344
15670
|
|
|
15345
|
-
// src/services/agent-web-app/
|
|
15346
|
-
var
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
if (chunk.type === import_protocols12.MessageChunkTypes.AI) {
|
|
15352
|
-
const events = [];
|
|
15353
|
-
const startedToolIds = context?.startedToolIds ?? /* @__PURE__ */ new Set();
|
|
15354
|
-
if (typeof chunk.data.content === "string" && chunk.data.content) events.push({ type: "message.delta", text: chunk.data.content });
|
|
15355
|
-
if (context?.allowGenUI && Array.isArray(chunk.data.content)) {
|
|
15356
|
-
for (const value of chunk.data.content) {
|
|
15357
|
-
const block = (0, import_protocols12.parseAgentWebAppGenUIBlock)(value);
|
|
15358
|
-
if (block) events.push({ type: "genui.render", block });
|
|
15359
|
-
}
|
|
15360
|
-
}
|
|
15361
|
-
for (const call of chunk.data.tool_call_chunks ?? []) {
|
|
15362
|
-
if (call.id && call.name && !startedToolIds.has(call.id)) {
|
|
15363
|
-
startedToolIds.add(call.id);
|
|
15364
|
-
events.push({ type: "tool.started", id: call.id, name: call.name });
|
|
15365
|
-
}
|
|
15366
|
-
}
|
|
15367
|
-
for (const call of chunk.data.tool_calls ?? []) {
|
|
15368
|
-
if (!startedToolIds.has(call.id)) {
|
|
15369
|
-
startedToolIds.add(call.id);
|
|
15370
|
-
events.push({ type: "tool.started", id: call.id, name: call.name });
|
|
15371
|
-
}
|
|
15372
|
-
}
|
|
15373
|
-
return events;
|
|
15374
|
-
}
|
|
15375
|
-
if (chunk.type === import_protocols12.MessageChunkTypes.TOOL && chunk.data.tool_call_id) {
|
|
15376
|
-
return [{ type: "tool.completed", id: chunk.data.tool_call_id }];
|
|
15377
|
-
}
|
|
15378
|
-
if (chunk.type === import_protocols12.MessageChunkTypes.INTERRUPT) {
|
|
15379
|
-
if (context && !context.allowInterrupts) return [{ type: "stream.completed" }];
|
|
15380
|
-
const interrupt = publicInterrupt(chunk);
|
|
15381
|
-
return interrupt ? [{ type: "interrupt.created", interrupt }, { type: "stream.completed" }] : [{ type: "stream.completed" }];
|
|
15382
|
-
}
|
|
15383
|
-
if (chunk.type === import_protocols12.MessageChunkTypes.MESSAGE_COMPLETED) {
|
|
15384
|
-
return [
|
|
15385
|
-
{ type: "message.completed", messageId: chunk.data.id },
|
|
15386
|
-
{ type: "stream.completed" }
|
|
15387
|
-
];
|
|
15388
|
-
}
|
|
15389
|
-
if (chunk.type === import_protocols12.MessageChunkTypes.MESSAGE_FAILED) {
|
|
15390
|
-
return [
|
|
15391
|
-
{ type: "error", error: { code: "STREAM_FAILED", message: "Stream failed", retryable: true } },
|
|
15392
|
-
{ type: "stream.completed" }
|
|
15393
|
-
];
|
|
15671
|
+
// src/services/agent-web-app/AgentWebAppTokenVerifier.ts
|
|
15672
|
+
var import_node_crypto7 = require("crypto");
|
|
15673
|
+
var WebAppTokenError = class extends Error {
|
|
15674
|
+
constructor(message) {
|
|
15675
|
+
super(message);
|
|
15676
|
+
this.name = "WebAppTokenError";
|
|
15394
15677
|
}
|
|
15395
|
-
|
|
15396
|
-
|
|
15397
|
-
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
|
|
15401
|
-
const
|
|
15402
|
-
|
|
15403
|
-
return {
|
|
15404
|
-
id: id3,
|
|
15405
|
-
type: typeof value.type === "string" ? value.type : "input",
|
|
15406
|
-
prompt: typeof value.prompt === "string" ? value.prompt : typeof value.message === "string" ? value.message : "Input required",
|
|
15407
|
-
...isRecord3(value.data) ? { data: value.data } : {}
|
|
15408
|
-
};
|
|
15409
|
-
}
|
|
15410
|
-
function parseInterruptContent(content) {
|
|
15411
|
-
if (!content) return {};
|
|
15412
|
-
if (isRecord3(content)) return content;
|
|
15413
|
-
if (typeof content !== "string") return void 0;
|
|
15678
|
+
};
|
|
15679
|
+
var MAX_TOKEN_BYTES = 8 * 1024;
|
|
15680
|
+
var CLOCK_SKEW_SECONDS = 60;
|
|
15681
|
+
var JWKS_TTL_MS = 10 * 60 * 1e3;
|
|
15682
|
+
var jwksCache = /* @__PURE__ */ new Map();
|
|
15683
|
+
function decodeSegment(segment) {
|
|
15684
|
+
const json = Buffer.from(segment, "base64url").toString("utf8");
|
|
15685
|
+
let value;
|
|
15414
15686
|
try {
|
|
15415
|
-
|
|
15416
|
-
return isRecord3(parsed) ? parsed : { prompt: content };
|
|
15687
|
+
value = JSON.parse(json);
|
|
15417
15688
|
} catch {
|
|
15418
|
-
|
|
15689
|
+
throw new WebAppTokenError("Malformed token segment");
|
|
15419
15690
|
}
|
|
15691
|
+
if (typeof value !== "object" || value === null) throw new WebAppTokenError("Malformed token segment");
|
|
15692
|
+
return value;
|
|
15420
15693
|
}
|
|
15421
|
-
function
|
|
15422
|
-
|
|
15694
|
+
async function resolveKey(issuer, kid, fetchImpl) {
|
|
15695
|
+
const cacheKey = issuer.jwksUrl ?? "";
|
|
15696
|
+
const load = async () => {
|
|
15697
|
+
const response = await fetchImpl(issuer.jwksUrl ?? "");
|
|
15698
|
+
if (!response.ok) throw new WebAppTokenError("JWKS fetch failed");
|
|
15699
|
+
const document = await response.json();
|
|
15700
|
+
const keys2 = document.keys;
|
|
15701
|
+
const entry2 = { keys: Array.isArray(keys2) ? keys2 : [], fetchedAt: Date.now() };
|
|
15702
|
+
jwksCache.set(cacheKey, entry2);
|
|
15703
|
+
return entry2;
|
|
15704
|
+
};
|
|
15705
|
+
const cached = jwksCache.get(cacheKey);
|
|
15706
|
+
let entry = cached && Date.now() - cached.fetchedAt < JWKS_TTL_MS ? cached : await load();
|
|
15707
|
+
let jwk = entry.keys.find((key) => kid === void 0 ? true : key.kid === kid);
|
|
15708
|
+
if (!jwk && kid !== void 0) {
|
|
15709
|
+
entry = await load();
|
|
15710
|
+
jwk = entry.keys.find((key) => key.kid === kid);
|
|
15711
|
+
}
|
|
15712
|
+
if (!jwk) throw new WebAppTokenError("No matching JWKS key");
|
|
15713
|
+
return (0, import_node_crypto7.createPublicKey)({ key: jwk, format: "jwk" });
|
|
15714
|
+
}
|
|
15715
|
+
async function verifyWebAppToken(token, issuer, options = {}) {
|
|
15716
|
+
const nowMs = options.now ? options.now() : Date.now();
|
|
15717
|
+
const now = Math.floor(nowMs / 1e3);
|
|
15718
|
+
if (token.length === 0 || Buffer.byteLength(token) > MAX_TOKEN_BYTES) throw new WebAppTokenError("Token size invalid");
|
|
15719
|
+
const parts = token.split(".");
|
|
15720
|
+
if (parts.length !== 3) throw new WebAppTokenError("Token structure invalid");
|
|
15721
|
+
const [rawHeader, rawPayload, rawSignature] = parts;
|
|
15722
|
+
const header2 = decodeSegment(rawHeader);
|
|
15723
|
+
const payload = decodeSegment(rawPayload);
|
|
15724
|
+
if (header2.alg !== issuer.alg) throw new WebAppTokenError("Token algorithm mismatch");
|
|
15725
|
+
if (payload.iss !== issuer.iss) throw new WebAppTokenError("Token issuer mismatch");
|
|
15726
|
+
if (payload.aud !== issuer.aud) throw new WebAppTokenError("Token audience mismatch");
|
|
15727
|
+
const exp = typeof payload.exp === "number" ? payload.exp : void 0;
|
|
15728
|
+
if (exp === void 0 || exp + CLOCK_SKEW_SECONDS < now) throw new WebAppTokenError("Token expired");
|
|
15729
|
+
const nbf = typeof payload.nbf === "number" ? payload.nbf : void 0;
|
|
15730
|
+
if (nbf !== void 0 && nbf - CLOCK_SKEW_SECONDS > now) throw new WebAppTokenError("Token not yet valid");
|
|
15731
|
+
const signature = Buffer.from(rawSignature, "base64url");
|
|
15732
|
+
const data = Buffer.from(`${rawHeader}.${rawPayload}`);
|
|
15733
|
+
if (issuer.alg === "HS256") {
|
|
15734
|
+
if (typeof issuer.secret !== "string" || issuer.secret.length === 0) throw new WebAppTokenError("Issuer secret missing");
|
|
15735
|
+
const expected = (0, import_node_crypto7.createHmac)("sha256", issuer.secret).update(data).digest();
|
|
15736
|
+
if (signature.length !== expected.length || !(0, import_node_crypto7.timingSafeEqual)(signature, expected)) {
|
|
15737
|
+
throw new WebAppTokenError("Token signature invalid");
|
|
15738
|
+
}
|
|
15739
|
+
} else {
|
|
15740
|
+
if (typeof issuer.jwksUrl !== "string") throw new WebAppTokenError("Issuer JWKS URL missing");
|
|
15741
|
+
const key = await resolveKey(issuer, typeof header2.kid === "string" ? header2.kid : void 0, options.fetchImpl ?? fetch);
|
|
15742
|
+
let valid = false;
|
|
15743
|
+
try {
|
|
15744
|
+
valid = (0, import_node_crypto7.verify)("RSA-SHA256", data, key, signature);
|
|
15745
|
+
} catch {
|
|
15746
|
+
valid = false;
|
|
15747
|
+
}
|
|
15748
|
+
if (!valid) throw new WebAppTokenError("Token signature invalid");
|
|
15749
|
+
}
|
|
15750
|
+
const sub = payload.sub;
|
|
15751
|
+
if (typeof sub !== "string" || sub.length === 0 || sub.length > 128 || !/^[A-Za-z0-9._:@-]+$/.test(sub)) {
|
|
15752
|
+
throw new WebAppTokenError("Token subject invalid");
|
|
15753
|
+
}
|
|
15754
|
+
return sub;
|
|
15423
15755
|
}
|
|
15424
15756
|
|
|
15425
15757
|
// src/routes/agent-web-app-runtime.ts
|
|
15758
|
+
var import_protocols12 = require("@axiom-lattice/protocols");
|
|
15426
15759
|
var idSchema = import_zod6.z.string().min(1).max(128);
|
|
15427
15760
|
var paramsSchema = import_zod6.z.object({ webAppId: idSchema }).strict();
|
|
15428
15761
|
var threadParamsSchema = import_zod6.z.object({ webAppId: idSchema, threadId: idSchema }).strict();
|
|
15429
15762
|
var interruptParamsSchema = import_zod6.z.object({ webAppId: idSchema, threadId: idSchema, interruptId: idSchema }).strict();
|
|
15763
|
+
var pendingMessageParamsSchema = import_zod6.z.object({ webAppId: idSchema, threadId: idSchema, messageId: idSchema }).strict();
|
|
15430
15764
|
var bootstrapQuerySchema = import_zod6.z.object({ projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
|
|
15431
15765
|
var projectQuerySchema = import_zod6.z.object({ projectId: idSchema.optional() }).strict();
|
|
15766
|
+
var projectQueryLenientSchema = projectQuerySchema.passthrough();
|
|
15432
15767
|
var threadBodySchema = import_zod6.z.object({ projectId: idSchema.optional(), label: import_zod6.z.string().max(128).optional() }).strict();
|
|
15433
15768
|
var streamBodySchema = import_zod6.z.object({
|
|
15434
15769
|
content: import_zod6.z.string().trim().min(1).max(32768),
|
|
@@ -15447,8 +15782,27 @@ var resumeBodySchema = import_zod6.z.object({
|
|
|
15447
15782
|
])
|
|
15448
15783
|
}).strict();
|
|
15449
15784
|
var MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
|
|
15785
|
+
var stateQuerySchema = import_zod6.z.object({
|
|
15786
|
+
threadId: idSchema,
|
|
15787
|
+
projectId: idSchema.optional(),
|
|
15788
|
+
modelKey: idSchema.optional()
|
|
15789
|
+
}).strict();
|
|
15790
|
+
var resumeSchema = import_zod6.z.object({
|
|
15791
|
+
messageId: idSchema,
|
|
15792
|
+
projectId: idSchema.optional(),
|
|
15793
|
+
modelKey: idSchema.optional()
|
|
15794
|
+
}).strict();
|
|
15795
|
+
var devTokenSchema = import_zod6.z.object({
|
|
15796
|
+
iss: import_zod6.z.string().min(1).max(255).optional(),
|
|
15797
|
+
sub: import_zod6.z.string().regex(/^[A-Za-z0-9._:@-]+$/).max(128).optional(),
|
|
15798
|
+
hours: import_zod6.z.number().gt(0).max(720).optional()
|
|
15799
|
+
}).strict();
|
|
15800
|
+
var fileQuerySchema = import_zod6.z.object({
|
|
15801
|
+
projectId: idSchema.optional(),
|
|
15802
|
+
modelKey: idSchema.optional()
|
|
15803
|
+
}).passthrough();
|
|
15450
15804
|
function createExternalHeaderIdentityResolver() {
|
|
15451
|
-
return { resolve(request) {
|
|
15805
|
+
return { async resolve(request) {
|
|
15452
15806
|
const raw = request.headers["x-axiom-external-user-id"];
|
|
15453
15807
|
if (raw === void 0) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "External user id is required");
|
|
15454
15808
|
if (Array.isArray(raw) || typeof raw !== "string") throw new AgentWebAppRuntimeError("INVALID_USER_ID", 400, "External user id is invalid");
|
|
@@ -15457,47 +15811,109 @@ function createExternalHeaderIdentityResolver() {
|
|
|
15457
15811
|
return { userId, assurance: "unverified" };
|
|
15458
15812
|
} };
|
|
15459
15813
|
}
|
|
15814
|
+
function createWebAppIdentityResolver(deps) {
|
|
15815
|
+
const headerResolver = createExternalHeaderIdentityResolver();
|
|
15816
|
+
return {
|
|
15817
|
+
async resolve(request, webAppId) {
|
|
15818
|
+
let config;
|
|
15819
|
+
try {
|
|
15820
|
+
config = await deps.getIdentityConfig(webAppId);
|
|
15821
|
+
} catch {
|
|
15822
|
+
config = void 0;
|
|
15823
|
+
}
|
|
15824
|
+
const policy = config?.policy ?? "unverified";
|
|
15825
|
+
const authHeader = request.headers.authorization;
|
|
15826
|
+
const bearer = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : void 0;
|
|
15827
|
+
if (policy !== "unverified" && bearer !== void 0) {
|
|
15828
|
+
const issuers = config?.issuers ?? [];
|
|
15829
|
+
if (issuers.length === 0) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 401, "No trusted issuer configured");
|
|
15830
|
+
let lastError = new AgentWebAppRuntimeError("INVALID_REQUEST", 401, "Invalid identity token");
|
|
15831
|
+
for (const issuer of issuers) {
|
|
15832
|
+
try {
|
|
15833
|
+
const sub = await verifyWebAppToken(bearer, issuer);
|
|
15834
|
+
return { userId: sub, assurance: "verified" };
|
|
15835
|
+
} catch (error) {
|
|
15836
|
+
if (error instanceof WebAppTokenError) {
|
|
15837
|
+
lastError = error;
|
|
15838
|
+
continue;
|
|
15839
|
+
}
|
|
15840
|
+
throw error;
|
|
15841
|
+
}
|
|
15842
|
+
}
|
|
15843
|
+
const message = lastError instanceof WebAppTokenError ? `Invalid identity token: ${lastError.message}` : "Invalid identity token";
|
|
15844
|
+
throw new AgentWebAppRuntimeError("INVALID_REQUEST", 401, message);
|
|
15845
|
+
}
|
|
15846
|
+
if (policy === "verified") throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "A verified identity token is required");
|
|
15847
|
+
return headerResolver.resolve(request, webAppId);
|
|
15848
|
+
}
|
|
15849
|
+
};
|
|
15850
|
+
}
|
|
15460
15851
|
function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
15461
|
-
const identities = deps.identityResolver ??
|
|
15852
|
+
const identities = deps.identityResolver ?? createWebAppIdentityResolver({ getIdentityConfig: (webAppId) => deps.service.getIdentityConfig(webAppId) });
|
|
15462
15853
|
app2.register(async (routes) => {
|
|
15463
15854
|
routes.setErrorHandler((error, _request, reply) => normalizeError2(reply, error));
|
|
15464
15855
|
routes.get("/api/web-apps/:webAppId/runtime/bootstrap", async (request, reply) => handle2(reply, async () => {
|
|
15465
15856
|
const { webAppId } = paramsSchema.parse(request.params);
|
|
15466
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
15467
|
-
return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview)) };
|
|
15857
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15858
|
+
return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview), { assurance: identity.assurance ?? "unverified" }) };
|
|
15468
15859
|
}));
|
|
15469
15860
|
routes.get("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
|
|
15470
15861
|
const { webAppId } = paramsSchema.parse(request.params);
|
|
15471
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
15862
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15472
15863
|
const records = await deps.service.listThreads(webAppId, identity.userId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
15473
15864
|
return { data: { records, total: records.length } };
|
|
15474
15865
|
}));
|
|
15475
15866
|
routes.post("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
|
|
15476
15867
|
const { webAppId } = paramsSchema.parse(request.params);
|
|
15477
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
15868
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15478
15869
|
return { statusCode: 201, data: await deps.service.createThread(webAppId, identity.userId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
|
|
15479
15870
|
}));
|
|
15480
15871
|
routes.patch("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
|
|
15481
15872
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
15482
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
15873
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15483
15874
|
return { data: await deps.service.updateThread(webAppId, identity.userId, threadId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
|
|
15484
15875
|
}));
|
|
15485
15876
|
routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
|
|
15486
15877
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
15487
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
15878
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15488
15879
|
await deps.service.deleteThread(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
15489
15880
|
return { data: { id: threadId } };
|
|
15490
15881
|
}));
|
|
15491
15882
|
routes.get("/api/web-apps/:webAppId/runtime/threads/:threadId/messages", async (request, reply) => handle2(reply, async () => {
|
|
15492
15883
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
15493
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
15884
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15494
15885
|
const messages = await deps.service.getMessages(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
15495
15886
|
return { data: { messages } };
|
|
15496
15887
|
}));
|
|
15888
|
+
routes.get("/api/web-apps/:webAppId/runtime/state", async (request, reply) => handle2(reply, async () => {
|
|
15889
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
15890
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15891
|
+
const { threadId, projectId, modelKey: modelKey2 } = stateQuerySchema.parse(request.query);
|
|
15892
|
+
const state = await deps.service.getState(webAppId, identity.userId, threadId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
|
|
15893
|
+
return { data: state };
|
|
15894
|
+
}));
|
|
15895
|
+
routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId/pending-messages/:messageId", async (request, reply) => handle2(reply, async () => {
|
|
15896
|
+
const { webAppId, threadId, messageId } = pendingMessageParamsSchema.parse(request.params);
|
|
15897
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15898
|
+
await deps.service.removePendingMessage(webAppId, identity.userId, threadId, messageId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
15899
|
+
return { data: { removed: true } };
|
|
15900
|
+
}));
|
|
15497
15901
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/messages/stream", async (request, reply) => {
|
|
15498
15902
|
try {
|
|
15499
15903
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
15500
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
15904
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15905
|
+
const input = streamBodySchema.parse(request.body);
|
|
15906
|
+
const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
|
|
15907
|
+
beginSse(reply);
|
|
15908
|
+
await pipeNativeWebAppStream(reply.raw, result.stream);
|
|
15909
|
+
} catch (error) {
|
|
15910
|
+
return normalizeError2(reply, error);
|
|
15911
|
+
}
|
|
15912
|
+
});
|
|
15913
|
+
routes.post("/api/web-apps/:webAppId/runtime/v1/threads/:threadId/messages/stream", async (request, reply) => {
|
|
15914
|
+
try {
|
|
15915
|
+
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
15916
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15501
15917
|
const input = streamBodySchema.parse(request.body);
|
|
15502
15918
|
const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
|
|
15503
15919
|
beginSse(reply);
|
|
@@ -15506,15 +15922,27 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
|
15506
15922
|
return normalizeError2(reply, error);
|
|
15507
15923
|
}
|
|
15508
15924
|
});
|
|
15925
|
+
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/resume", async (request, reply) => {
|
|
15926
|
+
try {
|
|
15927
|
+
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
15928
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15929
|
+
const { messageId, projectId, modelKey: modelKey2 } = resumeSchema.parse(request.body);
|
|
15930
|
+
const result = await deps.service.resumeStream(webAppId, identity.userId, threadId, messageId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
|
|
15931
|
+
beginSse(reply);
|
|
15932
|
+
await pipeNativeWebAppStream(reply.raw, result.stream);
|
|
15933
|
+
} catch (error) {
|
|
15934
|
+
return normalizeError2(reply, error);
|
|
15935
|
+
}
|
|
15936
|
+
});
|
|
15509
15937
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/abort", async (request, reply) => handle2(reply, async () => {
|
|
15510
15938
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
15511
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
15939
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15512
15940
|
await deps.service.abort(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
15513
15941
|
return { data: { aborted: true } };
|
|
15514
15942
|
}));
|
|
15515
15943
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/attachments", async (request, reply) => handle2(reply, async () => {
|
|
15516
15944
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
15517
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
15945
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15518
15946
|
if (!request.isMultipart()) throw new import_zod6.ZodError([]);
|
|
15519
15947
|
const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
|
|
15520
15948
|
if (!part || !part.filename || part.filename.length > 255) throw new import_zod6.ZodError([]);
|
|
@@ -15530,7 +15958,7 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
|
15530
15958
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/interrupts/:interruptId/resume", async (request, reply) => {
|
|
15531
15959
|
try {
|
|
15532
15960
|
const { webAppId, threadId, interruptId } = interruptParamsSchema.parse(request.params);
|
|
15533
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
15961
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15534
15962
|
const { projectId, modelKey: modelKey2, message, response } = resumeBodySchema.parse(request.body);
|
|
15535
15963
|
const result = await deps.service.resumeInterrupt(webAppId, identity.userId, threadId, interruptId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview), { message, response });
|
|
15536
15964
|
beginSse(reply);
|
|
@@ -15539,34 +15967,287 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
|
15539
15967
|
return normalizeError2(reply, error);
|
|
15540
15968
|
}
|
|
15541
15969
|
});
|
|
15970
|
+
routes.post("/api/web-apps/:webAppId/dev/token", async (request, reply) => handle2(reply, async () => {
|
|
15971
|
+
if (process.env.WEB_APP_DEV_TOKEN_ENABLED !== "true") {
|
|
15972
|
+
throw new AgentWebAppRuntimeError("FEATURE_DISABLED", 403, "Dev token issuance is disabled (set WEB_APP_DEV_TOKEN_ENABLED=true)");
|
|
15973
|
+
}
|
|
15974
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
15975
|
+
const { iss, sub, hours } = devTokenSchema.parse(request.body ?? {});
|
|
15976
|
+
const config = await deps.service.getIdentityConfig(webAppId);
|
|
15977
|
+
const issuer = (config?.issuers ?? []).find((entry) => entry.alg === "HS256" && (iss === void 0 || entry.iss === iss));
|
|
15978
|
+
if (!issuer || typeof issuer.secret !== "string") {
|
|
15979
|
+
throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "Web app has no HS256 issuer configured");
|
|
15980
|
+
}
|
|
15981
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
15982
|
+
const expiresIn = (hours ?? 24) * 3600;
|
|
15983
|
+
const subject = sub ?? "dev-user";
|
|
15984
|
+
const b64url = (input) => Buffer.from(input).toString("base64url");
|
|
15985
|
+
const body = `${b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }))}.${b64url(JSON.stringify({ sub: subject, iss: issuer.iss, aud: issuer.aud, iat: now, exp: now + expiresIn }))}`;
|
|
15986
|
+
const token = `${body}.${(0, import_node_crypto8.createHmac)("sha256", issuer.secret).update(body).digest("base64url")}`;
|
|
15987
|
+
return { data: { token, tokenType: "Bearer", expiresIn, subject } };
|
|
15988
|
+
}));
|
|
15989
|
+
if (deps.taskController) {
|
|
15990
|
+
const tasks = deps.taskController;
|
|
15991
|
+
const withSubject = (handler) => async (request, reply) => {
|
|
15992
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
15993
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
15994
|
+
const proxy = Object.create(request);
|
|
15995
|
+
proxy.headers = { ...request.headers, "x-user-id": identity.userId };
|
|
15996
|
+
return handler(proxy, reply);
|
|
15997
|
+
};
|
|
15998
|
+
routes.get("/api/web-apps/:webAppId/runtime/tasks", withSubject((request, reply) => tasks.list(request, reply)));
|
|
15999
|
+
routes.post("/api/web-apps/:webAppId/runtime/tasks", withSubject((request, reply) => tasks.create(request, reply)));
|
|
16000
|
+
routes.get("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.get(request, reply)));
|
|
16001
|
+
routes.put("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.update(request, reply)));
|
|
16002
|
+
routes.patch("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.update(request, reply)));
|
|
16003
|
+
routes.delete("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.remove(request, reply)));
|
|
16004
|
+
routes.patch("/api/web-apps/:webAppId/runtime/tasks/:taskId/complete", withSubject((request, reply) => tasks.complete(request, reply)));
|
|
16005
|
+
routes.get("/api/web-apps/:webAppId/runtime/tasks/:taskId/work-items", withSubject((request, reply) => tasks.workItems(request, reply)));
|
|
16006
|
+
}
|
|
16007
|
+
if (deps.workspaceController) {
|
|
16008
|
+
const workspace = deps.workspaceController;
|
|
16009
|
+
const fileScope = async (webAppId, selection) => {
|
|
16010
|
+
const scope = await deps.service.resolveFileScope(webAppId, selection);
|
|
16011
|
+
if (!scope.enabled) throw new AgentWebAppRuntimeError("FEATURE_DISABLED", 403, "Files are disabled for this web app");
|
|
16012
|
+
return scope;
|
|
16013
|
+
};
|
|
16014
|
+
const scopedRequest = (request, scope, params, query) => {
|
|
16015
|
+
const proxy = Object.create(request);
|
|
16016
|
+
proxy.params = params;
|
|
16017
|
+
proxy.query = query;
|
|
16018
|
+
proxy.headers = { ...request.headers, "x-tenant-id": scope.tenantId };
|
|
16019
|
+
return proxy;
|
|
16020
|
+
};
|
|
16021
|
+
const unwrap = (result) => ({ data: result.data });
|
|
16022
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/listpath", async (request) => {
|
|
16023
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
16024
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16025
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
16026
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
16027
|
+
const result = await workspace.listPath(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query));
|
|
16028
|
+
return unwrap(result);
|
|
16029
|
+
});
|
|
16030
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/readfile", async (request) => {
|
|
16031
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
16032
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16033
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
16034
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
16035
|
+
const result = await workspace.readFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query));
|
|
16036
|
+
return unwrap(result);
|
|
16037
|
+
});
|
|
16038
|
+
routes.post("/api/web-apps/:webAppId/runtime/files/uploadfile", async (request, reply) => {
|
|
16039
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
16040
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16041
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
16042
|
+
const scoped = scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, { assistantId: scope.assistantId });
|
|
16043
|
+
await workspace.uploadFile(scoped, reply);
|
|
16044
|
+
return reply;
|
|
16045
|
+
});
|
|
16046
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/viewfile", async (request, reply) => {
|
|
16047
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
16048
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16049
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
16050
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
16051
|
+
await workspace.viewFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
16052
|
+
return reply;
|
|
16053
|
+
});
|
|
16054
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/downloadfile", async (request, reply) => {
|
|
16055
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
16056
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16057
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
16058
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
16059
|
+
await workspace.downloadFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
16060
|
+
return reply;
|
|
16061
|
+
});
|
|
16062
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/downloadfolder", async (request, reply) => {
|
|
16063
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
16064
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16065
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
16066
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
16067
|
+
await workspace.downloadFolder(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
16068
|
+
return reply;
|
|
16069
|
+
});
|
|
16070
|
+
const genuiSandboxParams = import_zod6.z.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema }).strict();
|
|
16071
|
+
const delegateGenuiFile = (method) => async (request, reply) => {
|
|
16072
|
+
const { webAppId } = genuiSandboxParams.parse(request.params);
|
|
16073
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16074
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
16075
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
16076
|
+
await method(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
16077
|
+
return reply;
|
|
16078
|
+
};
|
|
16079
|
+
routes.get("/api/web-apps/:webAppId/runtime/assistants/:assistantId/threads/:threadId/sandbox/downloadfile", delegateGenuiFile(workspace.downloadFile.bind(workspace)));
|
|
16080
|
+
routes.get("/api/web-apps/:webAppId/runtime/assistants/:assistantId/threads/:threadId/sandbox/viewfile", delegateGenuiFile(workspace.viewFile.bind(workspace)));
|
|
16081
|
+
}
|
|
16082
|
+
const mirrorThreadParams = import_zod6.z.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema }).strict();
|
|
16083
|
+
const mirrorBody = import_zod6.z.object({ metadata: import_zod6.z.object({ label: import_zod6.z.string().max(300).optional() }).strict().optional(), projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
|
|
16084
|
+
const toConsoleThread = (thread, assistantId) => ({
|
|
16085
|
+
id: thread.id,
|
|
16086
|
+
assistantId,
|
|
16087
|
+
metadata: thread.label ? { label: thread.label } : {},
|
|
16088
|
+
createdAt: thread.createdAt,
|
|
16089
|
+
updatedAt: thread.updatedAt
|
|
16090
|
+
});
|
|
16091
|
+
const mirrorAssistantParams = import_zod6.z.object({ webAppId: idSchema, assistantId: idSchema }).strict();
|
|
16092
|
+
const mirrorPendingParams = import_zod6.z.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema, messageId: idSchema }).strict();
|
|
16093
|
+
const registerMirror = (prefix) => {
|
|
16094
|
+
routes.get(`${prefix}/assistants/:assistantId/threads`, async (request, reply) => handle2(reply, async () => {
|
|
16095
|
+
const { webAppId, assistantId } = mirrorAssistantParams.parse(request.params);
|
|
16096
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16097
|
+
const threads = await deps.service.listThreads(webAppId, identity.userId, projectQueryLenientSchema.parse(request.query));
|
|
16098
|
+
return { data: { records: threads.map((thread) => toConsoleThread(thread, assistantId)) } };
|
|
16099
|
+
}));
|
|
16100
|
+
routes.post(`${prefix}/assistants/:assistantId/threads`, async (request, reply) => handle2(reply, async () => {
|
|
16101
|
+
const { webAppId, assistantId } = mirrorAssistantParams.parse(request.params);
|
|
16102
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16103
|
+
const body = mirrorBody.parse(request.body ?? {});
|
|
16104
|
+
const created = await deps.service.createThread(webAppId, identity.userId, { label: body.metadata?.label, ...body.projectId ? { projectId: body.projectId } : {} });
|
|
16105
|
+
return { statusCode: 201, data: { id: created.id } };
|
|
16106
|
+
}));
|
|
16107
|
+
routes.get(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
|
|
16108
|
+
const { webAppId, assistantId, threadId } = mirrorThreadParams.parse(request.params);
|
|
16109
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16110
|
+
const thread = await deps.service.getThread(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
16111
|
+
return { data: toConsoleThread(thread, assistantId) };
|
|
16112
|
+
}));
|
|
16113
|
+
routes.put(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
|
|
16114
|
+
const { webAppId, assistantId, threadId } = mirrorThreadParams.parse(request.params);
|
|
16115
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16116
|
+
const body = mirrorBody.parse(request.body ?? {});
|
|
16117
|
+
const updated = await deps.service.updateThread(webAppId, identity.userId, threadId, { label: body.metadata?.label, ...body.projectId ? { projectId: body.projectId } : {} });
|
|
16118
|
+
return { data: toConsoleThread(updated, assistantId) };
|
|
16119
|
+
}));
|
|
16120
|
+
routes.delete(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
|
|
16121
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
16122
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16123
|
+
await deps.service.deleteThread(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
16124
|
+
return { data: { success: true, message: "Thread deleted" } };
|
|
16125
|
+
}));
|
|
16126
|
+
routes.post(`${prefix}/assistants/:assistantId/threads/:threadId/sandbox/uploadfile`, async (request, reply) => handle2(reply, async () => {
|
|
16127
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
16128
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16129
|
+
if (!request.isMultipart()) throw new import_zod6.ZodError([]);
|
|
16130
|
+
const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
|
|
16131
|
+
if (!part || !part.filename || part.filename.length > 255) throw new import_zod6.ZodError([]);
|
|
16132
|
+
const bytes = await part.toBuffer();
|
|
16133
|
+
if (bytes.length === 0 || part.file.truncated) throw new import_zod6.ZodError([]);
|
|
16134
|
+
const uploaded = await deps.service.uploadAttachment(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query), {
|
|
16135
|
+
name: part.filename,
|
|
16136
|
+
mimeType: part.mimetype || "application/octet-stream",
|
|
16137
|
+
bytes
|
|
16138
|
+
});
|
|
16139
|
+
return { statusCode: 201, data: { id: uploaded.ref, path: uploaded.ref, name: uploaded.name, mimeType: uploaded.mimeType, size: uploaded.size } };
|
|
16140
|
+
}));
|
|
16141
|
+
routes.get(`${prefix}/assistants/:assistantId/:threadId/memory`, async (request) => {
|
|
16142
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
16143
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16144
|
+
const messages = await deps.service.getConsoleMessages(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
16145
|
+
return messages;
|
|
16146
|
+
});
|
|
16147
|
+
routes.get(`${prefix}/assistants/:assistantId/:threadId/state`, async (request) => {
|
|
16148
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
16149
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16150
|
+
return deps.service.getState(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
16151
|
+
});
|
|
16152
|
+
routes.delete(`${prefix}/assistants/:assistantId/threads/:threadId/pending-messages/:messageId`, async (request, reply) => handle2(reply, async () => {
|
|
16153
|
+
const { webAppId, threadId, messageId } = mirrorPendingParams.parse(request.params);
|
|
16154
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16155
|
+
await deps.service.removePendingMessage(webAppId, identity.userId, threadId, messageId, projectQueryLenientSchema.parse(request.query));
|
|
16156
|
+
return { data: { removed: true } };
|
|
16157
|
+
}));
|
|
16158
|
+
routes.post(`${prefix}/assistants/:assistantId/threads/:threadId/abort`, async (request, reply) => handle2(reply, async () => {
|
|
16159
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
16160
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16161
|
+
await deps.service.abort(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
16162
|
+
return { data: { aborted: true } };
|
|
16163
|
+
}));
|
|
16164
|
+
const runsBodySchema = import_zod6.z.object({
|
|
16165
|
+
thread_id: idSchema.optional(),
|
|
16166
|
+
assistant_id: idSchema.optional(),
|
|
16167
|
+
messages: import_zod6.z.array(import_zod6.z.object({ content: import_zod6.z.string().min(1).max(32768) }).passthrough()).min(1).max(20),
|
|
16168
|
+
background: import_zod6.z.boolean().optional(),
|
|
16169
|
+
streaming: import_zod6.z.boolean().optional(),
|
|
16170
|
+
projectId: idSchema.optional(),
|
|
16171
|
+
modelKey: idSchema.optional()
|
|
16172
|
+
}).passthrough();
|
|
16173
|
+
routes.post(`${prefix}/runs`, async (request, reply) => {
|
|
16174
|
+
try {
|
|
16175
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
16176
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16177
|
+
const body = runsBodySchema.parse(request.body ?? {});
|
|
16178
|
+
if (!body.background || body.streaming) {
|
|
16179
|
+
throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "Mirror runs supports only background enqueue (stream via resume_stream)");
|
|
16180
|
+
}
|
|
16181
|
+
const threadId = body.thread_id ?? "";
|
|
16182
|
+
if (!threadId) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "thread_id is required");
|
|
16183
|
+
const content = body.messages.map((message) => message.content).join("\n");
|
|
16184
|
+
const { messageId } = await deps.service.enqueueMessage(webAppId, identity.userId, threadId, content, { projectId: body.projectId, modelKey: body.modelKey });
|
|
16185
|
+
return reply.code(202).send({ success: true, messageId, queued: true });
|
|
16186
|
+
} catch (error) {
|
|
16187
|
+
return normalizeError2(reply, error);
|
|
16188
|
+
}
|
|
16189
|
+
});
|
|
16190
|
+
const resumeStreamSchema2 = import_zod6.z.object({
|
|
16191
|
+
assistant_id: idSchema.optional(),
|
|
16192
|
+
thread_id: idSchema,
|
|
16193
|
+
message_id: idSchema,
|
|
16194
|
+
known_content: import_zod6.z.string().optional(),
|
|
16195
|
+
poll_interval: import_zod6.z.number().optional(),
|
|
16196
|
+
projectId: idSchema.optional(),
|
|
16197
|
+
modelKey: idSchema.optional()
|
|
16198
|
+
}).strict();
|
|
16199
|
+
routes.post(`${prefix}/resume_stream`, async (request, reply) => {
|
|
16200
|
+
try {
|
|
16201
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
16202
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
16203
|
+
const body = resumeStreamSchema2.parse(request.body ?? {});
|
|
16204
|
+
const result = await deps.service.resumeStream(webAppId, identity.userId, body.thread_id, body.message_id, { projectId: body.projectId, modelKey: body.modelKey });
|
|
16205
|
+
beginSse(reply);
|
|
16206
|
+
await pipeNativeWebAppStream(reply.raw, result.stream);
|
|
16207
|
+
} catch (error) {
|
|
16208
|
+
return normalizeError2(reply, error);
|
|
16209
|
+
}
|
|
16210
|
+
});
|
|
16211
|
+
};
|
|
16212
|
+
registerMirror("/api/web-apps/:webAppId/runtime");
|
|
16213
|
+
registerMirror("/api/web-apps/:webAppId/runtime/api");
|
|
15542
16214
|
});
|
|
15543
16215
|
}
|
|
15544
|
-
function resolveRuntimeIdentity(request, webAppId, identities) {
|
|
15545
|
-
if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request);
|
|
16216
|
+
async function resolveRuntimeIdentity(request, webAppId, identities) {
|
|
16217
|
+
if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request, webAppId);
|
|
15546
16218
|
const user = request.user;
|
|
15547
16219
|
if (!user?.id || !user.tenantId) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "Authenticated Console preview is required");
|
|
15548
|
-
return { userId: `console-preview:${user.id}:${webAppId}`, preview: { tenantId: user.tenantId, userId: user.id } };
|
|
16220
|
+
return { userId: `console-preview:${user.id}:${webAppId}`, assurance: "unverified", preview: { tenantId: user.tenantId, userId: user.id } };
|
|
15549
16221
|
}
|
|
15550
16222
|
function withPreview(selection, preview) {
|
|
15551
16223
|
return { ...selection, ...preview ? { preview } : {} };
|
|
15552
16224
|
}
|
|
15553
16225
|
async function pipeAgentWebAppStream(raw, stream, options = {}) {
|
|
15554
|
-
const projection = createAgentWebAppStreamProjectionContext(options);
|
|
16226
|
+
const projection = (0, import_protocols12.createAgentWebAppStreamProjectionContext)(options);
|
|
15555
16227
|
await pipeAsyncIterableToSse(raw, stream, {
|
|
15556
|
-
project: (chunk) => projectAgentWebAppChunk(chunk, projection),
|
|
16228
|
+
project: (chunk) => (0, import_protocols12.projectAgentWebAppChunk)(chunk, projection),
|
|
15557
16229
|
onError: () => [
|
|
15558
16230
|
{ type: "error", error: { code: "STREAM_FAILED", message: "Stream failed", retryable: true } },
|
|
15559
16231
|
{ type: "stream.completed" }
|
|
15560
16232
|
]
|
|
15561
16233
|
});
|
|
15562
16234
|
}
|
|
16235
|
+
async function pipeNativeWebAppStream(raw, stream) {
|
|
16236
|
+
await pipeAsyncIterableToSse(raw, stream, {
|
|
16237
|
+
onError: () => [{
|
|
16238
|
+
type: "error",
|
|
16239
|
+
data: { id: "", content: "Stream failed" }
|
|
16240
|
+
}]
|
|
16241
|
+
});
|
|
16242
|
+
}
|
|
15563
16243
|
function beginSse(reply) {
|
|
15564
16244
|
reply.hijack();
|
|
15565
16245
|
reply.raw.writeHead(200, {
|
|
15566
16246
|
"Content-Type": "text/event-stream; charset=utf-8",
|
|
15567
16247
|
"Cache-Control": "no-cache, no-transform",
|
|
15568
16248
|
Connection: "keep-alive",
|
|
15569
|
-
"X-Accel-Buffering": "no"
|
|
16249
|
+
"X-Accel-Buffering": "no",
|
|
16250
|
+
"Access-Control-Allow-Origin": "*"
|
|
15570
16251
|
});
|
|
15571
16252
|
}
|
|
15572
16253
|
async function handle2(reply, operation) {
|
|
@@ -15588,7 +16269,7 @@ function isMultipartRequestError(error) {
|
|
|
15588
16269
|
}
|
|
15589
16270
|
|
|
15590
16271
|
// src/services/agent-web-app/AgentWebAppAttachmentRef.ts
|
|
15591
|
-
var
|
|
16272
|
+
var import_node_crypto9 = require("crypto");
|
|
15592
16273
|
var TTL_MS = 15 * 6e4;
|
|
15593
16274
|
function resolveAgentWebAppAttachmentSecret(consoleSecret) {
|
|
15594
16275
|
return process.env.WEB_APP_ATTACHMENT_SECRET || process.env.AUTH_TOKEN_SECRET || process.env.JWT_SECRET || consoleSecret;
|
|
@@ -15597,7 +16278,7 @@ var AgentWebAppAttachmentRefService = class {
|
|
|
15597
16278
|
constructor(secret, now = Date.now) {
|
|
15598
16279
|
this.now = now;
|
|
15599
16280
|
if (!secret) throw new Error("Attachment signing secret is required");
|
|
15600
|
-
this.key = (0,
|
|
16281
|
+
this.key = (0, import_node_crypto9.createHash)("sha256").update(secret, "utf8").digest();
|
|
15601
16282
|
}
|
|
15602
16283
|
sign(input) {
|
|
15603
16284
|
const payload = {
|
|
@@ -15612,8 +16293,8 @@ var AgentWebAppAttachmentRefService = class {
|
|
|
15612
16293
|
contentDigest: input.contentDigest,
|
|
15613
16294
|
exp: this.now() + TTL_MS
|
|
15614
16295
|
};
|
|
15615
|
-
const iv = (0,
|
|
15616
|
-
const cipher = (0,
|
|
16296
|
+
const iv = (0, import_node_crypto9.randomBytes)(12);
|
|
16297
|
+
const cipher = (0, import_node_crypto9.createCipheriv)("aes-256-gcm", this.key, iv);
|
|
15617
16298
|
const ciphertext = Buffer.concat([
|
|
15618
16299
|
cipher.update(JSON.stringify(payload), "utf8"),
|
|
15619
16300
|
cipher.final()
|
|
@@ -15628,7 +16309,7 @@ var AgentWebAppAttachmentRefService = class {
|
|
|
15628
16309
|
const iv = decodeCanonical(encodedIv, 12);
|
|
15629
16310
|
const ciphertext = decodeCanonical(encodedCiphertext);
|
|
15630
16311
|
const tag = decodeCanonical(encodedTag, 16);
|
|
15631
|
-
const decipher = (0,
|
|
16312
|
+
const decipher = (0, import_node_crypto9.createDecipheriv)("aes-256-gcm", this.key, iv);
|
|
15632
16313
|
decipher.setAuthTag(tag);
|
|
15633
16314
|
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
15634
16315
|
const value = JSON.parse(plaintext.toString("utf8"));
|
|
@@ -15659,7 +16340,7 @@ function decodeCanonical(value, expectedBytes) {
|
|
|
15659
16340
|
return decoded;
|
|
15660
16341
|
}
|
|
15661
16342
|
function digestUser(userId) {
|
|
15662
|
-
return (0,
|
|
16343
|
+
return (0, import_node_crypto9.createHash)("sha256").update(userId, "utf8").digest("hex");
|
|
15663
16344
|
}
|
|
15664
16345
|
function isAttachmentPayload(value) {
|
|
15665
16346
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
@@ -16023,12 +16704,12 @@ function registerCapabilityBundleRoutes(app2, controller) {
|
|
|
16023
16704
|
}
|
|
16024
16705
|
|
|
16025
16706
|
// src/services/collaboration/ProjectRoomService.ts
|
|
16026
|
-
var
|
|
16707
|
+
var import_node_crypto10 = require("crypto");
|
|
16027
16708
|
var ProjectRoomService = class {
|
|
16028
16709
|
/** Creates a project room service from explicitly injected collaborators. */
|
|
16029
16710
|
constructor(deps) {
|
|
16030
16711
|
this.deps = deps;
|
|
16031
|
-
this.idFactory = deps.idFactory ??
|
|
16712
|
+
this.idFactory = deps.idFactory ?? import_node_crypto10.randomUUID;
|
|
16032
16713
|
}
|
|
16033
16714
|
/** Authorizes read access and atomically ensures the project's main room. */
|
|
16034
16715
|
async ensureMainRoom(input) {
|
|
@@ -16047,7 +16728,7 @@ var ProjectRoomService = class {
|
|
|
16047
16728
|
};
|
|
16048
16729
|
|
|
16049
16730
|
// src/services/collaboration/ProjectMembershipService.ts
|
|
16050
|
-
var
|
|
16731
|
+
var import_node_crypto11 = require("crypto");
|
|
16051
16732
|
var ProjectMembershipServiceError = class extends Error {
|
|
16052
16733
|
constructor(code, statusCode, message) {
|
|
16053
16734
|
super(message);
|
|
@@ -16063,11 +16744,13 @@ function isMembershipConflict(error) {
|
|
|
16063
16744
|
var ProjectMembershipService = class {
|
|
16064
16745
|
constructor(deps) {
|
|
16065
16746
|
this.deps = deps;
|
|
16066
|
-
this.idFactory = deps.idFactory ??
|
|
16747
|
+
this.idFactory = deps.idFactory ?? import_node_crypto11.randomUUID;
|
|
16067
16748
|
this.mutationMutex = deps.projectMutationMutex ?? import_core41.projectMutationMutex;
|
|
16068
16749
|
}
|
|
16069
16750
|
async list(actor2) {
|
|
16070
|
-
await this.
|
|
16751
|
+
await this.deps.accessService.requireRosterAccess(actor2);
|
|
16752
|
+
const membership = await this.deps.membershipStore.findByUser(actor2.tenantId, actor2.projectId, actor2.userId);
|
|
16753
|
+
if (!membership || membership.status !== "active") this.accessDenied();
|
|
16071
16754
|
return this.deps.membershipStore.list(actor2.tenantId, actor2.projectId);
|
|
16072
16755
|
}
|
|
16073
16756
|
async add(actor2, input) {
|
|
@@ -16187,7 +16870,7 @@ var ProjectMembershipService = class {
|
|
|
16187
16870
|
};
|
|
16188
16871
|
|
|
16189
16872
|
// src/services/collaboration/ProjectRosterService.ts
|
|
16190
|
-
var
|
|
16873
|
+
var import_node_crypto12 = require("crypto");
|
|
16191
16874
|
var ProjectRosterServiceError = class extends Error {
|
|
16192
16875
|
/** Creates a stable roster service error. */
|
|
16193
16876
|
constructor(code, statusCode, message) {
|
|
@@ -16204,7 +16887,7 @@ var ProjectRosterService = class {
|
|
|
16204
16887
|
/** Creates a roster service from explicitly injected collaborators. */
|
|
16205
16888
|
constructor(deps) {
|
|
16206
16889
|
this.deps = deps;
|
|
16207
|
-
this.idFactory = deps.idFactory ??
|
|
16890
|
+
this.idFactory = deps.idFactory ?? import_node_crypto12.randomUUID;
|
|
16208
16891
|
this.mutationMutex = deps.projectMutationMutex ?? import_core41.projectMutationMutex;
|
|
16209
16892
|
}
|
|
16210
16893
|
/** Lists all bot memberships visible to a project reader. */
|
|
@@ -16424,7 +17107,7 @@ var ProjectRosterService = class {
|
|
|
16424
17107
|
};
|
|
16425
17108
|
|
|
16426
17109
|
// src/services/collaboration/RoomMessageService.ts
|
|
16427
|
-
var
|
|
17110
|
+
var import_node_crypto13 = require("crypto");
|
|
16428
17111
|
|
|
16429
17112
|
// src/services/collaboration/ProjectBotMembershipSnapshot.ts
|
|
16430
17113
|
var import_protocols13 = require("@axiom-lattice/protocols");
|
|
@@ -16513,7 +17196,7 @@ var RoomMessageService = class {
|
|
|
16513
17196
|
constructor(deps, options = {}) {
|
|
16514
17197
|
this.deps = deps;
|
|
16515
17198
|
this.idempotentOutcomes = /* @__PURE__ */ new Map();
|
|
16516
|
-
this.idFactory = deps.idFactory ??
|
|
17199
|
+
this.idFactory = deps.idFactory ?? import_node_crypto13.randomUUID;
|
|
16517
17200
|
const limit = options.idempotentOutcomeLimit ?? 1e3;
|
|
16518
17201
|
if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError("idempotentOutcomeLimit must be a positive safe integer");
|
|
16519
17202
|
this.idempotentOutcomeLimit = limit;
|
|
@@ -16694,7 +17377,7 @@ var RoomMessageService = class {
|
|
|
16694
17377
|
let idempotencyKey;
|
|
16695
17378
|
if (value.requestIdempotencyKey !== void 0) {
|
|
16696
17379
|
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,
|
|
17380
|
+
idempotencyKey = `room-human:${actor2.userId}:${(0, import_node_crypto13.createHash)("sha256").update(value.requestIdempotencyKey.trim(), "utf8").digest("hex")}`;
|
|
16698
17381
|
}
|
|
16699
17382
|
return { text, mentions, botIds, ...value.replyToMessageId === void 0 ? {} : { replyToMessageId: value.replyToMessageId }, ...idempotencyKey === void 0 ? {} : { idempotencyKey } };
|
|
16700
17383
|
}
|
|
@@ -16751,7 +17434,7 @@ function cloneRoomMessage(message) {
|
|
|
16751
17434
|
};
|
|
16752
17435
|
}
|
|
16753
17436
|
function fingerprintRequest(input) {
|
|
16754
|
-
return (0,
|
|
17437
|
+
return (0, import_node_crypto13.createHash)("sha256").update(JSON.stringify({
|
|
16755
17438
|
text: input.text,
|
|
16756
17439
|
mentions: input.mentions,
|
|
16757
17440
|
replyToMessageId: input.replyToMessageId ?? null
|
|
@@ -16920,42 +17603,9 @@ async function execute(reply, operation) {
|
|
|
16920
17603
|
}
|
|
16921
17604
|
|
|
16922
17605
|
// src/routes/cospace.ts
|
|
16923
|
-
var
|
|
17606
|
+
var import_node_crypto14 = require("crypto");
|
|
16924
17607
|
var import_zod12 = require("zod");
|
|
16925
17608
|
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
17609
|
var CoSpaceAuthError = class extends Error {
|
|
16960
17610
|
constructor() {
|
|
16961
17611
|
super(...arguments);
|
|
@@ -16971,7 +17621,7 @@ function getActor2(request) {
|
|
|
16971
17621
|
return { tenantId: user.tenantId, userId: user.id };
|
|
16972
17622
|
}
|
|
16973
17623
|
function registerCoSpaceRoutes(app2, deps) {
|
|
16974
|
-
const idFactory = deps.idFactory ?? (() => (0,
|
|
17624
|
+
const idFactory = deps.idFactory ?? (() => (0, import_node_crypto14.randomUUID)());
|
|
16975
17625
|
app2.get(
|
|
16976
17626
|
"/api/workspaces/:workspaceId/rooms",
|
|
16977
17627
|
async (request, reply) => {
|
|
@@ -16996,6 +17646,7 @@ function registerCoSpaceRoutes(app2, deps) {
|
|
|
16996
17646
|
);
|
|
16997
17647
|
const accessibleProjects = [];
|
|
16998
17648
|
for (const project of allProjects) {
|
|
17649
|
+
if (project.kind === "public") continue;
|
|
16999
17650
|
const membership = await deps.membershipStore.findByUser(
|
|
17000
17651
|
actor2.tenantId,
|
|
17001
17652
|
project.id,
|
|
@@ -17049,6 +17700,34 @@ function registerCoSpaceRoutes(app2, deps) {
|
|
|
17049
17700
|
const bTime = b.lastMessage?.createdAt?.getTime() ?? 0;
|
|
17050
17701
|
return bTime - aTime;
|
|
17051
17702
|
});
|
|
17703
|
+
const publicRoom = await deps.publicRoom.ensure(actor2.tenantId, params.workspaceId);
|
|
17704
|
+
if (publicRoom) {
|
|
17705
|
+
const publicMessages = await deps.messageStore.list({
|
|
17706
|
+
tenantId: actor2.tenantId,
|
|
17707
|
+
roomId: publicRoom.room.id,
|
|
17708
|
+
limit: 1
|
|
17709
|
+
});
|
|
17710
|
+
const publicMemberships = await deps.membershipStore.list(
|
|
17711
|
+
actor2.tenantId,
|
|
17712
|
+
publicRoom.project.id
|
|
17713
|
+
);
|
|
17714
|
+
const publicParticipantNames = [];
|
|
17715
|
+
for (const m of publicMemberships) {
|
|
17716
|
+
if (m.status !== "active") continue;
|
|
17717
|
+
const user = await deps.userStore.getUserById(m.userId);
|
|
17718
|
+
if (user) {
|
|
17719
|
+
publicParticipantNames.push(user.name);
|
|
17720
|
+
}
|
|
17721
|
+
}
|
|
17722
|
+
rooms.unshift({
|
|
17723
|
+
project: publicRoom.project,
|
|
17724
|
+
room: publicRoom.room,
|
|
17725
|
+
lastMessage: publicMessages[0],
|
|
17726
|
+
participantCount: publicMemberships.filter((m) => m.status === "active").length,
|
|
17727
|
+
participantNames: publicParticipantNames,
|
|
17728
|
+
isPublic: true
|
|
17729
|
+
});
|
|
17730
|
+
}
|
|
17052
17731
|
return { data: { rooms } };
|
|
17053
17732
|
});
|
|
17054
17733
|
}
|
|
@@ -17805,7 +18484,7 @@ function exactValues(value, required, optional = []) {
|
|
|
17805
18484
|
}
|
|
17806
18485
|
|
|
17807
18486
|
// src/services/collaboration/CoordinatorWakeNotifier.ts
|
|
17808
|
-
var
|
|
18487
|
+
var import_node_crypto15 = require("crypto");
|
|
17809
18488
|
var import_core45 = require("@axiom-lattice/core");
|
|
17810
18489
|
var import_protocols15 = require("@axiom-lattice/protocols");
|
|
17811
18490
|
var SYNTHETIC_WAKE_AUTHOR = "coordinator-wake";
|
|
@@ -17820,7 +18499,7 @@ var CoordinatorWakeNotifier = class {
|
|
|
17820
18499
|
this.accepted = /* @__PURE__ */ new Set();
|
|
17821
18500
|
this.maxPending = deps.maxPending ?? 1024;
|
|
17822
18501
|
if (!Number.isSafeInteger(this.maxPending) || this.maxPending < 1) throw new RangeError("maxPending must be a positive safe integer");
|
|
17823
|
-
this.idFactory = deps.idFactory ??
|
|
18502
|
+
this.idFactory = deps.idFactory ?? import_node_crypto15.randomUUID;
|
|
17824
18503
|
}
|
|
17825
18504
|
/** Admits one notification without awaiting any store or dispatch operation. */
|
|
17826
18505
|
handle(value) {
|
|
@@ -18683,7 +19362,7 @@ function validRecoveryRow(row, task, thread) {
|
|
|
18683
19362
|
}
|
|
18684
19363
|
|
|
18685
19364
|
// src/services/collaboration/ProjectTaskObserver.ts
|
|
18686
|
-
var
|
|
19365
|
+
var import_node_crypto16 = require("crypto");
|
|
18687
19366
|
var import_core47 = require("@axiom-lattice/core");
|
|
18688
19367
|
var SYSTEM_ACTOR2 = "system";
|
|
18689
19368
|
var TERMINAL_ACTIONS = ["completed", "failed", "interrupted", "cancelled"];
|
|
@@ -19220,7 +19899,7 @@ var ProjectTaskObserver = class {
|
|
|
19220
19899
|
action: "activity",
|
|
19221
19900
|
summary: "Project task recovery warning",
|
|
19222
19901
|
detail: { type: "project_task_recovery_warning", phase, message },
|
|
19223
|
-
eventKey: `project-task-recovery-warning:${task.id}:${phase}:${(0,
|
|
19902
|
+
eventKey: `project-task-recovery-warning:${task.id}:${phase}:${(0, import_node_crypto16.createHash)("sha256").update(message).digest("hex")}`
|
|
19224
19903
|
});
|
|
19225
19904
|
}
|
|
19226
19905
|
async safeWarning(task, phase, error) {
|
|
@@ -19287,7 +19966,7 @@ var ProjectTaskObserver = class {
|
|
|
19287
19966
|
}
|
|
19288
19967
|
}
|
|
19289
19968
|
async persistOverflowWarning(event) {
|
|
19290
|
-
const scopeDigest = (0,
|
|
19969
|
+
const scopeDigest = (0, import_node_crypto16.createHash)("sha256").update(JSON.stringify([event.tenantId, event.workspaceId, event.projectId])).digest("hex");
|
|
19291
19970
|
try {
|
|
19292
19971
|
await this.deps.workItems.createIfAbsentByEventKey({
|
|
19293
19972
|
tenantId: event.tenantId,
|
|
@@ -19388,7 +20067,7 @@ function combineSignals(left, right) {
|
|
|
19388
20067
|
}
|
|
19389
20068
|
|
|
19390
20069
|
// src/services/collaboration/RoomProjectionService.ts
|
|
19391
|
-
var
|
|
20070
|
+
var import_node_crypto17 = require("crypto");
|
|
19392
20071
|
var import_core48 = require("@axiom-lattice/core");
|
|
19393
20072
|
var import_protocols18 = require("@axiom-lattice/protocols");
|
|
19394
20073
|
var RoomProjectionService = class {
|
|
@@ -19557,7 +20236,7 @@ function safeRepairIdentity(value, scope) {
|
|
|
19557
20236
|
return taskId && eventKey && tenantId === scope.tenantId && workspaceId === scope.workspaceId && projectId === scope.projectId ? { taskId, eventKey } : void 0;
|
|
19558
20237
|
}
|
|
19559
20238
|
function tupleKey(prefix, values) {
|
|
19560
|
-
const digest = (0,
|
|
20239
|
+
const digest = (0, import_node_crypto17.createHash)("sha256").update(JSON.stringify(values)).digest("hex");
|
|
19561
20240
|
return `${prefix}:${digest}`;
|
|
19562
20241
|
}
|
|
19563
20242
|
function parseProjectionInput(taskValue, itemValue) {
|
|
@@ -19585,7 +20264,7 @@ function snapshotProjectableWorkItem(value) {
|
|
|
19585
20264
|
if (!row || !createdAt || typeof row.id !== "string" || !row.id || typeof row.taskId !== "string" || !row.taskId || typeof row.tenantId !== "string" || !row.tenantId || typeof row.workspaceId !== "string" || !row.workspaceId || typeof row.projectId !== "string" || !row.projectId || typeof row.actor !== "string" || !row.actor || typeof row.eventKey !== "string" || !row.eventKey || !import_protocols18.PROJECT_TASK_LIFECYCLE_ACTIONS.includes(row.action) || row.threadId !== void 0 && typeof row.threadId !== "string" || row.summary !== void 0 && typeof row.summary !== "string" || row.attempt !== void 0 && (typeof row.attempt !== "number" || !Number.isFinite(row.attempt))) return void 0;
|
|
19586
20265
|
try {
|
|
19587
20266
|
const detail = row.detail === void 0 ? void 0 : snapshotJsonValue(row.detail);
|
|
19588
|
-
if (detail !== void 0 && !
|
|
20267
|
+
if (detail !== void 0 && !isRecord3(detail)) return void 0;
|
|
19589
20268
|
return {
|
|
19590
20269
|
id: row.id,
|
|
19591
20270
|
taskId: row.taskId,
|
|
@@ -19732,7 +20411,7 @@ function snapshotLifecycleCursor(value) {
|
|
|
19732
20411
|
const createdAt = cloneExactDate(row?.createdAt);
|
|
19733
20412
|
return row && typeof row.id === "string" && row.id.length > 0 && createdAt ? { id: row.id, createdAt } : void 0;
|
|
19734
20413
|
}
|
|
19735
|
-
function
|
|
20414
|
+
function isRecord3(value) {
|
|
19736
20415
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19737
20416
|
}
|
|
19738
20417
|
|
|
@@ -19740,7 +20419,7 @@ function isRecord4(value) {
|
|
|
19740
20419
|
var import_core50 = require("@axiom-lattice/core");
|
|
19741
20420
|
|
|
19742
20421
|
// src/services/collaboration/ProjectRoomEventBroker.ts
|
|
19743
|
-
var
|
|
20422
|
+
var import_node_crypto18 = require("crypto");
|
|
19744
20423
|
var import_protocols19 = require("@axiom-lattice/protocols");
|
|
19745
20424
|
var DEFAULT_MAX_EVENTS_PER_ROOM = 1e3;
|
|
19746
20425
|
var DEFAULT_MAX_EVENT_AGE_MS = 10 * 60 * 1e3;
|
|
@@ -19765,7 +20444,7 @@ var ProjectRoomEventBroker = class {
|
|
|
19765
20444
|
this.subscriberCount = 0;
|
|
19766
20445
|
this.usageClock = 0;
|
|
19767
20446
|
this.closed = false;
|
|
19768
|
-
this.epoch = options.epoch ?? (0,
|
|
20447
|
+
this.epoch = options.epoch ?? (0, import_node_crypto18.randomUUID)();
|
|
19769
20448
|
if (!UUID_PATTERN.test(this.epoch)) {
|
|
19770
20449
|
throw new TypeError("Project Room event epoch must be a lowercase canonical UUID");
|
|
19771
20450
|
}
|
|
@@ -20083,7 +20762,7 @@ function snapshotJson(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
|
20083
20762
|
}
|
|
20084
20763
|
|
|
20085
20764
|
// src/services/collaboration/ProjectRoomRealtimePublisher.ts
|
|
20086
|
-
var
|
|
20765
|
+
var import_node_crypto19 = require("crypto");
|
|
20087
20766
|
var import_protocols20 = require("@axiom-lattice/protocols");
|
|
20088
20767
|
var BrokerProjectRoomRealtimePublisher = class {
|
|
20089
20768
|
/** Creates a publisher around a broker and structured warning logger. */
|
|
@@ -20159,7 +20838,7 @@ var BrokerProjectRoomRealtimePublisher = class {
|
|
|
20159
20838
|
try {
|
|
20160
20839
|
event = build();
|
|
20161
20840
|
if (!event) throw new Error("Invalid Project Room realtime projection");
|
|
20162
|
-
const dedupeKey = (0,
|
|
20841
|
+
const dedupeKey = (0, import_node_crypto19.createHash)("sha256").update(JSON.stringify([type, event.scope.tenantId, event.scope.projectId, event.scope.roomId, event.sourceKey])).digest("hex");
|
|
20163
20842
|
if (this.sourceKeys.has(dedupeKey)) return;
|
|
20164
20843
|
this.deps.broker.publish(event.scope, event.draft);
|
|
20165
20844
|
this.sourceKeys.set(dedupeKey, true);
|
|
@@ -21098,12 +21777,24 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
21098
21777
|
});
|
|
21099
21778
|
}
|
|
21100
21779
|
if (projectTaskComposition) app2.addHook("onClose", async () => projectTaskComposition.lease.release());
|
|
21101
|
-
const taskThreadService = projectTaskComposition?.lease.runtime.threads ?? new ProjectTaskThreadService({ threads: threadStore, idFactory:
|
|
21780
|
+
const taskThreadService = projectTaskComposition?.lease.runtime.threads ?? new ProjectTaskThreadService({ threads: threadStore, idFactory: import_node_crypto20.randomUUID });
|
|
21102
21781
|
const taskLifecycle = projectTaskComposition?.lease.runtime.lifecycle ?? (0, import_core51.createTaskLifecycleService)({ taskStore, workItemStore: taskWorkItemStore });
|
|
21103
21782
|
const reassignmentAuthorizer = (0, import_core51.createProjectTaskReassignmentAuthorizer)(
|
|
21104
21783
|
new import_core51.ProjectTaskOwnerPolicy(projectBotMembershipStore),
|
|
21105
21784
|
(input) => validateReassignmentThread(threadStore, input)
|
|
21106
21785
|
);
|
|
21786
|
+
const coordinatorProvisioner = new RoomCoordinatorProvisioner({
|
|
21787
|
+
getAssistantById: assistantStore.getAssistantById.bind(assistantStore),
|
|
21788
|
+
createAssistant: assistantStore.createAssistant.bind(assistantStore)
|
|
21789
|
+
});
|
|
21790
|
+
const publicRoomService = new WorkspacePublicRoomService({
|
|
21791
|
+
getProjectById: projectStore.getProjectById.bind(projectStore),
|
|
21792
|
+
createProject: projectStore.createProject.bind(projectStore),
|
|
21793
|
+
ensureMainRoom: projectRoomStore.ensureMainRoom.bind(projectRoomStore),
|
|
21794
|
+
findBotByAssistant: projectBotMembershipStore.findByAssistant.bind(projectBotMembershipStore),
|
|
21795
|
+
saveBot: projectBotMembershipStore.save.bind(projectBotMembershipStore),
|
|
21796
|
+
ensureCoordinator: (tenantId) => coordinatorProvisioner.ensure(tenantId)
|
|
21797
|
+
});
|
|
21107
21798
|
registerProjectRoomRoutes(app2, { roomService, membershipService, messageService });
|
|
21108
21799
|
registerProjectRosterRoutes(app2, { service: rosterService });
|
|
21109
21800
|
registerCoSpaceRoutes(app2, {
|
|
@@ -21142,14 +21833,14 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
21142
21833
|
findByAssistant: projectBotMembershipStore.findByAssistant.bind(projectBotMembershipStore),
|
|
21143
21834
|
save: projectBotMembershipStore.save.bind(projectBotMembershipStore)
|
|
21144
21835
|
},
|
|
21145
|
-
coordinatorProvisioner
|
|
21146
|
-
getAssistantById: assistantStore.getAssistantById.bind(assistantStore),
|
|
21147
|
-
createAssistant: assistantStore.createAssistant.bind(assistantStore)
|
|
21148
|
-
}),
|
|
21836
|
+
coordinatorProvisioner,
|
|
21149
21837
|
rosterService: {
|
|
21150
21838
|
addBot: async (actor2, input) => {
|
|
21151
21839
|
await rosterService.add(actor2, input);
|
|
21152
21840
|
}
|
|
21841
|
+
},
|
|
21842
|
+
publicRoom: {
|
|
21843
|
+
ensure: (tenantId, workspaceId) => publicRoomService.ensure(tenantId, workspaceId)
|
|
21153
21844
|
}
|
|
21154
21845
|
});
|
|
21155
21846
|
registerCapabilityBundleRoutes(app2, new CapabilityBundleController({
|
|
@@ -21169,6 +21860,46 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
21169
21860
|
})
|
|
21170
21861
|
});
|
|
21171
21862
|
registerAgentWebAppRuntimeRoutes(app2, {
|
|
21863
|
+
workspaceController: new WorkspaceController(createProjectCollaborationInitializer({
|
|
21864
|
+
membershipStore: projectMembershipStore,
|
|
21865
|
+
roomStore: projectRoomStore,
|
|
21866
|
+
userTenantLinkStore
|
|
21867
|
+
})),
|
|
21868
|
+
taskController: createTaskController({
|
|
21869
|
+
access: {
|
|
21870
|
+
requireProjectAccess: async (input) => {
|
|
21871
|
+
const apps = await webAppStore.list(input.tenantId);
|
|
21872
|
+
const allowed = apps.some((app3) => app3.status === "active" && app3.scope.allowedProjectIds.includes(input.projectId));
|
|
21873
|
+
if (!allowed) throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project is not exposed through this web app");
|
|
21874
|
+
const project = await projectStore.getProjectById(input.tenantId, input.projectId);
|
|
21875
|
+
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
21876
|
+
return {
|
|
21877
|
+
project,
|
|
21878
|
+
membership: {
|
|
21879
|
+
id: `webapp_guest_${input.projectId}_${input.userId}`,
|
|
21880
|
+
tenantId: input.tenantId,
|
|
21881
|
+
projectId: input.projectId,
|
|
21882
|
+
userId: input.userId,
|
|
21883
|
+
role: "member",
|
|
21884
|
+
status: "active",
|
|
21885
|
+
joinedAt: /* @__PURE__ */ new Date(),
|
|
21886
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
21887
|
+
}
|
|
21888
|
+
};
|
|
21889
|
+
}
|
|
21890
|
+
},
|
|
21891
|
+
tasks: taskStore,
|
|
21892
|
+
workItems: taskWorkItemStore,
|
|
21893
|
+
bots: projectBotMembershipStore,
|
|
21894
|
+
rooms: projectRoomStore,
|
|
21895
|
+
threadService: taskThreadService,
|
|
21896
|
+
reassignmentAuthorizer,
|
|
21897
|
+
lifecycle: taskLifecycle,
|
|
21898
|
+
projectMutationMutex: import_core51.projectMutationMutex,
|
|
21899
|
+
executionMutex: projectTaskComposition?.mutex ?? {
|
|
21900
|
+
runExclusive: (_scope, operation) => operation()
|
|
21901
|
+
}
|
|
21902
|
+
}),
|
|
21172
21903
|
service: new AgentWebAppRuntimeService({
|
|
21173
21904
|
webAppStore,
|
|
21174
21905
|
threadStore: (0, import_core51.getStoreLattice)("default", "thread").store,
|
|
@@ -21176,7 +21907,7 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
21176
21907
|
assistantResolver: {
|
|
21177
21908
|
resolve: async (tenantId, assistantId) => {
|
|
21178
21909
|
const stored = await assistantStore.getAssistantById(tenantId, assistantId);
|
|
21179
|
-
if (stored) return stored;
|
|
21910
|
+
if (stored) return { id: stored.id, name: stored.name, description: stored.description };
|
|
21180
21911
|
await import_core51.agentLatticeManager.initializeStoredAssistantsForTenant(tenantId);
|
|
21181
21912
|
const config = import_core51.agentLatticeManager.getAgentConfigWithTenant(tenantId, assistantId);
|
|
21182
21913
|
return config ? { id: assistantId, name: config.name, description: config.description } : null;
|
|
@@ -21590,7 +22321,7 @@ function tryCreateProjectTaskRuntime(input) {
|
|
|
21590
22321
|
function createRuntime(input, stores, log2) {
|
|
21591
22322
|
const controller = new AbortController();
|
|
21592
22323
|
const mutex = new ProjectTaskExecutionMutex();
|
|
21593
|
-
const threads = new ProjectTaskThreadService({ threads: input.threadStore, idFactory:
|
|
22324
|
+
const threads = new ProjectTaskThreadService({ threads: input.threadStore, idFactory: import_node_crypto20.randomUUID });
|
|
21594
22325
|
const lifecycle = (0, import_core51.createTaskLifecycleService)({ taskStore: input.taskStore, workItemStore: input.taskWorkItemStore });
|
|
21595
22326
|
const projection = new RoomProjectionService({
|
|
21596
22327
|
messages: input.projectRoomMessageStore,
|
|
@@ -21599,7 +22330,7 @@ function createRuntime(input, stores, log2) {
|
|
|
21599
22330
|
warnings: input.taskWorkItemStore,
|
|
21600
22331
|
log: log2,
|
|
21601
22332
|
realtime: input.realtime,
|
|
21602
|
-
idFactory:
|
|
22333
|
+
idFactory: import_node_crypto20.randomUUID
|
|
21603
22334
|
});
|
|
21604
22335
|
const agentFor = (task, thread) => import_core51.agentInstanceManager.getAgent({
|
|
21605
22336
|
tenant_id: task.tenantId,
|