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