@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.mjs
CHANGED
|
@@ -32,7 +32,7 @@ import staticPlugin from "@fastify/static";
|
|
|
32
32
|
import path3 from "path";
|
|
33
33
|
|
|
34
34
|
// src/routes/index.ts
|
|
35
|
-
import { randomUUID as
|
|
35
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
36
36
|
import {
|
|
37
37
|
descriptorDataValue as descriptorDataValue3,
|
|
38
38
|
snapshotExactRecord as snapshotExactRecord13,
|
|
@@ -5221,6 +5221,113 @@ import { getStoreLattice as getStoreLattice6 } from "@axiom-lattice/core";
|
|
|
5221
5221
|
import { SandboxFilesystem, FilesystemBackend } from "@axiom-lattice/core";
|
|
5222
5222
|
import { getSandBoxManager as getSandBoxManager3 } from "@axiom-lattice/core";
|
|
5223
5223
|
import { v4 as uuidv42 } from "uuid";
|
|
5224
|
+
|
|
5225
|
+
// src/services/collaboration/RoomCoordinatorProvisioner.ts
|
|
5226
|
+
var ROOM_COORDINATOR_KEY = "room-coordinator";
|
|
5227
|
+
var RoomCoordinatorProvisioner = class {
|
|
5228
|
+
/** Creates a provisioner over the tenant-scoped assistant store operations. */
|
|
5229
|
+
constructor(deps) {
|
|
5230
|
+
this.deps = deps;
|
|
5231
|
+
}
|
|
5232
|
+
/**
|
|
5233
|
+
* Ensures the Room Coordinator assistant exists, reusing an existing record
|
|
5234
|
+
* and reconciling concurrent create races by rereading the winner.
|
|
5235
|
+
*
|
|
5236
|
+
* @param tenantId Tenant to provision the coordinator for.
|
|
5237
|
+
* @returns The persisted Room Coordinator assistant.
|
|
5238
|
+
*/
|
|
5239
|
+
async ensure(tenantId) {
|
|
5240
|
+
const existing = await this.deps.getAssistantById(tenantId, ROOM_COORDINATOR_KEY);
|
|
5241
|
+
if (existing) return existing;
|
|
5242
|
+
try {
|
|
5243
|
+
return await this.deps.createAssistant(tenantId, ROOM_COORDINATOR_KEY, {
|
|
5244
|
+
name: "Room Coordinator",
|
|
5245
|
+
description: "Coordinates project room conversations and organizes room work.",
|
|
5246
|
+
graphDefinition: {}
|
|
5247
|
+
});
|
|
5248
|
+
} catch (error) {
|
|
5249
|
+
const winner = await this.deps.getAssistantById(tenantId, ROOM_COORDINATOR_KEY);
|
|
5250
|
+
if (winner) return winner;
|
|
5251
|
+
throw error;
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
};
|
|
5255
|
+
|
|
5256
|
+
// src/services/collaboration/WorkspacePublicRoomService.ts
|
|
5257
|
+
import { createHash } from "crypto";
|
|
5258
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
5259
|
+
var WORKSPACE_PUBLIC_ROOM_PROJECT_PREFIX = "workspace_public_";
|
|
5260
|
+
var PUBLIC_ROOM_PROJECT_NAME = "General";
|
|
5261
|
+
var PUBLIC_ROOM_PROJECT_DESCRIPTION = "Public room for everyone in the workspace";
|
|
5262
|
+
function workspacePublicRoomProjectId(tenantId, workspaceId) {
|
|
5263
|
+
const hash = createHash("sha256").update([tenantId, workspaceId].join(":")).digest("hex");
|
|
5264
|
+
return `${WORKSPACE_PUBLIC_ROOM_PROJECT_PREFIX}${hash}`;
|
|
5265
|
+
}
|
|
5266
|
+
var WorkspacePublicRoomService = class {
|
|
5267
|
+
/** Creates the service from explicitly injected stores and provisioners. */
|
|
5268
|
+
constructor(deps) {
|
|
5269
|
+
this.deps = deps;
|
|
5270
|
+
this.idFactory = deps.idFactory ?? randomUUID5;
|
|
5271
|
+
}
|
|
5272
|
+
/**
|
|
5273
|
+
* Returns the workspace's public room project and room, creating them on first call.
|
|
5274
|
+
*
|
|
5275
|
+
* @param tenantId Tenant owning the workspace.
|
|
5276
|
+
* @param workspaceId Workspace hosting the public room.
|
|
5277
|
+
* @returns The ensured project and room.
|
|
5278
|
+
*/
|
|
5279
|
+
async ensure(tenantId, workspaceId) {
|
|
5280
|
+
const projectId = workspacePublicRoomProjectId(tenantId, workspaceId);
|
|
5281
|
+
const project = await this.ensureProject(tenantId, workspaceId, projectId);
|
|
5282
|
+
const room = await this.deps.ensureMainRoom({
|
|
5283
|
+
id: this.idFactory(),
|
|
5284
|
+
tenantId,
|
|
5285
|
+
workspaceId,
|
|
5286
|
+
projectId,
|
|
5287
|
+
name: project.name
|
|
5288
|
+
});
|
|
5289
|
+
await this.ensureCoordinatorBot(tenantId, workspaceId, projectId, room.id);
|
|
5290
|
+
return { project, room };
|
|
5291
|
+
}
|
|
5292
|
+
async ensureProject(tenantId, workspaceId, projectId) {
|
|
5293
|
+
const existing = await this.deps.getProjectById(tenantId, projectId);
|
|
5294
|
+
if (existing && existing.kind === "public") return existing;
|
|
5295
|
+
try {
|
|
5296
|
+
return await this.deps.createProject(tenantId, workspaceId, projectId, {
|
|
5297
|
+
name: PUBLIC_ROOM_PROJECT_NAME,
|
|
5298
|
+
description: PUBLIC_ROOM_PROJECT_DESCRIPTION,
|
|
5299
|
+
kind: "public"
|
|
5300
|
+
});
|
|
5301
|
+
} catch (error) {
|
|
5302
|
+
const winner = await this.deps.getProjectById(tenantId, projectId);
|
|
5303
|
+
if (winner && winner.kind === "public") return winner;
|
|
5304
|
+
throw Object.assign(new Error("Workspace public room project could not be ensured"), { cause: error });
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
async ensureCoordinatorBot(tenantId, workspaceId, projectId, roomId) {
|
|
5308
|
+
const coordinator = await this.deps.ensureCoordinator(tenantId);
|
|
5309
|
+
const existing = await this.deps.findBotByAssistant(tenantId, projectId, coordinator.id);
|
|
5310
|
+
if (existing && existing.status !== "removed") return;
|
|
5311
|
+
const saved = await this.deps.saveBot({
|
|
5312
|
+
id: this.idFactory(),
|
|
5313
|
+
tenantId,
|
|
5314
|
+
workspaceId,
|
|
5315
|
+
projectId,
|
|
5316
|
+
roomId,
|
|
5317
|
+
assistantId: coordinator.id,
|
|
5318
|
+
role: "coordinator",
|
|
5319
|
+
title: coordinator.name,
|
|
5320
|
+
mentionName: ROOM_COORDINATOR_KEY,
|
|
5321
|
+
status: "active",
|
|
5322
|
+
roomThreadId: this.idFactory()
|
|
5323
|
+
});
|
|
5324
|
+
if (saved.kind === "coordinator_conflict" || saved.kind === "mention_conflict") {
|
|
5325
|
+
throw new Error(`Workspace public room coordinator bot could not be ensured: ${saved.kind}`);
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
};
|
|
5329
|
+
|
|
5330
|
+
// src/controllers/workspace.ts
|
|
5224
5331
|
var MAX_ZIP_ENTRIES = 1e4;
|
|
5225
5332
|
var MAX_ZIP_FILE_SIZE = 100 * 1024 * 1024;
|
|
5226
5333
|
var MAX_ZIP_TOTAL_SIZE = 512 * 1024 * 1024;
|
|
@@ -5355,6 +5462,21 @@ var WorkspaceController = class {
|
|
|
5355
5462
|
this.projectCollaboration = projectCollaboration;
|
|
5356
5463
|
this.workspaceStore = getStoreLattice6("default", "workspace").store;
|
|
5357
5464
|
this.projectStore = getStoreLattice6("default", "project").store;
|
|
5465
|
+
const roomStore = getStoreLattice6("default", "projectRoom").store;
|
|
5466
|
+
const botMembershipStore = getStoreLattice6("default", "projectBotMembership").store;
|
|
5467
|
+
const assistantStore = getStoreLattice6("default", "assistant").store;
|
|
5468
|
+
const coordinatorProvisioner = new RoomCoordinatorProvisioner({
|
|
5469
|
+
getAssistantById: assistantStore.getAssistantById.bind(assistantStore),
|
|
5470
|
+
createAssistant: assistantStore.createAssistant.bind(assistantStore)
|
|
5471
|
+
});
|
|
5472
|
+
this.publicRoom = new WorkspacePublicRoomService({
|
|
5473
|
+
getProjectById: this.projectStore.getProjectById.bind(this.projectStore),
|
|
5474
|
+
createProject: this.projectStore.createProject.bind(this.projectStore),
|
|
5475
|
+
ensureMainRoom: roomStore.ensureMainRoom.bind(roomStore),
|
|
5476
|
+
findBotByAssistant: botMembershipStore.findByAssistant.bind(botMembershipStore),
|
|
5477
|
+
saveBot: botMembershipStore.save.bind(botMembershipStore),
|
|
5478
|
+
ensureCoordinator: (tenantId) => coordinatorProvisioner.ensure(tenantId)
|
|
5479
|
+
});
|
|
5358
5480
|
}
|
|
5359
5481
|
getTenantId(request) {
|
|
5360
5482
|
const userTenantId = request.user?.tenantId;
|
|
@@ -5428,6 +5550,7 @@ var WorkspaceController = class {
|
|
|
5428
5550
|
const { workspaceId } = request.params;
|
|
5429
5551
|
const kindFilter = request.query.kind;
|
|
5430
5552
|
await this.ensureTrainingProject(tenantId, workspaceId);
|
|
5553
|
+
await this.publicRoom.ensure(tenantId, workspaceId);
|
|
5431
5554
|
const projects = kindFilter ? await this.projectStore.getProjectsByWorkspace(tenantId, workspaceId, {
|
|
5432
5555
|
kind: kindFilter
|
|
5433
5556
|
}) : await this.projectStore.getProjectsByWorkspace(tenantId, workspaceId);
|
|
@@ -5471,6 +5594,12 @@ var WorkspaceController = class {
|
|
|
5471
5594
|
message: CAPABILITY_BUNDLE_CONFIG_MESSAGE
|
|
5472
5595
|
});
|
|
5473
5596
|
}
|
|
5597
|
+
if (data.kind === "public") {
|
|
5598
|
+
return reply.status(409).send({
|
|
5599
|
+
success: false,
|
|
5600
|
+
error: "The public room project is managed automatically"
|
|
5601
|
+
});
|
|
5602
|
+
}
|
|
5474
5603
|
if (user?.id && this.projectCollaboration) {
|
|
5475
5604
|
try {
|
|
5476
5605
|
const authorized = await this.projectCollaboration.authorizeCreator({ tenantId, userId: user.id });
|
|
@@ -5546,6 +5675,21 @@ var WorkspaceController = class {
|
|
|
5546
5675
|
message: CAPABILITY_BUNDLE_CONFIG_MESSAGE
|
|
5547
5676
|
});
|
|
5548
5677
|
}
|
|
5678
|
+
if (updates.kind !== void 0) {
|
|
5679
|
+
const existingProject = await this.projectStore.getProjectById(tenantId, projectId);
|
|
5680
|
+
if (existingProject && updates.kind === "public") {
|
|
5681
|
+
return reply.status(409).send({
|
|
5682
|
+
success: false,
|
|
5683
|
+
error: "kind public is reserved for the workspace public room"
|
|
5684
|
+
});
|
|
5685
|
+
}
|
|
5686
|
+
if (existingProject?.kind === "public") {
|
|
5687
|
+
return reply.status(409).send({
|
|
5688
|
+
success: false,
|
|
5689
|
+
error: "The public room project classification cannot be changed"
|
|
5690
|
+
});
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5549
5693
|
const project = await this.projectStore.updateProject(
|
|
5550
5694
|
tenantId,
|
|
5551
5695
|
projectId,
|
|
@@ -5576,6 +5720,12 @@ var WorkspaceController = class {
|
|
|
5576
5720
|
});
|
|
5577
5721
|
}
|
|
5578
5722
|
}
|
|
5723
|
+
if (project.kind === "public") {
|
|
5724
|
+
return reply.status(409).send({
|
|
5725
|
+
success: false,
|
|
5726
|
+
error: "The public room project cannot be deleted"
|
|
5727
|
+
});
|
|
5728
|
+
}
|
|
5579
5729
|
const deleted = await this.projectStore.deleteProject(tenantId, projectId);
|
|
5580
5730
|
if (!deleted) {
|
|
5581
5731
|
return reply.status(404).send({ success: false, error: "Project not found" });
|
|
@@ -6274,14 +6424,202 @@ function registerWorkspaceRoutes(app2, projectCollaboration) {
|
|
|
6274
6424
|
"/api/filesystem/browse",
|
|
6275
6425
|
controller.browseFilesystem.bind(controller)
|
|
6276
6426
|
);
|
|
6427
|
+
return controller;
|
|
6428
|
+
}
|
|
6429
|
+
|
|
6430
|
+
// src/services/collaboration/ProjectAccessService.ts
|
|
6431
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
6432
|
+
var ProjectCollaborationError = class extends Error {
|
|
6433
|
+
/** Creates a project collaboration error. */
|
|
6434
|
+
constructor(code, statusCode, message) {
|
|
6435
|
+
super(message);
|
|
6436
|
+
this.code = code;
|
|
6437
|
+
this.statusCode = statusCode;
|
|
6438
|
+
this.name = "ProjectCollaborationError";
|
|
6439
|
+
}
|
|
6440
|
+
};
|
|
6441
|
+
var allowedRoles = {
|
|
6442
|
+
read: /* @__PURE__ */ new Set(["owner", "admin", "member", "viewer"]),
|
|
6443
|
+
send: /* @__PURE__ */ new Set(["owner", "admin", "member"]),
|
|
6444
|
+
manage: /* @__PURE__ */ new Set(["owner", "admin"])
|
|
6445
|
+
};
|
|
6446
|
+
var membershipConflictNames = /* @__PURE__ */ new Set([
|
|
6447
|
+
"DuplicateProjectMembershipError",
|
|
6448
|
+
"ProjectMembershipIdConflictError"
|
|
6449
|
+
]);
|
|
6450
|
+
function isProjectMembershipConflict(error) {
|
|
6451
|
+
return error instanceof Error && membershipConflictNames.has(error.name);
|
|
6277
6452
|
}
|
|
6453
|
+
var ProjectAccessService = class {
|
|
6454
|
+
/** Creates a project access service from explicitly injected stores. */
|
|
6455
|
+
constructor(deps) {
|
|
6456
|
+
this.deps = deps;
|
|
6457
|
+
this.idFactory = deps.idFactory ?? randomUUID6;
|
|
6458
|
+
}
|
|
6459
|
+
/** Returns the project and active membership when the user has the requested capability. */
|
|
6460
|
+
async requireProjectAccess(input) {
|
|
6461
|
+
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
6462
|
+
if (!project) {
|
|
6463
|
+
throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
6464
|
+
}
|
|
6465
|
+
if (project.kind === "public") {
|
|
6466
|
+
return this.requirePublicProjectAccess(project, input);
|
|
6467
|
+
}
|
|
6468
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
6469
|
+
input.tenantId,
|
|
6470
|
+
input.projectId,
|
|
6471
|
+
input.userId
|
|
6472
|
+
);
|
|
6473
|
+
if (membership?.status === "active") {
|
|
6474
|
+
this.requireCapability(membership, input.capability);
|
|
6475
|
+
return { project, membership };
|
|
6476
|
+
}
|
|
6477
|
+
const memberships = await this.deps.membershipStore.list(input.tenantId, input.projectId);
|
|
6478
|
+
if (memberships.length !== 0) this.accessDenied();
|
|
6479
|
+
if (project.kind === "personal") this.accessDenied();
|
|
6480
|
+
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
6481
|
+
if (tenantLink?.role !== "owner" && tenantLink?.role !== "admin") this.accessDenied();
|
|
6482
|
+
let claim;
|
|
6483
|
+
try {
|
|
6484
|
+
claim = await this.deps.membershipStore.createInitialOwner({
|
|
6485
|
+
id: this.idFactory(),
|
|
6486
|
+
tenantId: input.tenantId,
|
|
6487
|
+
projectId: input.projectId,
|
|
6488
|
+
userId: input.userId
|
|
6489
|
+
});
|
|
6490
|
+
} catch (error) {
|
|
6491
|
+
if (isProjectMembershipConflict(error)) {
|
|
6492
|
+
throw new ProjectCollaborationError(
|
|
6493
|
+
"PROJECT_MEMBERSHIP_CONFLICT",
|
|
6494
|
+
409,
|
|
6495
|
+
"Project membership could not be initialized"
|
|
6496
|
+
);
|
|
6497
|
+
}
|
|
6498
|
+
throw error;
|
|
6499
|
+
}
|
|
6500
|
+
if (claim.kind === "created" || claim.kind === "existing") {
|
|
6501
|
+
return { project, membership: claim.membership };
|
|
6502
|
+
}
|
|
6503
|
+
const racedMembership = await this.deps.membershipStore.findByUser(
|
|
6504
|
+
input.tenantId,
|
|
6505
|
+
input.projectId,
|
|
6506
|
+
input.userId
|
|
6507
|
+
);
|
|
6508
|
+
if (racedMembership?.status !== "active") this.accessDenied();
|
|
6509
|
+
this.requireCapability(racedMembership, input.capability);
|
|
6510
|
+
return { project, membership: racedMembership };
|
|
6511
|
+
}
|
|
6512
|
+
/** Authorizes an already-existing membership, or any tenant member for public rooms; never mutates persistence. */
|
|
6513
|
+
async requireExistingProjectAccess(input) {
|
|
6514
|
+
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
6515
|
+
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
6516
|
+
if (project.kind === "public") {
|
|
6517
|
+
return this.requireExistingPublicProjectAccess(project, input);
|
|
6518
|
+
}
|
|
6519
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
6520
|
+
input.tenantId,
|
|
6521
|
+
input.projectId,
|
|
6522
|
+
input.userId
|
|
6523
|
+
);
|
|
6524
|
+
if (!membership || membership.tenantId !== input.tenantId || membership.projectId !== input.projectId || membership.userId !== input.userId || membership.status !== "active") this.accessDenied();
|
|
6525
|
+
this.requireCapability(membership, input.capability);
|
|
6526
|
+
return { project, membership };
|
|
6527
|
+
}
|
|
6528
|
+
/** Authorizes roster visibility: read for public rooms, manage elsewhere. */
|
|
6529
|
+
async requireRosterAccess(input) {
|
|
6530
|
+
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
6531
|
+
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
6532
|
+
return this.requireProjectAccess({
|
|
6533
|
+
...input,
|
|
6534
|
+
capability: project.kind === "public" ? "read" : "manage"
|
|
6535
|
+
});
|
|
6536
|
+
}
|
|
6537
|
+
async requirePublicProjectAccess(project, input) {
|
|
6538
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
6539
|
+
input.tenantId,
|
|
6540
|
+
input.projectId,
|
|
6541
|
+
input.userId
|
|
6542
|
+
);
|
|
6543
|
+
if (membership?.status === "active") {
|
|
6544
|
+
this.requireCapability(membership, input.capability);
|
|
6545
|
+
return { project, membership };
|
|
6546
|
+
}
|
|
6547
|
+
const materialized = await this.materializePublicMembership(input);
|
|
6548
|
+
this.requireCapability(materialized, input.capability);
|
|
6549
|
+
return { project, membership: materialized };
|
|
6550
|
+
}
|
|
6551
|
+
async requireExistingPublicProjectAccess(project, input) {
|
|
6552
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
6553
|
+
input.tenantId,
|
|
6554
|
+
input.projectId,
|
|
6555
|
+
input.userId
|
|
6556
|
+
);
|
|
6557
|
+
if (membership?.status === "active") {
|
|
6558
|
+
this.requireCapability(membership, input.capability);
|
|
6559
|
+
return { project, membership };
|
|
6560
|
+
}
|
|
6561
|
+
if (membership) this.accessDenied();
|
|
6562
|
+
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
6563
|
+
if (!tenantLink) this.accessDenied();
|
|
6564
|
+
const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
|
|
6565
|
+
const synthetic = {
|
|
6566
|
+
id: `public:${input.userId}`,
|
|
6567
|
+
tenantId: input.tenantId,
|
|
6568
|
+
projectId: input.projectId,
|
|
6569
|
+
userId: input.userId,
|
|
6570
|
+
role,
|
|
6571
|
+
status: "active",
|
|
6572
|
+
joinedAt: project.createdAt,
|
|
6573
|
+
updatedAt: project.updatedAt
|
|
6574
|
+
};
|
|
6575
|
+
this.requireCapability(synthetic, input.capability);
|
|
6576
|
+
return { project, membership: synthetic };
|
|
6577
|
+
}
|
|
6578
|
+
async materializePublicMembership(input) {
|
|
6579
|
+
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
6580
|
+
if (!tenantLink) this.accessDenied();
|
|
6581
|
+
const existing = await this.deps.membershipStore.findByUser(
|
|
6582
|
+
input.tenantId,
|
|
6583
|
+
input.projectId,
|
|
6584
|
+
input.userId
|
|
6585
|
+
);
|
|
6586
|
+
if (existing) this.accessDenied();
|
|
6587
|
+
const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
|
|
6588
|
+
try {
|
|
6589
|
+
return await this.deps.membershipStore.create({
|
|
6590
|
+
id: this.idFactory(),
|
|
6591
|
+
tenantId: input.tenantId,
|
|
6592
|
+
projectId: input.projectId,
|
|
6593
|
+
userId: input.userId,
|
|
6594
|
+
role,
|
|
6595
|
+
status: "active"
|
|
6596
|
+
});
|
|
6597
|
+
} catch (error) {
|
|
6598
|
+
if (isProjectMembershipConflict(error)) {
|
|
6599
|
+
const raced = await this.deps.membershipStore.findByUser(
|
|
6600
|
+
input.tenantId,
|
|
6601
|
+
input.projectId,
|
|
6602
|
+
input.userId
|
|
6603
|
+
);
|
|
6604
|
+
if (raced?.status === "active") return raced;
|
|
6605
|
+
}
|
|
6606
|
+
throw error;
|
|
6607
|
+
}
|
|
6608
|
+
}
|
|
6609
|
+
requireCapability(membership, capability2) {
|
|
6610
|
+
if (!allowedRoles[capability2].has(membership.role)) this.accessDenied();
|
|
6611
|
+
}
|
|
6612
|
+
accessDenied() {
|
|
6613
|
+
throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project access denied");
|
|
6614
|
+
}
|
|
6615
|
+
};
|
|
6278
6616
|
|
|
6279
6617
|
// src/controllers/database-configs.ts
|
|
6280
6618
|
import {
|
|
6281
6619
|
getStoreLattice as getStoreLattice7,
|
|
6282
6620
|
sqlDatabaseManager
|
|
6283
6621
|
} from "@axiom-lattice/core";
|
|
6284
|
-
import { randomUUID as
|
|
6622
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
6285
6623
|
function getTenantId9(request) {
|
|
6286
6624
|
const userTenantId = request.user?.tenantId;
|
|
6287
6625
|
if (userTenantId) {
|
|
@@ -6359,7 +6697,7 @@ async function createDatabaseConfig(request, reply) {
|
|
|
6359
6697
|
message: "Database configuration with this key already exists"
|
|
6360
6698
|
};
|
|
6361
6699
|
}
|
|
6362
|
-
const id3 = body.id ||
|
|
6700
|
+
const id3 = body.id || randomUUID7();
|
|
6363
6701
|
const config = await store.createConfig(tenantId, id3, body);
|
|
6364
6702
|
try {
|
|
6365
6703
|
sqlDatabaseManager.registerDatabase(tenantId, config.key, config.config);
|
|
@@ -6564,7 +6902,7 @@ import {
|
|
|
6564
6902
|
metricsServerManager as metricsServerManager2,
|
|
6565
6903
|
SemanticMetricsClient as SemanticMetricsClient2
|
|
6566
6904
|
} from "@axiom-lattice/core";
|
|
6567
|
-
import { randomUUID as
|
|
6905
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
6568
6906
|
function getTenantId10(request) {
|
|
6569
6907
|
const userTenantId = request.user?.tenantId;
|
|
6570
6908
|
if (userTenantId) {
|
|
@@ -6645,7 +6983,7 @@ async function createMetricsServerConfig(request, reply) {
|
|
|
6645
6983
|
message: "selectedDataSources is required for semantic metrics servers"
|
|
6646
6984
|
};
|
|
6647
6985
|
}
|
|
6648
|
-
const id3 = body.id ||
|
|
6986
|
+
const id3 = body.id || randomUUID8();
|
|
6649
6987
|
const configData = {
|
|
6650
6988
|
key: body.key,
|
|
6651
6989
|
name: body.name,
|
|
@@ -9152,7 +9490,7 @@ function registerChannelRoutes(app2, dependencies) {
|
|
|
9152
9490
|
}
|
|
9153
9491
|
|
|
9154
9492
|
// src/controllers/channel-installations.ts
|
|
9155
|
-
import { randomUUID as
|
|
9493
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
9156
9494
|
import {
|
|
9157
9495
|
descriptorDataValue
|
|
9158
9496
|
} from "@axiom-lattice/protocols";
|
|
@@ -9356,7 +9694,7 @@ async function createChannelInstallation(request, reply) {
|
|
|
9356
9694
|
};
|
|
9357
9695
|
}
|
|
9358
9696
|
const store = await getInstallationStore();
|
|
9359
|
-
const installationId =
|
|
9697
|
+
const installationId = randomUUID9();
|
|
9360
9698
|
const createInput = {
|
|
9361
9699
|
channel: body.channel,
|
|
9362
9700
|
name: body.name,
|
|
@@ -10316,7 +10654,7 @@ async function getLocalA2ATemplates(_request, reply) {
|
|
|
10316
10654
|
}
|
|
10317
10655
|
|
|
10318
10656
|
// src/routes/a2a-test-connection.ts
|
|
10319
|
-
import { randomUUID as
|
|
10657
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
10320
10658
|
function describeTestConnectionError(error, agentCardUrl) {
|
|
10321
10659
|
if (!(error instanceof Error)) {
|
|
10322
10660
|
return `Failed to fetch agent card from ${agentCardUrl}: ${String(error)}`;
|
|
@@ -10356,7 +10694,7 @@ async function ensureLocalRuntimeForConnectionTest(localRuntime, agentCardUrl) {
|
|
|
10356
10694
|
return { error: portValidation.error };
|
|
10357
10695
|
}
|
|
10358
10696
|
const state = await localA2AService.startProvider(
|
|
10359
|
-
`test-${
|
|
10697
|
+
`test-${randomUUID10()}`,
|
|
10360
10698
|
testRuntime
|
|
10361
10699
|
);
|
|
10362
10700
|
if (state.status === "running") return { runtimeId: state.runtimeId };
|
|
@@ -10445,106 +10783,6 @@ import {
|
|
|
10445
10783
|
import { snapshotExactRecord as snapshotExactRecord3 } from "@axiom-lattice/protocols";
|
|
10446
10784
|
import { ProjectTaskOwnerPolicyError, projectTaskAdmissionNotifier } from "@axiom-lattice/core";
|
|
10447
10785
|
|
|
10448
|
-
// src/services/collaboration/ProjectAccessService.ts
|
|
10449
|
-
import { randomUUID as randomUUID9 } from "crypto";
|
|
10450
|
-
var ProjectCollaborationError = class extends Error {
|
|
10451
|
-
/** Creates a project collaboration error. */
|
|
10452
|
-
constructor(code, statusCode, message) {
|
|
10453
|
-
super(message);
|
|
10454
|
-
this.code = code;
|
|
10455
|
-
this.statusCode = statusCode;
|
|
10456
|
-
this.name = "ProjectCollaborationError";
|
|
10457
|
-
}
|
|
10458
|
-
};
|
|
10459
|
-
var allowedRoles = {
|
|
10460
|
-
read: /* @__PURE__ */ new Set(["owner", "admin", "member", "viewer"]),
|
|
10461
|
-
send: /* @__PURE__ */ new Set(["owner", "admin", "member"]),
|
|
10462
|
-
manage: /* @__PURE__ */ new Set(["owner", "admin"])
|
|
10463
|
-
};
|
|
10464
|
-
var membershipConflictNames = /* @__PURE__ */ new Set([
|
|
10465
|
-
"DuplicateProjectMembershipError",
|
|
10466
|
-
"ProjectMembershipIdConflictError"
|
|
10467
|
-
]);
|
|
10468
|
-
function isProjectMembershipConflict(error) {
|
|
10469
|
-
return error instanceof Error && membershipConflictNames.has(error.name);
|
|
10470
|
-
}
|
|
10471
|
-
var ProjectAccessService = class {
|
|
10472
|
-
/** Creates a project access service from explicitly injected stores. */
|
|
10473
|
-
constructor(deps) {
|
|
10474
|
-
this.deps = deps;
|
|
10475
|
-
this.idFactory = deps.idFactory ?? randomUUID9;
|
|
10476
|
-
}
|
|
10477
|
-
/** Returns the project and active membership when the user has the requested capability. */
|
|
10478
|
-
async requireProjectAccess(input) {
|
|
10479
|
-
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
10480
|
-
if (!project) {
|
|
10481
|
-
throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
10482
|
-
}
|
|
10483
|
-
const membership = await this.deps.membershipStore.findByUser(
|
|
10484
|
-
input.tenantId,
|
|
10485
|
-
input.projectId,
|
|
10486
|
-
input.userId
|
|
10487
|
-
);
|
|
10488
|
-
if (membership?.status === "active") {
|
|
10489
|
-
this.requireCapability(membership, input.capability);
|
|
10490
|
-
return { project, membership };
|
|
10491
|
-
}
|
|
10492
|
-
const memberships = await this.deps.membershipStore.list(input.tenantId, input.projectId);
|
|
10493
|
-
if (memberships.length !== 0) this.accessDenied();
|
|
10494
|
-
if (project.kind === "personal") this.accessDenied();
|
|
10495
|
-
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
10496
|
-
if (tenantLink?.role !== "owner" && tenantLink?.role !== "admin") this.accessDenied();
|
|
10497
|
-
let claim;
|
|
10498
|
-
try {
|
|
10499
|
-
claim = await this.deps.membershipStore.createInitialOwner({
|
|
10500
|
-
id: this.idFactory(),
|
|
10501
|
-
tenantId: input.tenantId,
|
|
10502
|
-
projectId: input.projectId,
|
|
10503
|
-
userId: input.userId
|
|
10504
|
-
});
|
|
10505
|
-
} catch (error) {
|
|
10506
|
-
if (isProjectMembershipConflict(error)) {
|
|
10507
|
-
throw new ProjectCollaborationError(
|
|
10508
|
-
"PROJECT_MEMBERSHIP_CONFLICT",
|
|
10509
|
-
409,
|
|
10510
|
-
"Project membership could not be initialized"
|
|
10511
|
-
);
|
|
10512
|
-
}
|
|
10513
|
-
throw error;
|
|
10514
|
-
}
|
|
10515
|
-
if (claim.kind === "created" || claim.kind === "existing") {
|
|
10516
|
-
return { project, membership: claim.membership };
|
|
10517
|
-
}
|
|
10518
|
-
const racedMembership = await this.deps.membershipStore.findByUser(
|
|
10519
|
-
input.tenantId,
|
|
10520
|
-
input.projectId,
|
|
10521
|
-
input.userId
|
|
10522
|
-
);
|
|
10523
|
-
if (racedMembership?.status !== "active") this.accessDenied();
|
|
10524
|
-
this.requireCapability(racedMembership, input.capability);
|
|
10525
|
-
return { project, membership: racedMembership };
|
|
10526
|
-
}
|
|
10527
|
-
/** Authorizes only an already-existing active membership and never mutates persistence. */
|
|
10528
|
-
async requireExistingProjectAccess(input) {
|
|
10529
|
-
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
10530
|
-
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
10531
|
-
const membership = await this.deps.membershipStore.findByUser(
|
|
10532
|
-
input.tenantId,
|
|
10533
|
-
input.projectId,
|
|
10534
|
-
input.userId
|
|
10535
|
-
);
|
|
10536
|
-
if (!membership || membership.tenantId !== input.tenantId || membership.projectId !== input.projectId || membership.userId !== input.userId || membership.status !== "active") this.accessDenied();
|
|
10537
|
-
this.requireCapability(membership, input.capability);
|
|
10538
|
-
return { project, membership };
|
|
10539
|
-
}
|
|
10540
|
-
requireCapability(membership, capability2) {
|
|
10541
|
-
if (!allowedRoles[capability2].has(membership.role)) this.accessDenied();
|
|
10542
|
-
}
|
|
10543
|
-
accessDenied() {
|
|
10544
|
-
throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project access denied");
|
|
10545
|
-
}
|
|
10546
|
-
};
|
|
10547
|
-
|
|
10548
10786
|
// src/services/collaboration/ProjectTaskThreadService.ts
|
|
10549
10787
|
import { isProjectLifecycleTask } from "@axiom-lattice/core";
|
|
10550
10788
|
import {
|
|
@@ -11083,9 +11321,6 @@ function governedUpdate(task, body) {
|
|
|
11083
11321
|
if (has("ownerType") && body.ownerType !== task.ownerType || has("ownerId") && body.ownerId !== task.ownerId) {
|
|
11084
11322
|
return "TASK_OWNERSHIP_IMMUTABLE";
|
|
11085
11323
|
}
|
|
11086
|
-
if (task.ownerType !== "agent") return void 0;
|
|
11087
|
-
if (has("parentId") && body.parentId !== task.parentId || has("dependencies") || has("requireReview") || has("result") || has("failureReason") || has("status")) return "TASK_LIFECYCLE_TRANSITION_REQUIRED";
|
|
11088
|
-
if (has("context") || has("description")) return "COMPLETION_CONTEXT_MUTATION";
|
|
11089
11324
|
return void 0;
|
|
11090
11325
|
}
|
|
11091
11326
|
function lifecycleStatus(code) {
|
|
@@ -11526,7 +11761,8 @@ var AgentWebAppService = class {
|
|
|
11526
11761
|
integration: current.integration,
|
|
11527
11762
|
scope: { ...current.scope, ...patch.scope },
|
|
11528
11763
|
features: { ...current.features, ...patch.features },
|
|
11529
|
-
appearance: { ...current.appearance, ...patch.appearance }
|
|
11764
|
+
appearance: { ...current.appearance, ...patch.appearance },
|
|
11765
|
+
identity: patch.identity ?? current.identity
|
|
11530
11766
|
};
|
|
11531
11767
|
await this.validateAssistant(context, current.assistantId);
|
|
11532
11768
|
const validated = await this.validateConfig(context.tenantId, effective);
|
|
@@ -11535,7 +11771,8 @@ var AgentWebAppService = class {
|
|
|
11535
11771
|
...Object.prototype.hasOwnProperty.call(patch, "description") ? { description: validated.description } : {},
|
|
11536
11772
|
...patch.scope ? { scope: validated.scope } : {},
|
|
11537
11773
|
...patch.features ? { features: validated.features } : {},
|
|
11538
|
-
...patch.appearance ? { appearance: validated.appearance } : {}
|
|
11774
|
+
...patch.appearance ? { appearance: validated.appearance } : {},
|
|
11775
|
+
...patch.identity !== void 0 ? { identity: patch.identity } : {}
|
|
11539
11776
|
};
|
|
11540
11777
|
return this.writeSnapshot(context, current, update);
|
|
11541
11778
|
}
|
|
@@ -11648,13 +11885,28 @@ var agentWebAppFeaturesSchema = z4.object({
|
|
|
11648
11885
|
threadManagement: z4.boolean(),
|
|
11649
11886
|
attachments: z4.boolean(),
|
|
11650
11887
|
hitl: z4.boolean(),
|
|
11651
|
-
genUI: z4.boolean()
|
|
11888
|
+
genUI: z4.boolean(),
|
|
11889
|
+
files: z4.boolean().optional()
|
|
11652
11890
|
}).strict();
|
|
11653
11891
|
var agentWebAppAppearanceSchema = z4.object({
|
|
11654
11892
|
title: z4.string().max(120).optional(),
|
|
11655
11893
|
welcomeMessage: z4.string().max(1e3).optional(),
|
|
11656
11894
|
primaryColor: z4.string().regex(/^#[0-9A-Fa-f]{6}$/).optional()
|
|
11657
11895
|
}).strict();
|
|
11896
|
+
var agentWebAppIssuerSchema = z4.object({
|
|
11897
|
+
iss: z4.string().min(1).max(255),
|
|
11898
|
+
aud: z4.string().min(1).max(255),
|
|
11899
|
+
alg: z4.enum(["HS256", "RS256"]),
|
|
11900
|
+
secret: z4.string().min(1).max(512).optional(),
|
|
11901
|
+
jwksUrl: z4.string().url().max(2048).optional()
|
|
11902
|
+
}).strict().refine(
|
|
11903
|
+
(value) => value.alg === "HS256" ? value.secret !== void 0 : value.jwksUrl !== void 0,
|
|
11904
|
+
{ message: "HS256 requires secret; RS256 requires jwksUrl" }
|
|
11905
|
+
);
|
|
11906
|
+
var agentWebAppIdentitySchema = z4.object({
|
|
11907
|
+
policy: z4.enum(["unverified", "verified", "both"]),
|
|
11908
|
+
issuers: z4.array(agentWebAppIssuerSchema).min(1).max(10)
|
|
11909
|
+
}).strict();
|
|
11658
11910
|
var createAgentWebAppSchema = z4.object({
|
|
11659
11911
|
assistantId: externalId,
|
|
11660
11912
|
name: z4.string().min(1).max(120),
|
|
@@ -11662,14 +11914,16 @@ var createAgentWebAppSchema = z4.object({
|
|
|
11662
11914
|
integration: z4.object({ type: z4.literal("react_sdk") }).strict(),
|
|
11663
11915
|
scope: agentWebAppScopeSchema,
|
|
11664
11916
|
features: agentWebAppFeaturesSchema,
|
|
11665
|
-
appearance: agentWebAppAppearanceSchema
|
|
11917
|
+
appearance: agentWebAppAppearanceSchema,
|
|
11918
|
+
identity: agentWebAppIdentitySchema.optional()
|
|
11666
11919
|
}).strict();
|
|
11667
11920
|
var updateAgentWebAppSchema = z4.object({
|
|
11668
11921
|
name: z4.string().min(1).max(120).optional(),
|
|
11669
11922
|
description: z4.string().max(1e3).optional(),
|
|
11670
11923
|
scope: agentWebAppScopeSchema.partial().strict().optional(),
|
|
11671
11924
|
features: agentWebAppFeaturesSchema.partial().strict().optional(),
|
|
11672
|
-
appearance: agentWebAppAppearanceSchema.partial().strict().optional()
|
|
11925
|
+
appearance: agentWebAppAppearanceSchema.partial().strict().optional(),
|
|
11926
|
+
identity: agentWebAppIdentitySchema.optional()
|
|
11673
11927
|
}).strict().refine((value) => Object.keys(value).length > 0, "Patch must not be empty");
|
|
11674
11928
|
var listAgentWebAppsQuerySchema = z4.object({
|
|
11675
11929
|
assistantId: externalId.optional()
|
|
@@ -11756,10 +12010,11 @@ function invalidRequest(reply, statusCode) {
|
|
|
11756
12010
|
}
|
|
11757
12011
|
|
|
11758
12012
|
// src/routes/agent-web-app-runtime.ts
|
|
12013
|
+
import { createHmac as createHmac3 } from "crypto";
|
|
11759
12014
|
import { z as z5, ZodError as ZodError2 } from "zod";
|
|
11760
12015
|
|
|
11761
12016
|
// src/services/agent-web-app/AgentWebAppRuntimeService.ts
|
|
11762
|
-
import { createHash, randomUUID as
|
|
12017
|
+
import { createHash as createHash2, randomUUID as randomUUID11 } from "crypto";
|
|
11763
12018
|
import { MessageChunkTypes as MessageChunkTypes3, parseAgentWebAppGenUIBlock } from "@axiom-lattice/protocols";
|
|
11764
12019
|
function isRecord2(value) {
|
|
11765
12020
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -11776,7 +12031,16 @@ var AgentWebAppRuntimeService = class {
|
|
|
11776
12031
|
constructor(deps) {
|
|
11777
12032
|
this.deps = deps;
|
|
11778
12033
|
}
|
|
11779
|
-
|
|
12034
|
+
/** Identity policy configuration for the web app, or null when the app is missing/unconfigured. */
|
|
12035
|
+
async getIdentityConfig(webAppId) {
|
|
12036
|
+
try {
|
|
12037
|
+
const runtime = await this.resolveRuntime(webAppId, {});
|
|
12038
|
+
return runtime.webApp.identity ?? null;
|
|
12039
|
+
} catch {
|
|
12040
|
+
return null;
|
|
12041
|
+
}
|
|
12042
|
+
}
|
|
12043
|
+
async bootstrap(webAppId, userId, selection, identity) {
|
|
11780
12044
|
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
11781
12045
|
const assistant = await this.deps.assistantResolver.resolve(
|
|
11782
12046
|
runtime.webApp.tenantId,
|
|
@@ -11809,7 +12073,7 @@ var AgentWebAppRuntimeService = class {
|
|
|
11809
12073
|
defaultModelKey: runtime.webApp.scope.defaultModelKey,
|
|
11810
12074
|
features: runtime.webApp.features,
|
|
11811
12075
|
appearance: runtime.webApp.appearance,
|
|
11812
|
-
identityAssurance: "unverified"
|
|
12076
|
+
identityAssurance: identity?.assurance ?? "unverified"
|
|
11813
12077
|
},
|
|
11814
12078
|
projects,
|
|
11815
12079
|
models
|
|
@@ -11830,6 +12094,14 @@ var AgentWebAppRuntimeService = class {
|
|
|
11830
12094
|
this.requireFeature(runtime.webApp.features.threadManagement);
|
|
11831
12095
|
return this.createOwnedThread(runtime, userId, input.label);
|
|
11832
12096
|
}
|
|
12097
|
+
/** Fetch one owned thread. */
|
|
12098
|
+
async getThread(webAppId, userId, threadId, selection) {
|
|
12099
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12100
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
12101
|
+
const thread = await this.deps.threadStore.getThreadById(runtime.webApp.tenantId, threadId);
|
|
12102
|
+
if (!thread) this.threadNotFound();
|
|
12103
|
+
return toRuntimeThread(thread);
|
|
12104
|
+
}
|
|
11833
12105
|
/** Update a thread label; omitted or blank labels preserve the existing label. */
|
|
11834
12106
|
async updateThread(webAppId, userId, threadId, input) {
|
|
11835
12107
|
const runtime = await this.resolveRuntime(webAppId, input);
|
|
@@ -11864,6 +12136,60 @@ var AgentWebAppRuntimeService = class {
|
|
|
11864
12136
|
return [{ id: message.id, role: message.role, ...content && (typeof content === "string" || content.length > 0) ? { content } : {} }];
|
|
11865
12137
|
});
|
|
11866
12138
|
}
|
|
12139
|
+
async getState(webAppId, userId, threadId, selection) {
|
|
12140
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12141
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
12142
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
12143
|
+
const state = await agent.getCurrentState();
|
|
12144
|
+
const pendingMessages = await agent.getPendingMessages();
|
|
12145
|
+
const normalized = pendingMessages.map((message) => ({
|
|
12146
|
+
...message,
|
|
12147
|
+
content: typeof message.content === "object" && message.content !== null ? "message" in message.content ? message.content.message : JSON.stringify(message.content) : String(message.content ?? "")
|
|
12148
|
+
}));
|
|
12149
|
+
return { ...state && typeof state === "object" ? state : {}, pendingMessages: normalized };
|
|
12150
|
+
}
|
|
12151
|
+
/** Raw stored messages (Console memory contract, tool/interrupt preserved). */
|
|
12152
|
+
async getConsoleMessages(webAppId, userId, threadId, selection) {
|
|
12153
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12154
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
12155
|
+
return this.deps.messageReader.getCurrentMessages({
|
|
12156
|
+
tenantId: runtime.webApp.tenantId,
|
|
12157
|
+
assistantId: runtime.webApp.assistantId,
|
|
12158
|
+
threadId,
|
|
12159
|
+
workspaceId: runtime.project.workspaceId,
|
|
12160
|
+
projectId: runtime.project.id
|
|
12161
|
+
});
|
|
12162
|
+
}
|
|
12163
|
+
/** Background enqueue only (ADR-026): returns the message id; delivery happens over resume_stream. */
|
|
12164
|
+
async enqueueMessage(webAppId, userId, threadId, content, selection) {
|
|
12165
|
+
const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, selection);
|
|
12166
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
12167
|
+
const execution = await this.execute(agent, runtime, { input: { message: content } });
|
|
12168
|
+
return { messageId: execution.messageId };
|
|
12169
|
+
}
|
|
12170
|
+
async removePendingMessage(webAppId, userId, threadId, messageId, selection) {
|
|
12171
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12172
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
12173
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
12174
|
+
if (!await agent.removePendingMessage(messageId)) this.threadNotFound();
|
|
12175
|
+
}
|
|
12176
|
+
/** Thread-lifetime SSE resume from the last known client message id. */
|
|
12177
|
+
async resumeStream(webAppId, userId, threadId, messageId, selection) {
|
|
12178
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12179
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
12180
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
12181
|
+
return { stream: agent.chunkStream(messageId, []) };
|
|
12182
|
+
}
|
|
12183
|
+
async resolveFileScope(webAppId, selection) {
|
|
12184
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12185
|
+
return {
|
|
12186
|
+
tenantId: runtime.webApp.tenantId,
|
|
12187
|
+
workspaceId: runtime.project.workspaceId,
|
|
12188
|
+
projectId: runtime.project.id,
|
|
12189
|
+
assistantId: runtime.webApp.assistantId,
|
|
12190
|
+
enabled: runtime.webApp.features.files === true
|
|
12191
|
+
};
|
|
12192
|
+
}
|
|
11867
12193
|
async streamMessage(webAppId, userId, threadId, input) {
|
|
11868
12194
|
const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, input);
|
|
11869
12195
|
const refs = this.resolveAttachmentRefs(runtime, userId, threadId, input.attachmentRefs ?? []);
|
|
@@ -11882,10 +12208,10 @@ ${formatFileRefsSection(refs)}` : input.content;
|
|
|
11882
12208
|
const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, selection);
|
|
11883
12209
|
this.requireNamedFeature(runtime.webApp.features.attachments, "Attachments are disabled");
|
|
11884
12210
|
if (!this.deps.saveProjectFile || !this.deps.attachmentRefs) this.fail("INTERNAL_ERROR", 500, "Attachment service is unavailable");
|
|
11885
|
-
const userDigest =
|
|
11886
|
-
const contentDigest =
|
|
12211
|
+
const userDigest = createHash2("sha256").update(userId, "utf8").digest("hex");
|
|
12212
|
+
const contentDigest = createHash2("sha256").update(file.bytes).digest("hex");
|
|
11887
12213
|
const displayName = file.name;
|
|
11888
|
-
const physicalName = `${
|
|
12214
|
+
const physicalName = `${randomUUID11()}-${sanitizePhysicalFilename(file.name)}`;
|
|
11889
12215
|
const saved = await this.deps.saveProjectFile({
|
|
11890
12216
|
tenantId: runtime.webApp.tenantId,
|
|
11891
12217
|
workspaceId: runtime.project.workspaceId,
|
|
@@ -12037,7 +12363,7 @@ ${formatFileRefsSection(refs)}` : input.content;
|
|
|
12037
12363
|
const thread = await this.deps.threadStore.createThread(
|
|
12038
12364
|
runtime.webApp.tenantId,
|
|
12039
12365
|
runtime.webApp.assistantId,
|
|
12040
|
-
|
|
12366
|
+
randomUUID11(),
|
|
12041
12367
|
{ metadata: this.metadata(runtime, userId, normalizeLabel(rawLabel)) }
|
|
12042
12368
|
);
|
|
12043
12369
|
return toRuntimeThread(thread);
|
|
@@ -12089,7 +12415,7 @@ function implicitThreadId(runtime, userId) {
|
|
|
12089
12415
|
userId,
|
|
12090
12416
|
runtime.project.id
|
|
12091
12417
|
]);
|
|
12092
|
-
const bytes =
|
|
12418
|
+
const bytes = createHash2("sha256").update(canonicalTuple, "utf8").digest().subarray(0, 16);
|
|
12093
12419
|
bytes[6] = bytes[6] & 15 | 128;
|
|
12094
12420
|
bytes[8] = bytes[8] & 63 | 128;
|
|
12095
12421
|
const hex = bytes.toString("hex");
|
|
@@ -12108,96 +12434,102 @@ function toRuntimeThread(thread) {
|
|
|
12108
12434
|
};
|
|
12109
12435
|
}
|
|
12110
12436
|
|
|
12111
|
-
// src/services/agent-web-app/
|
|
12112
|
-
import {
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
return { startedToolIds: /* @__PURE__ */ new Set(), allowInterrupts: options.allowInterrupts ?? true, allowGenUI: options.allowGenUI ?? false };
|
|
12118
|
-
}
|
|
12119
|
-
function projectAgentWebAppChunk(chunk, context) {
|
|
12120
|
-
if (chunk.type === MessageChunkTypes4.AI) {
|
|
12121
|
-
const events = [];
|
|
12122
|
-
const startedToolIds = context?.startedToolIds ?? /* @__PURE__ */ new Set();
|
|
12123
|
-
if (typeof chunk.data.content === "string" && chunk.data.content) events.push({ type: "message.delta", text: chunk.data.content });
|
|
12124
|
-
if (context?.allowGenUI && Array.isArray(chunk.data.content)) {
|
|
12125
|
-
for (const value of chunk.data.content) {
|
|
12126
|
-
const block = parseAgentWebAppGenUIBlock2(value);
|
|
12127
|
-
if (block) events.push({ type: "genui.render", block });
|
|
12128
|
-
}
|
|
12129
|
-
}
|
|
12130
|
-
for (const call of chunk.data.tool_call_chunks ?? []) {
|
|
12131
|
-
if (call.id && call.name && !startedToolIds.has(call.id)) {
|
|
12132
|
-
startedToolIds.add(call.id);
|
|
12133
|
-
events.push({ type: "tool.started", id: call.id, name: call.name });
|
|
12134
|
-
}
|
|
12135
|
-
}
|
|
12136
|
-
for (const call of chunk.data.tool_calls ?? []) {
|
|
12137
|
-
if (!startedToolIds.has(call.id)) {
|
|
12138
|
-
startedToolIds.add(call.id);
|
|
12139
|
-
events.push({ type: "tool.started", id: call.id, name: call.name });
|
|
12140
|
-
}
|
|
12141
|
-
}
|
|
12142
|
-
return events;
|
|
12143
|
-
}
|
|
12144
|
-
if (chunk.type === MessageChunkTypes4.TOOL && chunk.data.tool_call_id) {
|
|
12145
|
-
return [{ type: "tool.completed", id: chunk.data.tool_call_id }];
|
|
12146
|
-
}
|
|
12147
|
-
if (chunk.type === MessageChunkTypes4.INTERRUPT) {
|
|
12148
|
-
if (context && !context.allowInterrupts) return [{ type: "stream.completed" }];
|
|
12149
|
-
const interrupt = publicInterrupt(chunk);
|
|
12150
|
-
return interrupt ? [{ type: "interrupt.created", interrupt }, { type: "stream.completed" }] : [{ type: "stream.completed" }];
|
|
12151
|
-
}
|
|
12152
|
-
if (chunk.type === MessageChunkTypes4.MESSAGE_COMPLETED) {
|
|
12153
|
-
return [
|
|
12154
|
-
{ type: "message.completed", messageId: chunk.data.id },
|
|
12155
|
-
{ type: "stream.completed" }
|
|
12156
|
-
];
|
|
12157
|
-
}
|
|
12158
|
-
if (chunk.type === MessageChunkTypes4.MESSAGE_FAILED) {
|
|
12159
|
-
return [
|
|
12160
|
-
{ type: "error", error: { code: "STREAM_FAILED", message: "Stream failed", retryable: true } },
|
|
12161
|
-
{ type: "stream.completed" }
|
|
12162
|
-
];
|
|
12437
|
+
// src/services/agent-web-app/AgentWebAppTokenVerifier.ts
|
|
12438
|
+
import { createHmac as createHmac2, createPublicKey, timingSafeEqual as timingSafeEqual2, verify as cryptoVerify } from "crypto";
|
|
12439
|
+
var WebAppTokenError = class extends Error {
|
|
12440
|
+
constructor(message) {
|
|
12441
|
+
super(message);
|
|
12442
|
+
this.name = "WebAppTokenError";
|
|
12163
12443
|
}
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
const
|
|
12171
|
-
|
|
12172
|
-
return {
|
|
12173
|
-
id: id3,
|
|
12174
|
-
type: typeof value.type === "string" ? value.type : "input",
|
|
12175
|
-
prompt: typeof value.prompt === "string" ? value.prompt : typeof value.message === "string" ? value.message : "Input required",
|
|
12176
|
-
...isRecord3(value.data) ? { data: value.data } : {}
|
|
12177
|
-
};
|
|
12178
|
-
}
|
|
12179
|
-
function parseInterruptContent(content) {
|
|
12180
|
-
if (!content) return {};
|
|
12181
|
-
if (isRecord3(content)) return content;
|
|
12182
|
-
if (typeof content !== "string") return void 0;
|
|
12444
|
+
};
|
|
12445
|
+
var MAX_TOKEN_BYTES = 8 * 1024;
|
|
12446
|
+
var CLOCK_SKEW_SECONDS = 60;
|
|
12447
|
+
var JWKS_TTL_MS = 10 * 60 * 1e3;
|
|
12448
|
+
var jwksCache = /* @__PURE__ */ new Map();
|
|
12449
|
+
function decodeSegment(segment) {
|
|
12450
|
+
const json = Buffer.from(segment, "base64url").toString("utf8");
|
|
12451
|
+
let value;
|
|
12183
12452
|
try {
|
|
12184
|
-
|
|
12185
|
-
return isRecord3(parsed) ? parsed : { prompt: content };
|
|
12453
|
+
value = JSON.parse(json);
|
|
12186
12454
|
} catch {
|
|
12187
|
-
|
|
12455
|
+
throw new WebAppTokenError("Malformed token segment");
|
|
12188
12456
|
}
|
|
12457
|
+
if (typeof value !== "object" || value === null) throw new WebAppTokenError("Malformed token segment");
|
|
12458
|
+
return value;
|
|
12189
12459
|
}
|
|
12190
|
-
function
|
|
12191
|
-
|
|
12460
|
+
async function resolveKey(issuer, kid, fetchImpl) {
|
|
12461
|
+
const cacheKey = issuer.jwksUrl ?? "";
|
|
12462
|
+
const load = async () => {
|
|
12463
|
+
const response = await fetchImpl(issuer.jwksUrl ?? "");
|
|
12464
|
+
if (!response.ok) throw new WebAppTokenError("JWKS fetch failed");
|
|
12465
|
+
const document = await response.json();
|
|
12466
|
+
const keys2 = document.keys;
|
|
12467
|
+
const entry2 = { keys: Array.isArray(keys2) ? keys2 : [], fetchedAt: Date.now() };
|
|
12468
|
+
jwksCache.set(cacheKey, entry2);
|
|
12469
|
+
return entry2;
|
|
12470
|
+
};
|
|
12471
|
+
const cached = jwksCache.get(cacheKey);
|
|
12472
|
+
let entry = cached && Date.now() - cached.fetchedAt < JWKS_TTL_MS ? cached : await load();
|
|
12473
|
+
let jwk = entry.keys.find((key) => kid === void 0 ? true : key.kid === kid);
|
|
12474
|
+
if (!jwk && kid !== void 0) {
|
|
12475
|
+
entry = await load();
|
|
12476
|
+
jwk = entry.keys.find((key) => key.kid === kid);
|
|
12477
|
+
}
|
|
12478
|
+
if (!jwk) throw new WebAppTokenError("No matching JWKS key");
|
|
12479
|
+
return createPublicKey({ key: jwk, format: "jwk" });
|
|
12480
|
+
}
|
|
12481
|
+
async function verifyWebAppToken(token, issuer, options = {}) {
|
|
12482
|
+
const nowMs = options.now ? options.now() : Date.now();
|
|
12483
|
+
const now = Math.floor(nowMs / 1e3);
|
|
12484
|
+
if (token.length === 0 || Buffer.byteLength(token) > MAX_TOKEN_BYTES) throw new WebAppTokenError("Token size invalid");
|
|
12485
|
+
const parts = token.split(".");
|
|
12486
|
+
if (parts.length !== 3) throw new WebAppTokenError("Token structure invalid");
|
|
12487
|
+
const [rawHeader, rawPayload, rawSignature] = parts;
|
|
12488
|
+
const header2 = decodeSegment(rawHeader);
|
|
12489
|
+
const payload = decodeSegment(rawPayload);
|
|
12490
|
+
if (header2.alg !== issuer.alg) throw new WebAppTokenError("Token algorithm mismatch");
|
|
12491
|
+
if (payload.iss !== issuer.iss) throw new WebAppTokenError("Token issuer mismatch");
|
|
12492
|
+
if (payload.aud !== issuer.aud) throw new WebAppTokenError("Token audience mismatch");
|
|
12493
|
+
const exp = typeof payload.exp === "number" ? payload.exp : void 0;
|
|
12494
|
+
if (exp === void 0 || exp + CLOCK_SKEW_SECONDS < now) throw new WebAppTokenError("Token expired");
|
|
12495
|
+
const nbf = typeof payload.nbf === "number" ? payload.nbf : void 0;
|
|
12496
|
+
if (nbf !== void 0 && nbf - CLOCK_SKEW_SECONDS > now) throw new WebAppTokenError("Token not yet valid");
|
|
12497
|
+
const signature = Buffer.from(rawSignature, "base64url");
|
|
12498
|
+
const data = Buffer.from(`${rawHeader}.${rawPayload}`);
|
|
12499
|
+
if (issuer.alg === "HS256") {
|
|
12500
|
+
if (typeof issuer.secret !== "string" || issuer.secret.length === 0) throw new WebAppTokenError("Issuer secret missing");
|
|
12501
|
+
const expected = createHmac2("sha256", issuer.secret).update(data).digest();
|
|
12502
|
+
if (signature.length !== expected.length || !timingSafeEqual2(signature, expected)) {
|
|
12503
|
+
throw new WebAppTokenError("Token signature invalid");
|
|
12504
|
+
}
|
|
12505
|
+
} else {
|
|
12506
|
+
if (typeof issuer.jwksUrl !== "string") throw new WebAppTokenError("Issuer JWKS URL missing");
|
|
12507
|
+
const key = await resolveKey(issuer, typeof header2.kid === "string" ? header2.kid : void 0, options.fetchImpl ?? fetch);
|
|
12508
|
+
let valid = false;
|
|
12509
|
+
try {
|
|
12510
|
+
valid = cryptoVerify("RSA-SHA256", data, key, signature);
|
|
12511
|
+
} catch {
|
|
12512
|
+
valid = false;
|
|
12513
|
+
}
|
|
12514
|
+
if (!valid) throw new WebAppTokenError("Token signature invalid");
|
|
12515
|
+
}
|
|
12516
|
+
const sub = payload.sub;
|
|
12517
|
+
if (typeof sub !== "string" || sub.length === 0 || sub.length > 128 || !/^[A-Za-z0-9._:@-]+$/.test(sub)) {
|
|
12518
|
+
throw new WebAppTokenError("Token subject invalid");
|
|
12519
|
+
}
|
|
12520
|
+
return sub;
|
|
12192
12521
|
}
|
|
12193
12522
|
|
|
12194
12523
|
// src/routes/agent-web-app-runtime.ts
|
|
12524
|
+
import { createAgentWebAppStreamProjectionContext, projectAgentWebAppChunk } from "@axiom-lattice/protocols";
|
|
12195
12525
|
var idSchema = z5.string().min(1).max(128);
|
|
12196
12526
|
var paramsSchema = z5.object({ webAppId: idSchema }).strict();
|
|
12197
12527
|
var threadParamsSchema = z5.object({ webAppId: idSchema, threadId: idSchema }).strict();
|
|
12198
12528
|
var interruptParamsSchema = z5.object({ webAppId: idSchema, threadId: idSchema, interruptId: idSchema }).strict();
|
|
12529
|
+
var pendingMessageParamsSchema = z5.object({ webAppId: idSchema, threadId: idSchema, messageId: idSchema }).strict();
|
|
12199
12530
|
var bootstrapQuerySchema = z5.object({ projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
|
|
12200
12531
|
var projectQuerySchema = z5.object({ projectId: idSchema.optional() }).strict();
|
|
12532
|
+
var projectQueryLenientSchema = projectQuerySchema.passthrough();
|
|
12201
12533
|
var threadBodySchema = z5.object({ projectId: idSchema.optional(), label: z5.string().max(128).optional() }).strict();
|
|
12202
12534
|
var streamBodySchema = z5.object({
|
|
12203
12535
|
content: z5.string().trim().min(1).max(32768),
|
|
@@ -12216,8 +12548,27 @@ var resumeBodySchema = z5.object({
|
|
|
12216
12548
|
])
|
|
12217
12549
|
}).strict();
|
|
12218
12550
|
var MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
|
|
12551
|
+
var stateQuerySchema = z5.object({
|
|
12552
|
+
threadId: idSchema,
|
|
12553
|
+
projectId: idSchema.optional(),
|
|
12554
|
+
modelKey: idSchema.optional()
|
|
12555
|
+
}).strict();
|
|
12556
|
+
var resumeSchema = z5.object({
|
|
12557
|
+
messageId: idSchema,
|
|
12558
|
+
projectId: idSchema.optional(),
|
|
12559
|
+
modelKey: idSchema.optional()
|
|
12560
|
+
}).strict();
|
|
12561
|
+
var devTokenSchema = z5.object({
|
|
12562
|
+
iss: z5.string().min(1).max(255).optional(),
|
|
12563
|
+
sub: z5.string().regex(/^[A-Za-z0-9._:@-]+$/).max(128).optional(),
|
|
12564
|
+
hours: z5.number().gt(0).max(720).optional()
|
|
12565
|
+
}).strict();
|
|
12566
|
+
var fileQuerySchema = z5.object({
|
|
12567
|
+
projectId: idSchema.optional(),
|
|
12568
|
+
modelKey: idSchema.optional()
|
|
12569
|
+
}).passthrough();
|
|
12219
12570
|
function createExternalHeaderIdentityResolver() {
|
|
12220
|
-
return { resolve(request) {
|
|
12571
|
+
return { async resolve(request) {
|
|
12221
12572
|
const raw = request.headers["x-axiom-external-user-id"];
|
|
12222
12573
|
if (raw === void 0) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "External user id is required");
|
|
12223
12574
|
if (Array.isArray(raw) || typeof raw !== "string") throw new AgentWebAppRuntimeError("INVALID_USER_ID", 400, "External user id is invalid");
|
|
@@ -12226,47 +12577,109 @@ function createExternalHeaderIdentityResolver() {
|
|
|
12226
12577
|
return { userId, assurance: "unverified" };
|
|
12227
12578
|
} };
|
|
12228
12579
|
}
|
|
12580
|
+
function createWebAppIdentityResolver(deps) {
|
|
12581
|
+
const headerResolver = createExternalHeaderIdentityResolver();
|
|
12582
|
+
return {
|
|
12583
|
+
async resolve(request, webAppId) {
|
|
12584
|
+
let config;
|
|
12585
|
+
try {
|
|
12586
|
+
config = await deps.getIdentityConfig(webAppId);
|
|
12587
|
+
} catch {
|
|
12588
|
+
config = void 0;
|
|
12589
|
+
}
|
|
12590
|
+
const policy = config?.policy ?? "unverified";
|
|
12591
|
+
const authHeader = request.headers.authorization;
|
|
12592
|
+
const bearer = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : void 0;
|
|
12593
|
+
if (policy !== "unverified" && bearer !== void 0) {
|
|
12594
|
+
const issuers = config?.issuers ?? [];
|
|
12595
|
+
if (issuers.length === 0) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 401, "No trusted issuer configured");
|
|
12596
|
+
let lastError = new AgentWebAppRuntimeError("INVALID_REQUEST", 401, "Invalid identity token");
|
|
12597
|
+
for (const issuer of issuers) {
|
|
12598
|
+
try {
|
|
12599
|
+
const sub = await verifyWebAppToken(bearer, issuer);
|
|
12600
|
+
return { userId: sub, assurance: "verified" };
|
|
12601
|
+
} catch (error) {
|
|
12602
|
+
if (error instanceof WebAppTokenError) {
|
|
12603
|
+
lastError = error;
|
|
12604
|
+
continue;
|
|
12605
|
+
}
|
|
12606
|
+
throw error;
|
|
12607
|
+
}
|
|
12608
|
+
}
|
|
12609
|
+
const message = lastError instanceof WebAppTokenError ? `Invalid identity token: ${lastError.message}` : "Invalid identity token";
|
|
12610
|
+
throw new AgentWebAppRuntimeError("INVALID_REQUEST", 401, message);
|
|
12611
|
+
}
|
|
12612
|
+
if (policy === "verified") throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "A verified identity token is required");
|
|
12613
|
+
return headerResolver.resolve(request, webAppId);
|
|
12614
|
+
}
|
|
12615
|
+
};
|
|
12616
|
+
}
|
|
12229
12617
|
function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
12230
|
-
const identities = deps.identityResolver ??
|
|
12618
|
+
const identities = deps.identityResolver ?? createWebAppIdentityResolver({ getIdentityConfig: (webAppId) => deps.service.getIdentityConfig(webAppId) });
|
|
12231
12619
|
app2.register(async (routes) => {
|
|
12232
12620
|
routes.setErrorHandler((error, _request, reply) => normalizeError2(reply, error));
|
|
12233
12621
|
routes.get("/api/web-apps/:webAppId/runtime/bootstrap", async (request, reply) => handle2(reply, async () => {
|
|
12234
12622
|
const { webAppId } = paramsSchema.parse(request.params);
|
|
12235
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12236
|
-
return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview)) };
|
|
12623
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12624
|
+
return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview), { assurance: identity.assurance ?? "unverified" }) };
|
|
12237
12625
|
}));
|
|
12238
12626
|
routes.get("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
|
|
12239
12627
|
const { webAppId } = paramsSchema.parse(request.params);
|
|
12240
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12628
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12241
12629
|
const records = await deps.service.listThreads(webAppId, identity.userId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
12242
12630
|
return { data: { records, total: records.length } };
|
|
12243
12631
|
}));
|
|
12244
12632
|
routes.post("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
|
|
12245
12633
|
const { webAppId } = paramsSchema.parse(request.params);
|
|
12246
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12634
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12247
12635
|
return { statusCode: 201, data: await deps.service.createThread(webAppId, identity.userId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
|
|
12248
12636
|
}));
|
|
12249
12637
|
routes.patch("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
|
|
12250
12638
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12251
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12639
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12252
12640
|
return { data: await deps.service.updateThread(webAppId, identity.userId, threadId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
|
|
12253
12641
|
}));
|
|
12254
12642
|
routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
|
|
12255
12643
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12256
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12644
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12257
12645
|
await deps.service.deleteThread(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
12258
12646
|
return { data: { id: threadId } };
|
|
12259
12647
|
}));
|
|
12260
12648
|
routes.get("/api/web-apps/:webAppId/runtime/threads/:threadId/messages", async (request, reply) => handle2(reply, async () => {
|
|
12261
12649
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12262
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12650
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12263
12651
|
const messages = await deps.service.getMessages(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
12264
12652
|
return { data: { messages } };
|
|
12265
12653
|
}));
|
|
12654
|
+
routes.get("/api/web-apps/:webAppId/runtime/state", async (request, reply) => handle2(reply, async () => {
|
|
12655
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12656
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12657
|
+
const { threadId, projectId, modelKey: modelKey2 } = stateQuerySchema.parse(request.query);
|
|
12658
|
+
const state = await deps.service.getState(webAppId, identity.userId, threadId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
|
|
12659
|
+
return { data: state };
|
|
12660
|
+
}));
|
|
12661
|
+
routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId/pending-messages/:messageId", async (request, reply) => handle2(reply, async () => {
|
|
12662
|
+
const { webAppId, threadId, messageId } = pendingMessageParamsSchema.parse(request.params);
|
|
12663
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12664
|
+
await deps.service.removePendingMessage(webAppId, identity.userId, threadId, messageId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
12665
|
+
return { data: { removed: true } };
|
|
12666
|
+
}));
|
|
12266
12667
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/messages/stream", async (request, reply) => {
|
|
12267
12668
|
try {
|
|
12268
12669
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12269
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12670
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12671
|
+
const input = streamBodySchema.parse(request.body);
|
|
12672
|
+
const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
|
|
12673
|
+
beginSse(reply);
|
|
12674
|
+
await pipeNativeWebAppStream(reply.raw, result.stream);
|
|
12675
|
+
} catch (error) {
|
|
12676
|
+
return normalizeError2(reply, error);
|
|
12677
|
+
}
|
|
12678
|
+
});
|
|
12679
|
+
routes.post("/api/web-apps/:webAppId/runtime/v1/threads/:threadId/messages/stream", async (request, reply) => {
|
|
12680
|
+
try {
|
|
12681
|
+
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12682
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12270
12683
|
const input = streamBodySchema.parse(request.body);
|
|
12271
12684
|
const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
|
|
12272
12685
|
beginSse(reply);
|
|
@@ -12275,15 +12688,27 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
|
12275
12688
|
return normalizeError2(reply, error);
|
|
12276
12689
|
}
|
|
12277
12690
|
});
|
|
12691
|
+
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/resume", async (request, reply) => {
|
|
12692
|
+
try {
|
|
12693
|
+
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12694
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12695
|
+
const { messageId, projectId, modelKey: modelKey2 } = resumeSchema.parse(request.body);
|
|
12696
|
+
const result = await deps.service.resumeStream(webAppId, identity.userId, threadId, messageId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
|
|
12697
|
+
beginSse(reply);
|
|
12698
|
+
await pipeNativeWebAppStream(reply.raw, result.stream);
|
|
12699
|
+
} catch (error) {
|
|
12700
|
+
return normalizeError2(reply, error);
|
|
12701
|
+
}
|
|
12702
|
+
});
|
|
12278
12703
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/abort", async (request, reply) => handle2(reply, async () => {
|
|
12279
12704
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12280
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12705
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12281
12706
|
await deps.service.abort(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
12282
12707
|
return { data: { aborted: true } };
|
|
12283
12708
|
}));
|
|
12284
12709
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/attachments", async (request, reply) => handle2(reply, async () => {
|
|
12285
12710
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12286
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12711
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12287
12712
|
if (!request.isMultipart()) throw new ZodError2([]);
|
|
12288
12713
|
const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
|
|
12289
12714
|
if (!part || !part.filename || part.filename.length > 255) throw new ZodError2([]);
|
|
@@ -12299,7 +12724,7 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
|
12299
12724
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/interrupts/:interruptId/resume", async (request, reply) => {
|
|
12300
12725
|
try {
|
|
12301
12726
|
const { webAppId, threadId, interruptId } = interruptParamsSchema.parse(request.params);
|
|
12302
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12727
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12303
12728
|
const { projectId, modelKey: modelKey2, message, response } = resumeBodySchema.parse(request.body);
|
|
12304
12729
|
const result = await deps.service.resumeInterrupt(webAppId, identity.userId, threadId, interruptId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview), { message, response });
|
|
12305
12730
|
beginSse(reply);
|
|
@@ -12308,13 +12733,257 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
|
12308
12733
|
return normalizeError2(reply, error);
|
|
12309
12734
|
}
|
|
12310
12735
|
});
|
|
12736
|
+
routes.post("/api/web-apps/:webAppId/dev/token", async (request, reply) => handle2(reply, async () => {
|
|
12737
|
+
if (process.env.WEB_APP_DEV_TOKEN_ENABLED !== "true") {
|
|
12738
|
+
throw new AgentWebAppRuntimeError("FEATURE_DISABLED", 403, "Dev token issuance is disabled (set WEB_APP_DEV_TOKEN_ENABLED=true)");
|
|
12739
|
+
}
|
|
12740
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12741
|
+
const { iss, sub, hours } = devTokenSchema.parse(request.body ?? {});
|
|
12742
|
+
const config = await deps.service.getIdentityConfig(webAppId);
|
|
12743
|
+
const issuer = (config?.issuers ?? []).find((entry) => entry.alg === "HS256" && (iss === void 0 || entry.iss === iss));
|
|
12744
|
+
if (!issuer || typeof issuer.secret !== "string") {
|
|
12745
|
+
throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "Web app has no HS256 issuer configured");
|
|
12746
|
+
}
|
|
12747
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
12748
|
+
const expiresIn = (hours ?? 24) * 3600;
|
|
12749
|
+
const subject = sub ?? "dev-user";
|
|
12750
|
+
const b64url = (input) => Buffer.from(input).toString("base64url");
|
|
12751
|
+
const body = `${b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }))}.${b64url(JSON.stringify({ sub: subject, iss: issuer.iss, aud: issuer.aud, iat: now, exp: now + expiresIn }))}`;
|
|
12752
|
+
const token = `${body}.${createHmac3("sha256", issuer.secret).update(body).digest("base64url")}`;
|
|
12753
|
+
return { data: { token, tokenType: "Bearer", expiresIn, subject } };
|
|
12754
|
+
}));
|
|
12755
|
+
if (deps.taskController) {
|
|
12756
|
+
const tasks = deps.taskController;
|
|
12757
|
+
const withSubject = (handler) => async (request, reply) => {
|
|
12758
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12759
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12760
|
+
const proxy = Object.create(request);
|
|
12761
|
+
proxy.headers = { ...request.headers, "x-user-id": identity.userId };
|
|
12762
|
+
return handler(proxy, reply);
|
|
12763
|
+
};
|
|
12764
|
+
routes.get("/api/web-apps/:webAppId/runtime/tasks", withSubject((request, reply) => tasks.list(request, reply)));
|
|
12765
|
+
routes.post("/api/web-apps/:webAppId/runtime/tasks", withSubject((request, reply) => tasks.create(request, reply)));
|
|
12766
|
+
routes.get("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.get(request, reply)));
|
|
12767
|
+
routes.put("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.update(request, reply)));
|
|
12768
|
+
routes.patch("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.update(request, reply)));
|
|
12769
|
+
routes.delete("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.remove(request, reply)));
|
|
12770
|
+
routes.patch("/api/web-apps/:webAppId/runtime/tasks/:taskId/complete", withSubject((request, reply) => tasks.complete(request, reply)));
|
|
12771
|
+
routes.get("/api/web-apps/:webAppId/runtime/tasks/:taskId/work-items", withSubject((request, reply) => tasks.workItems(request, reply)));
|
|
12772
|
+
}
|
|
12773
|
+
if (deps.workspaceController) {
|
|
12774
|
+
const workspace = deps.workspaceController;
|
|
12775
|
+
const fileScope = async (webAppId, selection) => {
|
|
12776
|
+
const scope = await deps.service.resolveFileScope(webAppId, selection);
|
|
12777
|
+
if (!scope.enabled) throw new AgentWebAppRuntimeError("FEATURE_DISABLED", 403, "Files are disabled for this web app");
|
|
12778
|
+
return scope;
|
|
12779
|
+
};
|
|
12780
|
+
const scopedRequest = (request, scope, params, query) => {
|
|
12781
|
+
const proxy = Object.create(request);
|
|
12782
|
+
proxy.params = params;
|
|
12783
|
+
proxy.query = query;
|
|
12784
|
+
proxy.headers = { ...request.headers, "x-tenant-id": scope.tenantId };
|
|
12785
|
+
return proxy;
|
|
12786
|
+
};
|
|
12787
|
+
const unwrap = (result) => ({ data: result.data });
|
|
12788
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/listpath", async (request) => {
|
|
12789
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12790
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12791
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12792
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12793
|
+
const result = await workspace.listPath(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query));
|
|
12794
|
+
return unwrap(result);
|
|
12795
|
+
});
|
|
12796
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/readfile", async (request) => {
|
|
12797
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12798
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12799
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12800
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12801
|
+
const result = await workspace.readFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query));
|
|
12802
|
+
return unwrap(result);
|
|
12803
|
+
});
|
|
12804
|
+
routes.post("/api/web-apps/:webAppId/runtime/files/uploadfile", async (request, reply) => {
|
|
12805
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12806
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12807
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12808
|
+
const scoped = scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, { assistantId: scope.assistantId });
|
|
12809
|
+
await workspace.uploadFile(scoped, reply);
|
|
12810
|
+
return reply;
|
|
12811
|
+
});
|
|
12812
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/viewfile", async (request, reply) => {
|
|
12813
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12814
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12815
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12816
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12817
|
+
await workspace.viewFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
12818
|
+
return reply;
|
|
12819
|
+
});
|
|
12820
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/downloadfile", async (request, reply) => {
|
|
12821
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12822
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12823
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12824
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12825
|
+
await workspace.downloadFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
12826
|
+
return reply;
|
|
12827
|
+
});
|
|
12828
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/downloadfolder", async (request, reply) => {
|
|
12829
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12830
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12831
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12832
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12833
|
+
await workspace.downloadFolder(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
12834
|
+
return reply;
|
|
12835
|
+
});
|
|
12836
|
+
const genuiSandboxParams = z5.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema }).strict();
|
|
12837
|
+
const delegateGenuiFile = (method) => async (request, reply) => {
|
|
12838
|
+
const { webAppId } = genuiSandboxParams.parse(request.params);
|
|
12839
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12840
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12841
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12842
|
+
await method(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
12843
|
+
return reply;
|
|
12844
|
+
};
|
|
12845
|
+
routes.get("/api/web-apps/:webAppId/runtime/assistants/:assistantId/threads/:threadId/sandbox/downloadfile", delegateGenuiFile(workspace.downloadFile.bind(workspace)));
|
|
12846
|
+
routes.get("/api/web-apps/:webAppId/runtime/assistants/:assistantId/threads/:threadId/sandbox/viewfile", delegateGenuiFile(workspace.viewFile.bind(workspace)));
|
|
12847
|
+
}
|
|
12848
|
+
const mirrorThreadParams = z5.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema }).strict();
|
|
12849
|
+
const mirrorBody = z5.object({ metadata: z5.object({ label: z5.string().max(300).optional() }).strict().optional(), projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
|
|
12850
|
+
const toConsoleThread = (thread, assistantId) => ({
|
|
12851
|
+
id: thread.id,
|
|
12852
|
+
assistantId,
|
|
12853
|
+
metadata: thread.label ? { label: thread.label } : {},
|
|
12854
|
+
createdAt: thread.createdAt,
|
|
12855
|
+
updatedAt: thread.updatedAt
|
|
12856
|
+
});
|
|
12857
|
+
const mirrorAssistantParams = z5.object({ webAppId: idSchema, assistantId: idSchema }).strict();
|
|
12858
|
+
const mirrorPendingParams = z5.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema, messageId: idSchema }).strict();
|
|
12859
|
+
const registerMirror = (prefix) => {
|
|
12860
|
+
routes.get(`${prefix}/assistants/:assistantId/threads`, async (request, reply) => handle2(reply, async () => {
|
|
12861
|
+
const { webAppId, assistantId } = mirrorAssistantParams.parse(request.params);
|
|
12862
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12863
|
+
const threads = await deps.service.listThreads(webAppId, identity.userId, projectQueryLenientSchema.parse(request.query));
|
|
12864
|
+
return { data: { records: threads.map((thread) => toConsoleThread(thread, assistantId)) } };
|
|
12865
|
+
}));
|
|
12866
|
+
routes.post(`${prefix}/assistants/:assistantId/threads`, async (request, reply) => handle2(reply, async () => {
|
|
12867
|
+
const { webAppId, assistantId } = mirrorAssistantParams.parse(request.params);
|
|
12868
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12869
|
+
const body = mirrorBody.parse(request.body ?? {});
|
|
12870
|
+
const created = await deps.service.createThread(webAppId, identity.userId, { label: body.metadata?.label, ...body.projectId ? { projectId: body.projectId } : {} });
|
|
12871
|
+
return { statusCode: 201, data: { id: created.id } };
|
|
12872
|
+
}));
|
|
12873
|
+
routes.get(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
|
|
12874
|
+
const { webAppId, assistantId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12875
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12876
|
+
const thread = await deps.service.getThread(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
12877
|
+
return { data: toConsoleThread(thread, assistantId) };
|
|
12878
|
+
}));
|
|
12879
|
+
routes.put(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
|
|
12880
|
+
const { webAppId, assistantId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12881
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12882
|
+
const body = mirrorBody.parse(request.body ?? {});
|
|
12883
|
+
const updated = await deps.service.updateThread(webAppId, identity.userId, threadId, { label: body.metadata?.label, ...body.projectId ? { projectId: body.projectId } : {} });
|
|
12884
|
+
return { data: toConsoleThread(updated, assistantId) };
|
|
12885
|
+
}));
|
|
12886
|
+
routes.delete(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
|
|
12887
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12888
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12889
|
+
await deps.service.deleteThread(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
12890
|
+
return { data: { success: true, message: "Thread deleted" } };
|
|
12891
|
+
}));
|
|
12892
|
+
routes.post(`${prefix}/assistants/:assistantId/threads/:threadId/sandbox/uploadfile`, async (request, reply) => handle2(reply, async () => {
|
|
12893
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12894
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12895
|
+
if (!request.isMultipart()) throw new ZodError2([]);
|
|
12896
|
+
const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
|
|
12897
|
+
if (!part || !part.filename || part.filename.length > 255) throw new ZodError2([]);
|
|
12898
|
+
const bytes = await part.toBuffer();
|
|
12899
|
+
if (bytes.length === 0 || part.file.truncated) throw new ZodError2([]);
|
|
12900
|
+
const uploaded = await deps.service.uploadAttachment(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query), {
|
|
12901
|
+
name: part.filename,
|
|
12902
|
+
mimeType: part.mimetype || "application/octet-stream",
|
|
12903
|
+
bytes
|
|
12904
|
+
});
|
|
12905
|
+
return { statusCode: 201, data: { id: uploaded.ref, path: uploaded.ref, name: uploaded.name, mimeType: uploaded.mimeType, size: uploaded.size } };
|
|
12906
|
+
}));
|
|
12907
|
+
routes.get(`${prefix}/assistants/:assistantId/:threadId/memory`, async (request) => {
|
|
12908
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12909
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12910
|
+
const messages = await deps.service.getConsoleMessages(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
12911
|
+
return messages;
|
|
12912
|
+
});
|
|
12913
|
+
routes.get(`${prefix}/assistants/:assistantId/:threadId/state`, async (request) => {
|
|
12914
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12915
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12916
|
+
return deps.service.getState(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
12917
|
+
});
|
|
12918
|
+
routes.delete(`${prefix}/assistants/:assistantId/threads/:threadId/pending-messages/:messageId`, async (request, reply) => handle2(reply, async () => {
|
|
12919
|
+
const { webAppId, threadId, messageId } = mirrorPendingParams.parse(request.params);
|
|
12920
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12921
|
+
await deps.service.removePendingMessage(webAppId, identity.userId, threadId, messageId, projectQueryLenientSchema.parse(request.query));
|
|
12922
|
+
return { data: { removed: true } };
|
|
12923
|
+
}));
|
|
12924
|
+
routes.post(`${prefix}/assistants/:assistantId/threads/:threadId/abort`, async (request, reply) => handle2(reply, async () => {
|
|
12925
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12926
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12927
|
+
await deps.service.abort(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
12928
|
+
return { data: { aborted: true } };
|
|
12929
|
+
}));
|
|
12930
|
+
const runsBodySchema = z5.object({
|
|
12931
|
+
thread_id: idSchema.optional(),
|
|
12932
|
+
assistant_id: idSchema.optional(),
|
|
12933
|
+
messages: z5.array(z5.object({ content: z5.string().min(1).max(32768) }).passthrough()).min(1).max(20),
|
|
12934
|
+
background: z5.boolean().optional(),
|
|
12935
|
+
streaming: z5.boolean().optional(),
|
|
12936
|
+
projectId: idSchema.optional(),
|
|
12937
|
+
modelKey: idSchema.optional()
|
|
12938
|
+
}).passthrough();
|
|
12939
|
+
routes.post(`${prefix}/runs`, async (request, reply) => {
|
|
12940
|
+
try {
|
|
12941
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12942
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12943
|
+
const body = runsBodySchema.parse(request.body ?? {});
|
|
12944
|
+
if (!body.background || body.streaming) {
|
|
12945
|
+
throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "Mirror runs supports only background enqueue (stream via resume_stream)");
|
|
12946
|
+
}
|
|
12947
|
+
const threadId = body.thread_id ?? "";
|
|
12948
|
+
if (!threadId) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "thread_id is required");
|
|
12949
|
+
const content = body.messages.map((message) => message.content).join("\n");
|
|
12950
|
+
const { messageId } = await deps.service.enqueueMessage(webAppId, identity.userId, threadId, content, { projectId: body.projectId, modelKey: body.modelKey });
|
|
12951
|
+
return reply.code(202).send({ success: true, messageId, queued: true });
|
|
12952
|
+
} catch (error) {
|
|
12953
|
+
return normalizeError2(reply, error);
|
|
12954
|
+
}
|
|
12955
|
+
});
|
|
12956
|
+
const resumeStreamSchema2 = z5.object({
|
|
12957
|
+
assistant_id: idSchema.optional(),
|
|
12958
|
+
thread_id: idSchema,
|
|
12959
|
+
message_id: idSchema,
|
|
12960
|
+
known_content: z5.string().optional(),
|
|
12961
|
+
poll_interval: z5.number().optional(),
|
|
12962
|
+
projectId: idSchema.optional(),
|
|
12963
|
+
modelKey: idSchema.optional()
|
|
12964
|
+
}).strict();
|
|
12965
|
+
routes.post(`${prefix}/resume_stream`, async (request, reply) => {
|
|
12966
|
+
try {
|
|
12967
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12968
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12969
|
+
const body = resumeStreamSchema2.parse(request.body ?? {});
|
|
12970
|
+
const result = await deps.service.resumeStream(webAppId, identity.userId, body.thread_id, body.message_id, { projectId: body.projectId, modelKey: body.modelKey });
|
|
12971
|
+
beginSse(reply);
|
|
12972
|
+
await pipeNativeWebAppStream(reply.raw, result.stream);
|
|
12973
|
+
} catch (error) {
|
|
12974
|
+
return normalizeError2(reply, error);
|
|
12975
|
+
}
|
|
12976
|
+
});
|
|
12977
|
+
};
|
|
12978
|
+
registerMirror("/api/web-apps/:webAppId/runtime");
|
|
12979
|
+
registerMirror("/api/web-apps/:webAppId/runtime/api");
|
|
12311
12980
|
});
|
|
12312
12981
|
}
|
|
12313
|
-
function resolveRuntimeIdentity(request, webAppId, identities) {
|
|
12314
|
-
if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request);
|
|
12982
|
+
async function resolveRuntimeIdentity(request, webAppId, identities) {
|
|
12983
|
+
if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request, webAppId);
|
|
12315
12984
|
const user = request.user;
|
|
12316
12985
|
if (!user?.id || !user.tenantId) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "Authenticated Console preview is required");
|
|
12317
|
-
return { userId: `console-preview:${user.id}:${webAppId}`, preview: { tenantId: user.tenantId, userId: user.id } };
|
|
12986
|
+
return { userId: `console-preview:${user.id}:${webAppId}`, assurance: "unverified", preview: { tenantId: user.tenantId, userId: user.id } };
|
|
12318
12987
|
}
|
|
12319
12988
|
function withPreview(selection, preview) {
|
|
12320
12989
|
return { ...selection, ...preview ? { preview } : {} };
|
|
@@ -12329,13 +12998,22 @@ async function pipeAgentWebAppStream(raw, stream, options = {}) {
|
|
|
12329
12998
|
]
|
|
12330
12999
|
});
|
|
12331
13000
|
}
|
|
13001
|
+
async function pipeNativeWebAppStream(raw, stream) {
|
|
13002
|
+
await pipeAsyncIterableToSse(raw, stream, {
|
|
13003
|
+
onError: () => [{
|
|
13004
|
+
type: "error",
|
|
13005
|
+
data: { id: "", content: "Stream failed" }
|
|
13006
|
+
}]
|
|
13007
|
+
});
|
|
13008
|
+
}
|
|
12332
13009
|
function beginSse(reply) {
|
|
12333
13010
|
reply.hijack();
|
|
12334
13011
|
reply.raw.writeHead(200, {
|
|
12335
13012
|
"Content-Type": "text/event-stream; charset=utf-8",
|
|
12336
13013
|
"Cache-Control": "no-cache, no-transform",
|
|
12337
13014
|
Connection: "keep-alive",
|
|
12338
|
-
"X-Accel-Buffering": "no"
|
|
13015
|
+
"X-Accel-Buffering": "no",
|
|
13016
|
+
"Access-Control-Allow-Origin": "*"
|
|
12339
13017
|
});
|
|
12340
13018
|
}
|
|
12341
13019
|
async function handle2(reply, operation) {
|
|
@@ -12357,7 +13035,7 @@ function isMultipartRequestError(error) {
|
|
|
12357
13035
|
}
|
|
12358
13036
|
|
|
12359
13037
|
// src/services/agent-web-app/AgentWebAppAttachmentRef.ts
|
|
12360
|
-
import { createCipheriv, createDecipheriv, createHash as
|
|
13038
|
+
import { createCipheriv, createDecipheriv, createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
12361
13039
|
var TTL_MS = 15 * 6e4;
|
|
12362
13040
|
function resolveAgentWebAppAttachmentSecret(consoleSecret) {
|
|
12363
13041
|
return process.env.WEB_APP_ATTACHMENT_SECRET || process.env.AUTH_TOKEN_SECRET || process.env.JWT_SECRET || consoleSecret;
|
|
@@ -12366,7 +13044,7 @@ var AgentWebAppAttachmentRefService = class {
|
|
|
12366
13044
|
constructor(secret, now = Date.now) {
|
|
12367
13045
|
this.now = now;
|
|
12368
13046
|
if (!secret) throw new Error("Attachment signing secret is required");
|
|
12369
|
-
this.key =
|
|
13047
|
+
this.key = createHash3("sha256").update(secret, "utf8").digest();
|
|
12370
13048
|
}
|
|
12371
13049
|
sign(input) {
|
|
12372
13050
|
const payload = {
|
|
@@ -12428,7 +13106,7 @@ function decodeCanonical(value, expectedBytes) {
|
|
|
12428
13106
|
return decoded;
|
|
12429
13107
|
}
|
|
12430
13108
|
function digestUser(userId) {
|
|
12431
|
-
return
|
|
13109
|
+
return createHash3("sha256").update(userId, "utf8").digest("hex");
|
|
12432
13110
|
}
|
|
12433
13111
|
function isAttachmentPayload(value) {
|
|
12434
13112
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
@@ -12788,12 +13466,12 @@ function registerCapabilityBundleRoutes(app2, controller) {
|
|
|
12788
13466
|
}
|
|
12789
13467
|
|
|
12790
13468
|
// src/services/collaboration/ProjectRoomService.ts
|
|
12791
|
-
import { randomUUID as
|
|
13469
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
12792
13470
|
var ProjectRoomService = class {
|
|
12793
13471
|
/** Creates a project room service from explicitly injected collaborators. */
|
|
12794
13472
|
constructor(deps) {
|
|
12795
13473
|
this.deps = deps;
|
|
12796
|
-
this.idFactory = deps.idFactory ??
|
|
13474
|
+
this.idFactory = deps.idFactory ?? randomUUID12;
|
|
12797
13475
|
}
|
|
12798
13476
|
/** Authorizes read access and atomically ensures the project's main room. */
|
|
12799
13477
|
async ensureMainRoom(input) {
|
|
@@ -12812,7 +13490,7 @@ var ProjectRoomService = class {
|
|
|
12812
13490
|
};
|
|
12813
13491
|
|
|
12814
13492
|
// src/services/collaboration/ProjectMembershipService.ts
|
|
12815
|
-
import { randomUUID as
|
|
13493
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
12816
13494
|
var ProjectMembershipServiceError = class extends Error {
|
|
12817
13495
|
constructor(code, statusCode, message) {
|
|
12818
13496
|
super(message);
|
|
@@ -12828,11 +13506,13 @@ function isMembershipConflict(error) {
|
|
|
12828
13506
|
var ProjectMembershipService = class {
|
|
12829
13507
|
constructor(deps) {
|
|
12830
13508
|
this.deps = deps;
|
|
12831
|
-
this.idFactory = deps.idFactory ??
|
|
13509
|
+
this.idFactory = deps.idFactory ?? randomUUID13;
|
|
12832
13510
|
this.mutationMutex = deps.projectMutationMutex ?? projectMutationMutex;
|
|
12833
13511
|
}
|
|
12834
13512
|
async list(actor2) {
|
|
12835
|
-
await this.
|
|
13513
|
+
await this.deps.accessService.requireRosterAccess(actor2);
|
|
13514
|
+
const membership = await this.deps.membershipStore.findByUser(actor2.tenantId, actor2.projectId, actor2.userId);
|
|
13515
|
+
if (!membership || membership.status !== "active") this.accessDenied();
|
|
12836
13516
|
return this.deps.membershipStore.list(actor2.tenantId, actor2.projectId);
|
|
12837
13517
|
}
|
|
12838
13518
|
async add(actor2, input) {
|
|
@@ -12952,7 +13632,7 @@ var ProjectMembershipService = class {
|
|
|
12952
13632
|
};
|
|
12953
13633
|
|
|
12954
13634
|
// src/services/collaboration/ProjectRosterService.ts
|
|
12955
|
-
import { randomUUID as
|
|
13635
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
12956
13636
|
var ProjectRosterServiceError = class extends Error {
|
|
12957
13637
|
/** Creates a stable roster service error. */
|
|
12958
13638
|
constructor(code, statusCode, message) {
|
|
@@ -12969,7 +13649,7 @@ var ProjectRosterService = class {
|
|
|
12969
13649
|
/** Creates a roster service from explicitly injected collaborators. */
|
|
12970
13650
|
constructor(deps) {
|
|
12971
13651
|
this.deps = deps;
|
|
12972
|
-
this.idFactory = deps.idFactory ??
|
|
13652
|
+
this.idFactory = deps.idFactory ?? randomUUID14;
|
|
12973
13653
|
this.mutationMutex = deps.projectMutationMutex ?? projectMutationMutex;
|
|
12974
13654
|
}
|
|
12975
13655
|
/** Lists all bot memberships visible to a project reader. */
|
|
@@ -13189,7 +13869,7 @@ var ProjectRosterService = class {
|
|
|
13189
13869
|
};
|
|
13190
13870
|
|
|
13191
13871
|
// src/services/collaboration/RoomMessageService.ts
|
|
13192
|
-
import { createHash as
|
|
13872
|
+
import { createHash as createHash4, randomUUID as randomUUID15 } from "crypto";
|
|
13193
13873
|
|
|
13194
13874
|
// src/services/collaboration/ProjectBotMembershipSnapshot.ts
|
|
13195
13875
|
import { snapshotExactRecord as snapshotExactRecord4 } from "@axiom-lattice/protocols";
|
|
@@ -13278,7 +13958,7 @@ var RoomMessageService = class {
|
|
|
13278
13958
|
constructor(deps, options = {}) {
|
|
13279
13959
|
this.deps = deps;
|
|
13280
13960
|
this.idempotentOutcomes = /* @__PURE__ */ new Map();
|
|
13281
|
-
this.idFactory = deps.idFactory ??
|
|
13961
|
+
this.idFactory = deps.idFactory ?? randomUUID15;
|
|
13282
13962
|
const limit = options.idempotentOutcomeLimit ?? 1e3;
|
|
13283
13963
|
if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError("idempotentOutcomeLimit must be a positive safe integer");
|
|
13284
13964
|
this.idempotentOutcomeLimit = limit;
|
|
@@ -13459,7 +14139,7 @@ var RoomMessageService = class {
|
|
|
13459
14139
|
let idempotencyKey;
|
|
13460
14140
|
if (value.requestIdempotencyKey !== void 0) {
|
|
13461
14141
|
if (typeof value.requestIdempotencyKey !== "string" || !/^[\x21-\x7e]{1,200}$/.test(value.requestIdempotencyKey.trim())) throw new RoomMessageServiceError("INVALID_MESSAGE", 400, "Invalid idempotency key");
|
|
13462
|
-
idempotencyKey = `room-human:${actor2.userId}:${
|
|
14142
|
+
idempotencyKey = `room-human:${actor2.userId}:${createHash4("sha256").update(value.requestIdempotencyKey.trim(), "utf8").digest("hex")}`;
|
|
13463
14143
|
}
|
|
13464
14144
|
return { text, mentions, botIds, ...value.replyToMessageId === void 0 ? {} : { replyToMessageId: value.replyToMessageId }, ...idempotencyKey === void 0 ? {} : { idempotencyKey } };
|
|
13465
14145
|
}
|
|
@@ -13516,7 +14196,7 @@ function cloneRoomMessage(message) {
|
|
|
13516
14196
|
};
|
|
13517
14197
|
}
|
|
13518
14198
|
function fingerprintRequest(input) {
|
|
13519
|
-
return
|
|
14199
|
+
return createHash4("sha256").update(JSON.stringify({
|
|
13520
14200
|
text: input.text,
|
|
13521
14201
|
mentions: input.mentions,
|
|
13522
14202
|
replyToMessageId: input.replyToMessageId ?? null
|
|
@@ -13685,45 +14365,12 @@ async function execute(reply, operation) {
|
|
|
13685
14365
|
}
|
|
13686
14366
|
|
|
13687
14367
|
// src/routes/cospace.ts
|
|
13688
|
-
import { randomUUID as
|
|
14368
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
13689
14369
|
import { ZodError as ZodError5 } from "zod";
|
|
13690
14370
|
import {
|
|
13691
14371
|
generateDirectHumanProjectId,
|
|
13692
14372
|
generateDirectBotProjectId
|
|
13693
14373
|
} from "@axiom-lattice/core";
|
|
13694
|
-
|
|
13695
|
-
// src/services/collaboration/RoomCoordinatorProvisioner.ts
|
|
13696
|
-
var ROOM_COORDINATOR_KEY = "room-coordinator";
|
|
13697
|
-
var RoomCoordinatorProvisioner = class {
|
|
13698
|
-
/** Creates a provisioner over the tenant-scoped assistant store operations. */
|
|
13699
|
-
constructor(deps) {
|
|
13700
|
-
this.deps = deps;
|
|
13701
|
-
}
|
|
13702
|
-
/**
|
|
13703
|
-
* Ensures the Room Coordinator assistant exists, reusing an existing record
|
|
13704
|
-
* and reconciling concurrent create races by rereading the winner.
|
|
13705
|
-
*
|
|
13706
|
-
* @param tenantId Tenant to provision the coordinator for.
|
|
13707
|
-
* @returns The persisted Room Coordinator assistant.
|
|
13708
|
-
*/
|
|
13709
|
-
async ensure(tenantId) {
|
|
13710
|
-
const existing = await this.deps.getAssistantById(tenantId, ROOM_COORDINATOR_KEY);
|
|
13711
|
-
if (existing) return existing;
|
|
13712
|
-
try {
|
|
13713
|
-
return await this.deps.createAssistant(tenantId, ROOM_COORDINATOR_KEY, {
|
|
13714
|
-
name: "Room Coordinator",
|
|
13715
|
-
description: "Coordinates project room conversations and organizes room work.",
|
|
13716
|
-
graphDefinition: {}
|
|
13717
|
-
});
|
|
13718
|
-
} catch (error) {
|
|
13719
|
-
const winner = await this.deps.getAssistantById(tenantId, ROOM_COORDINATOR_KEY);
|
|
13720
|
-
if (winner) return winner;
|
|
13721
|
-
throw error;
|
|
13722
|
-
}
|
|
13723
|
-
}
|
|
13724
|
-
};
|
|
13725
|
-
|
|
13726
|
-
// src/routes/cospace.ts
|
|
13727
14374
|
var CoSpaceAuthError = class extends Error {
|
|
13728
14375
|
constructor() {
|
|
13729
14376
|
super(...arguments);
|
|
@@ -13739,7 +14386,7 @@ function getActor2(request) {
|
|
|
13739
14386
|
return { tenantId: user.tenantId, userId: user.id };
|
|
13740
14387
|
}
|
|
13741
14388
|
function registerCoSpaceRoutes(app2, deps) {
|
|
13742
|
-
const idFactory = deps.idFactory ?? (() =>
|
|
14389
|
+
const idFactory = deps.idFactory ?? (() => randomUUID16());
|
|
13743
14390
|
app2.get(
|
|
13744
14391
|
"/api/workspaces/:workspaceId/rooms",
|
|
13745
14392
|
async (request, reply) => {
|
|
@@ -13764,6 +14411,7 @@ function registerCoSpaceRoutes(app2, deps) {
|
|
|
13764
14411
|
);
|
|
13765
14412
|
const accessibleProjects = [];
|
|
13766
14413
|
for (const project of allProjects) {
|
|
14414
|
+
if (project.kind === "public") continue;
|
|
13767
14415
|
const membership = await deps.membershipStore.findByUser(
|
|
13768
14416
|
actor2.tenantId,
|
|
13769
14417
|
project.id,
|
|
@@ -13817,6 +14465,34 @@ function registerCoSpaceRoutes(app2, deps) {
|
|
|
13817
14465
|
const bTime = b.lastMessage?.createdAt?.getTime() ?? 0;
|
|
13818
14466
|
return bTime - aTime;
|
|
13819
14467
|
});
|
|
14468
|
+
const publicRoom = await deps.publicRoom.ensure(actor2.tenantId, params.workspaceId);
|
|
14469
|
+
if (publicRoom) {
|
|
14470
|
+
const publicMessages = await deps.messageStore.list({
|
|
14471
|
+
tenantId: actor2.tenantId,
|
|
14472
|
+
roomId: publicRoom.room.id,
|
|
14473
|
+
limit: 1
|
|
14474
|
+
});
|
|
14475
|
+
const publicMemberships = await deps.membershipStore.list(
|
|
14476
|
+
actor2.tenantId,
|
|
14477
|
+
publicRoom.project.id
|
|
14478
|
+
);
|
|
14479
|
+
const publicParticipantNames = [];
|
|
14480
|
+
for (const m of publicMemberships) {
|
|
14481
|
+
if (m.status !== "active") continue;
|
|
14482
|
+
const user = await deps.userStore.getUserById(m.userId);
|
|
14483
|
+
if (user) {
|
|
14484
|
+
publicParticipantNames.push(user.name);
|
|
14485
|
+
}
|
|
14486
|
+
}
|
|
14487
|
+
rooms.unshift({
|
|
14488
|
+
project: publicRoom.project,
|
|
14489
|
+
room: publicRoom.room,
|
|
14490
|
+
lastMessage: publicMessages[0],
|
|
14491
|
+
participantCount: publicMemberships.filter((m) => m.status === "active").length,
|
|
14492
|
+
participantNames: publicParticipantNames,
|
|
14493
|
+
isPublic: true
|
|
14494
|
+
});
|
|
14495
|
+
}
|
|
13820
14496
|
return { data: { rooms } };
|
|
13821
14497
|
});
|
|
13822
14498
|
}
|
|
@@ -14573,7 +15249,7 @@ function exactValues(value, required, optional = []) {
|
|
|
14573
15249
|
}
|
|
14574
15250
|
|
|
14575
15251
|
// src/services/collaboration/CoordinatorWakeNotifier.ts
|
|
14576
|
-
import { randomUUID as
|
|
15252
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
14577
15253
|
import { isProjectLifecycleTask as isProjectLifecycleTask2 } from "@axiom-lattice/core";
|
|
14578
15254
|
import {
|
|
14579
15255
|
snapshotExactRecord as snapshotExactRecord6
|
|
@@ -14590,7 +15266,7 @@ var CoordinatorWakeNotifier = class {
|
|
|
14590
15266
|
this.accepted = /* @__PURE__ */ new Set();
|
|
14591
15267
|
this.maxPending = deps.maxPending ?? 1024;
|
|
14592
15268
|
if (!Number.isSafeInteger(this.maxPending) || this.maxPending < 1) throw new RangeError("maxPending must be a positive safe integer");
|
|
14593
|
-
this.idFactory = deps.idFactory ??
|
|
15269
|
+
this.idFactory = deps.idFactory ?? randomUUID17;
|
|
14594
15270
|
}
|
|
14595
15271
|
/** Admits one notification without awaiting any store or dispatch operation. */
|
|
14596
15272
|
handle(value) {
|
|
@@ -15460,7 +16136,7 @@ function validRecoveryRow(row, task, thread) {
|
|
|
15460
16136
|
}
|
|
15461
16137
|
|
|
15462
16138
|
// src/services/collaboration/ProjectTaskObserver.ts
|
|
15463
|
-
import { createHash as
|
|
16139
|
+
import { createHash as createHash5 } from "crypto";
|
|
15464
16140
|
import {
|
|
15465
16141
|
isProjectLifecycleTask as isProjectLifecycleTask4,
|
|
15466
16142
|
parseExternalDependency as parseExternalDependency2
|
|
@@ -16000,7 +16676,7 @@ var ProjectTaskObserver = class {
|
|
|
16000
16676
|
action: "activity",
|
|
16001
16677
|
summary: "Project task recovery warning",
|
|
16002
16678
|
detail: { type: "project_task_recovery_warning", phase, message },
|
|
16003
|
-
eventKey: `project-task-recovery-warning:${task.id}:${phase}:${
|
|
16679
|
+
eventKey: `project-task-recovery-warning:${task.id}:${phase}:${createHash5("sha256").update(message).digest("hex")}`
|
|
16004
16680
|
});
|
|
16005
16681
|
}
|
|
16006
16682
|
async safeWarning(task, phase, error) {
|
|
@@ -16067,7 +16743,7 @@ var ProjectTaskObserver = class {
|
|
|
16067
16743
|
}
|
|
16068
16744
|
}
|
|
16069
16745
|
async persistOverflowWarning(event) {
|
|
16070
|
-
const scopeDigest =
|
|
16746
|
+
const scopeDigest = createHash5("sha256").update(JSON.stringify([event.tenantId, event.workspaceId, event.projectId])).digest("hex");
|
|
16071
16747
|
try {
|
|
16072
16748
|
await this.deps.workItems.createIfAbsentByEventKey({
|
|
16073
16749
|
tenantId: event.tenantId,
|
|
@@ -16168,7 +16844,7 @@ function combineSignals(left, right) {
|
|
|
16168
16844
|
}
|
|
16169
16845
|
|
|
16170
16846
|
// src/services/collaboration/RoomProjectionService.ts
|
|
16171
|
-
import { createHash as
|
|
16847
|
+
import { createHash as createHash6 } from "crypto";
|
|
16172
16848
|
import { isProjectLifecycleTask as isProjectLifecycleTask5 } from "@axiom-lattice/core";
|
|
16173
16849
|
import {
|
|
16174
16850
|
PROJECT_TASK_LIFECYCLE_ACTIONS,
|
|
@@ -16341,7 +17017,7 @@ function safeRepairIdentity(value, scope) {
|
|
|
16341
17017
|
return taskId && eventKey && tenantId === scope.tenantId && workspaceId === scope.workspaceId && projectId === scope.projectId ? { taskId, eventKey } : void 0;
|
|
16342
17018
|
}
|
|
16343
17019
|
function tupleKey(prefix, values) {
|
|
16344
|
-
const digest =
|
|
17020
|
+
const digest = createHash6("sha256").update(JSON.stringify(values)).digest("hex");
|
|
16345
17021
|
return `${prefix}:${digest}`;
|
|
16346
17022
|
}
|
|
16347
17023
|
function parseProjectionInput(taskValue, itemValue) {
|
|
@@ -16369,7 +17045,7 @@ function snapshotProjectableWorkItem(value) {
|
|
|
16369
17045
|
if (!row || !createdAt || typeof row.id !== "string" || !row.id || typeof row.taskId !== "string" || !row.taskId || typeof row.tenantId !== "string" || !row.tenantId || typeof row.workspaceId !== "string" || !row.workspaceId || typeof row.projectId !== "string" || !row.projectId || typeof row.actor !== "string" || !row.actor || typeof row.eventKey !== "string" || !row.eventKey || !PROJECT_TASK_LIFECYCLE_ACTIONS.includes(row.action) || row.threadId !== void 0 && typeof row.threadId !== "string" || row.summary !== void 0 && typeof row.summary !== "string" || row.attempt !== void 0 && (typeof row.attempt !== "number" || !Number.isFinite(row.attempt))) return void 0;
|
|
16370
17046
|
try {
|
|
16371
17047
|
const detail = row.detail === void 0 ? void 0 : snapshotJsonValue(row.detail);
|
|
16372
|
-
if (detail !== void 0 && !
|
|
17048
|
+
if (detail !== void 0 && !isRecord3(detail)) return void 0;
|
|
16373
17049
|
return {
|
|
16374
17050
|
id: row.id,
|
|
16375
17051
|
taskId: row.taskId,
|
|
@@ -16516,7 +17192,7 @@ function snapshotLifecycleCursor(value) {
|
|
|
16516
17192
|
const createdAt = cloneExactDate(row?.createdAt);
|
|
16517
17193
|
return row && typeof row.id === "string" && row.id.length > 0 && createdAt ? { id: row.id, createdAt } : void 0;
|
|
16518
17194
|
}
|
|
16519
|
-
function
|
|
17195
|
+
function isRecord3(value) {
|
|
16520
17196
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16521
17197
|
}
|
|
16522
17198
|
|
|
@@ -16524,7 +17200,7 @@ function isRecord4(value) {
|
|
|
16524
17200
|
import { taskLifecycleNotifier } from "@axiom-lattice/core";
|
|
16525
17201
|
|
|
16526
17202
|
// src/services/collaboration/ProjectRoomEventBroker.ts
|
|
16527
|
-
import { randomUUID as
|
|
17203
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
16528
17204
|
import {
|
|
16529
17205
|
parseProjectRoomEventId,
|
|
16530
17206
|
ProjectRoomBrokerCapacityError,
|
|
@@ -16553,7 +17229,7 @@ var ProjectRoomEventBroker = class {
|
|
|
16553
17229
|
this.subscriberCount = 0;
|
|
16554
17230
|
this.usageClock = 0;
|
|
16555
17231
|
this.closed = false;
|
|
16556
|
-
this.epoch = options.epoch ??
|
|
17232
|
+
this.epoch = options.epoch ?? randomUUID18();
|
|
16557
17233
|
if (!UUID_PATTERN.test(this.epoch)) {
|
|
16558
17234
|
throw new TypeError("Project Room event epoch must be a lowercase canonical UUID");
|
|
16559
17235
|
}
|
|
@@ -16871,7 +17547,7 @@ function snapshotJson(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
|
16871
17547
|
}
|
|
16872
17548
|
|
|
16873
17549
|
// src/services/collaboration/ProjectRoomRealtimePublisher.ts
|
|
16874
|
-
import { createHash as
|
|
17550
|
+
import { createHash as createHash7 } from "crypto";
|
|
16875
17551
|
import {
|
|
16876
17552
|
snapshotExactRecord as snapshotExactRecord10,
|
|
16877
17553
|
snapshotProjectRoomBotMembershipRealtime,
|
|
@@ -16952,7 +17628,7 @@ var BrokerProjectRoomRealtimePublisher = class {
|
|
|
16952
17628
|
try {
|
|
16953
17629
|
event = build();
|
|
16954
17630
|
if (!event) throw new Error("Invalid Project Room realtime projection");
|
|
16955
|
-
const dedupeKey =
|
|
17631
|
+
const dedupeKey = createHash7("sha256").update(JSON.stringify([type, event.scope.tenantId, event.scope.projectId, event.scope.roomId, event.sourceKey])).digest("hex");
|
|
16956
17632
|
if (this.sourceKeys.has(dedupeKey)) return;
|
|
16957
17633
|
this.deps.broker.publish(event.scope, event.draft);
|
|
16958
17634
|
this.sourceKeys.set(dedupeKey, true);
|
|
@@ -17891,12 +18567,24 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
17891
18567
|
});
|
|
17892
18568
|
}
|
|
17893
18569
|
if (projectTaskComposition) app2.addHook("onClose", async () => projectTaskComposition.lease.release());
|
|
17894
|
-
const taskThreadService = projectTaskComposition?.lease.runtime.threads ?? new ProjectTaskThreadService({ threads: threadStore, idFactory:
|
|
18570
|
+
const taskThreadService = projectTaskComposition?.lease.runtime.threads ?? new ProjectTaskThreadService({ threads: threadStore, idFactory: randomUUID19 });
|
|
17895
18571
|
const taskLifecycle = projectTaskComposition?.lease.runtime.lifecycle ?? createTaskLifecycleService({ taskStore, workItemStore: taskWorkItemStore });
|
|
17896
18572
|
const reassignmentAuthorizer = createProjectTaskReassignmentAuthorizer(
|
|
17897
18573
|
new ProjectTaskOwnerPolicy(projectBotMembershipStore),
|
|
17898
18574
|
(input) => validateReassignmentThread(threadStore, input)
|
|
17899
18575
|
);
|
|
18576
|
+
const coordinatorProvisioner = new RoomCoordinatorProvisioner({
|
|
18577
|
+
getAssistantById: assistantStore.getAssistantById.bind(assistantStore),
|
|
18578
|
+
createAssistant: assistantStore.createAssistant.bind(assistantStore)
|
|
18579
|
+
});
|
|
18580
|
+
const publicRoomService = new WorkspacePublicRoomService({
|
|
18581
|
+
getProjectById: projectStore.getProjectById.bind(projectStore),
|
|
18582
|
+
createProject: projectStore.createProject.bind(projectStore),
|
|
18583
|
+
ensureMainRoom: projectRoomStore.ensureMainRoom.bind(projectRoomStore),
|
|
18584
|
+
findBotByAssistant: projectBotMembershipStore.findByAssistant.bind(projectBotMembershipStore),
|
|
18585
|
+
saveBot: projectBotMembershipStore.save.bind(projectBotMembershipStore),
|
|
18586
|
+
ensureCoordinator: (tenantId) => coordinatorProvisioner.ensure(tenantId)
|
|
18587
|
+
});
|
|
17900
18588
|
registerProjectRoomRoutes(app2, { roomService, membershipService, messageService });
|
|
17901
18589
|
registerProjectRosterRoutes(app2, { service: rosterService });
|
|
17902
18590
|
registerCoSpaceRoutes(app2, {
|
|
@@ -17935,14 +18623,14 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
17935
18623
|
findByAssistant: projectBotMembershipStore.findByAssistant.bind(projectBotMembershipStore),
|
|
17936
18624
|
save: projectBotMembershipStore.save.bind(projectBotMembershipStore)
|
|
17937
18625
|
},
|
|
17938
|
-
coordinatorProvisioner
|
|
17939
|
-
getAssistantById: assistantStore.getAssistantById.bind(assistantStore),
|
|
17940
|
-
createAssistant: assistantStore.createAssistant.bind(assistantStore)
|
|
17941
|
-
}),
|
|
18626
|
+
coordinatorProvisioner,
|
|
17942
18627
|
rosterService: {
|
|
17943
18628
|
addBot: async (actor2, input) => {
|
|
17944
18629
|
await rosterService.add(actor2, input);
|
|
17945
18630
|
}
|
|
18631
|
+
},
|
|
18632
|
+
publicRoom: {
|
|
18633
|
+
ensure: (tenantId, workspaceId) => publicRoomService.ensure(tenantId, workspaceId)
|
|
17946
18634
|
}
|
|
17947
18635
|
});
|
|
17948
18636
|
registerCapabilityBundleRoutes(app2, new CapabilityBundleController({
|
|
@@ -17962,6 +18650,46 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
17962
18650
|
})
|
|
17963
18651
|
});
|
|
17964
18652
|
registerAgentWebAppRuntimeRoutes(app2, {
|
|
18653
|
+
workspaceController: new WorkspaceController(createProjectCollaborationInitializer({
|
|
18654
|
+
membershipStore: projectMembershipStore,
|
|
18655
|
+
roomStore: projectRoomStore,
|
|
18656
|
+
userTenantLinkStore
|
|
18657
|
+
})),
|
|
18658
|
+
taskController: createTaskController({
|
|
18659
|
+
access: {
|
|
18660
|
+
requireProjectAccess: async (input) => {
|
|
18661
|
+
const apps = await webAppStore.list(input.tenantId);
|
|
18662
|
+
const allowed = apps.some((app3) => app3.status === "active" && app3.scope.allowedProjectIds.includes(input.projectId));
|
|
18663
|
+
if (!allowed) throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project is not exposed through this web app");
|
|
18664
|
+
const project = await projectStore.getProjectById(input.tenantId, input.projectId);
|
|
18665
|
+
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
18666
|
+
return {
|
|
18667
|
+
project,
|
|
18668
|
+
membership: {
|
|
18669
|
+
id: `webapp_guest_${input.projectId}_${input.userId}`,
|
|
18670
|
+
tenantId: input.tenantId,
|
|
18671
|
+
projectId: input.projectId,
|
|
18672
|
+
userId: input.userId,
|
|
18673
|
+
role: "member",
|
|
18674
|
+
status: "active",
|
|
18675
|
+
joinedAt: /* @__PURE__ */ new Date(),
|
|
18676
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
18677
|
+
}
|
|
18678
|
+
};
|
|
18679
|
+
}
|
|
18680
|
+
},
|
|
18681
|
+
tasks: taskStore,
|
|
18682
|
+
workItems: taskWorkItemStore,
|
|
18683
|
+
bots: projectBotMembershipStore,
|
|
18684
|
+
rooms: projectRoomStore,
|
|
18685
|
+
threadService: taskThreadService,
|
|
18686
|
+
reassignmentAuthorizer,
|
|
18687
|
+
lifecycle: taskLifecycle,
|
|
18688
|
+
projectMutationMutex: projectMutationMutex2,
|
|
18689
|
+
executionMutex: projectTaskComposition?.mutex ?? {
|
|
18690
|
+
runExclusive: (_scope, operation) => operation()
|
|
18691
|
+
}
|
|
18692
|
+
}),
|
|
17965
18693
|
service: new AgentWebAppRuntimeService({
|
|
17966
18694
|
webAppStore,
|
|
17967
18695
|
threadStore: getStoreLattice16("default", "thread").store,
|
|
@@ -17969,7 +18697,7 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
17969
18697
|
assistantResolver: {
|
|
17970
18698
|
resolve: async (tenantId, assistantId) => {
|
|
17971
18699
|
const stored = await assistantStore.getAssistantById(tenantId, assistantId);
|
|
17972
|
-
if (stored) return stored;
|
|
18700
|
+
if (stored) return { id: stored.id, name: stored.name, description: stored.description };
|
|
17973
18701
|
await agentLatticeManager4.initializeStoredAssistantsForTenant(tenantId);
|
|
17974
18702
|
const config = agentLatticeManager4.getAgentConfigWithTenant(tenantId, assistantId);
|
|
17975
18703
|
return config ? { id: assistantId, name: config.name, description: config.description } : null;
|
|
@@ -18383,7 +19111,7 @@ function tryCreateProjectTaskRuntime(input) {
|
|
|
18383
19111
|
function createRuntime(input, stores, log2) {
|
|
18384
19112
|
const controller = new AbortController();
|
|
18385
19113
|
const mutex = new ProjectTaskExecutionMutex();
|
|
18386
|
-
const threads = new ProjectTaskThreadService({ threads: input.threadStore, idFactory:
|
|
19114
|
+
const threads = new ProjectTaskThreadService({ threads: input.threadStore, idFactory: randomUUID19 });
|
|
18387
19115
|
const lifecycle = createTaskLifecycleService({ taskStore: input.taskStore, workItemStore: input.taskWorkItemStore });
|
|
18388
19116
|
const projection = new RoomProjectionService({
|
|
18389
19117
|
messages: input.projectRoomMessageStore,
|
|
@@ -18392,7 +19120,7 @@ function createRuntime(input, stores, log2) {
|
|
|
18392
19120
|
warnings: input.taskWorkItemStore,
|
|
18393
19121
|
log: log2,
|
|
18394
19122
|
realtime: input.realtime,
|
|
18395
|
-
idFactory:
|
|
19123
|
+
idFactory: randomUUID19
|
|
18396
19124
|
});
|
|
18397
19125
|
const agentFor = (task, thread) => agentInstanceManager6.getAgent({
|
|
18398
19126
|
tenant_id: task.tenantId,
|
|
@@ -19217,7 +19945,7 @@ var collectionRegistration = {
|
|
|
19217
19945
|
|
|
19218
19946
|
// src/export_registrations/channel-installation.registration.ts
|
|
19219
19947
|
import { getStoreLattice as getStoreLattice23 } from "@axiom-lattice/core";
|
|
19220
|
-
import { randomUUID as
|
|
19948
|
+
import { randomUUID as randomUUID20 } from "crypto";
|
|
19221
19949
|
var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
19222
19950
|
var PUBLIC_CHANNELS = /* @__PURE__ */ new Set(["lark", "email", "slack", "wechat"]);
|
|
19223
19951
|
function isPublicChannel(channel) {
|
|
@@ -19376,7 +20104,7 @@ var channelInstallationRegistration = {
|
|
|
19376
20104
|
}
|
|
19377
20105
|
id3 = sourceId;
|
|
19378
20106
|
} else {
|
|
19379
|
-
id3 =
|
|
20107
|
+
id3 = randomUUID20();
|
|
19380
20108
|
}
|
|
19381
20109
|
await store.createInstallation(tenantId, id3, createInput);
|
|
19382
20110
|
return { newId: id3 };
|
|
@@ -20264,7 +20992,7 @@ import {
|
|
|
20264
20992
|
snapshotExactArray as snapshotExactArray3,
|
|
20265
20993
|
snapshotExactRecord as snapshotExactRecord14
|
|
20266
20994
|
} from "@axiom-lattice/protocols";
|
|
20267
|
-
import { randomUUID as
|
|
20995
|
+
import { randomUUID as randomUUID21 } from "crypto";
|
|
20268
20996
|
var BindingNotFoundError = class extends Error {
|
|
20269
20997
|
constructor(message) {
|
|
20270
20998
|
super(message);
|
|
@@ -20579,7 +21307,7 @@ var MessageRouter = class {
|
|
|
20579
21307
|
}
|
|
20580
21308
|
if (!threadId) {
|
|
20581
21309
|
const threadStore = getStoreLattice26("default", "thread").store;
|
|
20582
|
-
const newThreadId =
|
|
21310
|
+
const newThreadId = randomUUID21();
|
|
20583
21311
|
console.log({
|
|
20584
21312
|
event: "dispatch:thread:create",
|
|
20585
21313
|
agentId,
|
|
@@ -20625,7 +21353,7 @@ var MessageRouter = class {
|
|
|
20625
21353
|
workspace_id: binding?.workspaceId || "",
|
|
20626
21354
|
project_id: binding?.projectId || ""
|
|
20627
21355
|
});
|
|
20628
|
-
const inputMessageId =
|
|
21356
|
+
const inputMessageId = randomUUID21();
|
|
20629
21357
|
const replyTarget = internalRoom ? correlatedRoomReplyTarget(message.replyTarget, inputMessageId) : message.replyTarget;
|
|
20630
21358
|
const replyAdapter = replyTarget ? this.adapterRegistry.get(replyTarget.adapterChannel) : void 0;
|
|
20631
21359
|
if (options.trustedMessageOptions && (!this.trustedDispatcher || !replyTarget || !replyAdapter)) {
|
|
@@ -21082,7 +21810,7 @@ var start = async (config) => {
|
|
|
21082
21810
|
});
|
|
21083
21811
|
try {
|
|
21084
21812
|
const { A2AAuthService, parseEnvKeys } = await import("./A2AAuthService-DK5X6VIL.mjs");
|
|
21085
|
-
const { registerA2AStandardRoutes } = await import("./a2a-standard-
|
|
21813
|
+
const { registerA2AStandardRoutes } = await import("./a2a-standard-WC2BSOFV.mjs");
|
|
21086
21814
|
const a2a = await import("./a2a-7N3WDAOP.mjs");
|
|
21087
21815
|
const a2aKeyStore = getStoreLattice27("default", "a2aApiKey").store;
|
|
21088
21816
|
const assistantStore = getStoreLattice27("default", "assistant").store;
|