@promptowl/contextnest-community 1.23.0 → 1.24.0
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/API.md +2092 -0
- package/CONFIGURATION.md +1 -0
- package/README.md +61 -9
- package/STEWARDSHIP.md +229 -0
- package/dist/{chunk-2YPY2HGZ.js → chunk-2TPQTN4Y.js} +5 -4
- package/dist/{chunk-MLQU4I5J.js → chunk-5EZOPA47.js} +5 -5
- package/dist/{chunk-UOMW7TT6.js → chunk-I3CSD6CK.js} +1 -1
- package/dist/{chunk-QLXC6542.js → chunk-KIAAEHWL.js} +49 -25
- package/dist/{chunk-QWNXWRWO.js → chunk-LA3VTQ22.js} +25 -1
- package/dist/{chunk-XYD6V2LH.js → chunk-XUIWAWDO.js} +28 -33
- package/dist/{chunk-5CVZMHHB.js → chunk-ZTT4U4NE.js} +2 -2
- package/dist/{client-LWDMNKX3.js → client-KEY4PYJH.js} +1 -1
- package/dist/{engine-QFF2IA3G.js → engine-S3QBQ7LH.js} +2 -2
- package/dist/{external-edit-service-F7D3VSA2.js → external-edit-service-6FNFPOJJ.js} +3 -3
- package/dist/{grants-service-TBGTCO5K.js → grants-service-UT3EQ3R7.js} +2 -2
- package/dist/index.js +371 -90
- package/dist/{migrations.postgres-APCVSYUE.js → migrations.postgres-AIQ7WSU7.js} +45 -4
- package/dist/{review-service-A7PNLWG5.js → review-service-HHGOTO6P.js} +6 -6
- package/dist/{stewardship-service-32UHIRQ5.js → stewardship-service-4DHNRULG.js} +3 -3
- package/dist/{version-service-RJ6CWZ3O.js → version-service-KIOQME6U.js} +3 -3
- package/dist/web3/assets/ActivityTracePage-BOZHgJ8S.js +1 -0
- package/dist/web3/assets/AgentDocsPage-CIiaBqMy.js +1 -0
- package/dist/web3/assets/CollaboratorManager-By91wrIr.js +1 -0
- package/dist/web3/assets/CollaboratorsTab-D1EE64Fs.js +1 -0
- package/dist/web3/assets/DocumentEditor-DrQfrW3d.js +36 -0
- package/dist/web3/assets/DocumentsTab-Vba-AzBv.js +6 -0
- package/dist/web3/assets/ExternalEditsTab-BSzAQIGM.js +1 -0
- package/dist/web3/assets/MarkdownEditor-74N_naNQ.css +1 -0
- package/dist/web3/assets/MarkdownEditor-C9zqZjC5.js +643 -0
- package/dist/web3/assets/NestPageHeader-CoLUV1Oz.js +1 -0
- package/dist/web3/assets/NestView-CTZ55tXD.js +63 -0
- package/dist/web3/assets/OverviewTab-Dxi-Hu-N.js +1 -0
- package/dist/web3/assets/PersonCombobox-CPOlNs6G.js +1 -0
- package/dist/web3/assets/ReasonDialog-DgLxfRWv.js +1 -0
- package/dist/web3/assets/ReviewActions-OfOdqzCn.js +6 -0
- package/dist/web3/assets/ReviewTab-CyX8yxd3.js +1 -0
- package/dist/web3/assets/StewardsTab-DBEEfAQI.js +1 -0
- package/dist/web3/assets/SubmitForReviewModal-Ci2DdWs0.js +1 -0
- package/dist/web3/assets/alert-dialog-DTozKlmV.js +7 -0
- package/dist/web3/assets/arrow-left-BCKt4CwQ.js +6 -0
- package/dist/web3/assets/backlinks-CYd4xENL.js +24 -0
- package/dist/web3/assets/card-XG2dP5Xf.js +1 -0
- package/dist/web3/assets/chevron-left-BPwUT9DS.js +6 -0
- package/dist/web3/assets/circle-check-DI8IBGeo.js +6 -0
- package/dist/web3/assets/circle-x-VKVhC88W.js +6 -0
- package/dist/web3/assets/code-xml-CMabYZvH.js +6 -0
- package/dist/web3/assets/corner-down-right-BGxwK6wH.js +6 -0
- package/dist/web3/assets/count-skeleton-DMbdpscX.js +1 -0
- package/dist/web3/assets/dates-BCxbm4_q.js +1 -0
- package/dist/web3/assets/earth-BEp1EKTo.js +6 -0
- package/dist/web3/assets/file-exclamation-point-r8qZGIjP.js +6 -0
- package/dist/web3/assets/folder-input-Cmtx1Jhk.js +11 -0
- package/dist/web3/assets/folder-target-CUSWqImF.js +1 -0
- package/dist/web3/assets/index-BM-h3DwI.css +1 -0
- package/dist/web3/assets/index-C1wTSjfP.js +29 -0
- package/dist/web3/assets/index-EaX2yql0.js +389 -0
- package/dist/web3/assets/page-B3yvQvHy.js +1 -0
- package/dist/web3/assets/page-BExMvttJ.js +1 -0
- package/dist/web3/assets/page-BUOADv2X.js +1 -0
- package/dist/web3/assets/page-BYUFhYWZ.js +1 -0
- package/dist/web3/assets/page-Bicf02Cz.js +1 -0
- package/dist/web3/assets/page-BsPMDm5d.js +45 -0
- package/dist/web3/assets/page-ByyVLDVo.js +16 -0
- package/dist/web3/assets/page-CC0NJi8v.js +1 -0
- package/dist/web3/assets/page-CHb0F5hV.js +24 -0
- package/dist/web3/assets/page-CPjWQNKx.js +11 -0
- package/dist/web3/assets/page-CQt3ZGbf.js +2 -0
- package/dist/web3/assets/page-Cj1NetQ-.js +1 -0
- package/dist/web3/assets/page-CtaW65El.js +1 -0
- package/dist/web3/assets/page-DGOL9l8i.js +6 -0
- package/dist/web3/assets/page-title-ClvvBsIo.js +1 -0
- package/dist/web3/assets/play-C5YNKpAH.js +6 -0
- package/dist/web3/assets/refresh-cw-BhHzSH9m.js +6 -0
- package/dist/web3/assets/scroll-area-dRWncRqa.css +1 -0
- package/dist/web3/assets/scroll-area-hz-xtayP.js +1 -0
- package/dist/web3/assets/select-DBFTrUmI.js +6 -0
- package/dist/web3/assets/send-0CHvmluT.js +6 -0
- package/dist/web3/assets/settings-CeeF4LEb.js +6 -0
- package/dist/web3/assets/share-2-CMnOlOt-.js +6 -0
- package/dist/web3/assets/tag-DQ_6J5Gv.js +11 -0
- package/dist/web3/assets/trash-2-HBi-Pslz.js +6 -0
- package/dist/web3/assets/triangle-alert-CDy8-7sv.js +6 -0
- package/dist/web3/assets/user-plus-CvBNcpn4.js +6 -0
- package/dist/web3/assets/x-By7piikG.js +6 -0
- package/dist/web3/assets/zap-C2T8riBp.js +11 -0
- package/dist/web3/index.html +2 -2
- package/package.json +4 -2
- package/dist/web3/assets/index-BJ-LRNis.js +0 -1382
- package/dist/web3/assets/index-DPMEt-A_.css +0 -1
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
submitForReview,
|
|
39
39
|
withNodeWriteLock,
|
|
40
40
|
withdrawDeletionRequest
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-5EZOPA47.js";
|
|
42
42
|
import {
|
|
43
43
|
canCreateInNest,
|
|
44
44
|
canManageStewards,
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
resolveUserRoles,
|
|
59
59
|
syncFromConfig,
|
|
60
60
|
updateSteward
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-2TPQTN4Y.js";
|
|
62
62
|
import {
|
|
63
63
|
createGrant,
|
|
64
64
|
deleteGrant,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
listGrants,
|
|
68
68
|
listUserGrants,
|
|
69
69
|
resolveNodeGrant
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-I3CSD6CK.js";
|
|
71
71
|
import {
|
|
72
72
|
generateApiKey,
|
|
73
73
|
getKeyPrefix,
|
|
@@ -93,7 +93,7 @@ import {
|
|
|
93
93
|
listMyDrafts,
|
|
94
94
|
setApprovedVersion,
|
|
95
95
|
upsertVersion
|
|
96
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-ZTT4U4NE.js";
|
|
97
97
|
import {
|
|
98
98
|
approveExternalEdit,
|
|
99
99
|
getExternalEditDetail,
|
|
@@ -105,7 +105,7 @@ import {
|
|
|
105
105
|
scanDocumentForDrift,
|
|
106
106
|
scanNestForDrift,
|
|
107
107
|
startDriftScanner
|
|
108
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-XUIWAWDO.js";
|
|
109
109
|
import {
|
|
110
110
|
addMember,
|
|
111
111
|
addWatcher,
|
|
@@ -211,7 +211,7 @@ import {
|
|
|
211
211
|
updateMemberRole,
|
|
212
212
|
validateLicense,
|
|
213
213
|
verifySmtp
|
|
214
|
-
} from "./chunk-
|
|
214
|
+
} from "./chunk-KIAAEHWL.js";
|
|
215
215
|
import {
|
|
216
216
|
AppError,
|
|
217
217
|
ConflictError,
|
|
@@ -229,7 +229,7 @@ import {
|
|
|
229
229
|
initDb,
|
|
230
230
|
isEmailListish,
|
|
231
231
|
isEmailish
|
|
232
|
-
} from "./chunk-
|
|
232
|
+
} from "./chunk-LA3VTQ22.js";
|
|
233
233
|
import {
|
|
234
234
|
ANON_EMAIL,
|
|
235
235
|
ANON_USER_ID,
|
|
@@ -1696,6 +1696,15 @@ authRoutes.get("/admin-status", async (c) => {
|
|
|
1696
1696
|
me
|
|
1697
1697
|
});
|
|
1698
1698
|
});
|
|
1699
|
+
authRoutes.patch("/profile", authMiddleware, async (c) => {
|
|
1700
|
+
const userId = c.get("userId");
|
|
1701
|
+
const body = await c.req.json();
|
|
1702
|
+
if (typeof body.name !== "string") throw new ValidationError("name is required");
|
|
1703
|
+
const name = body.name.trim();
|
|
1704
|
+
if (name.length > 80) throw new ValidationError("name must be 80 characters or fewer");
|
|
1705
|
+
await getDb().run("UPDATE users SET name = ? WHERE id = ?", [name || null, userId]);
|
|
1706
|
+
return c.json({ ok: true, name: name || null });
|
|
1707
|
+
});
|
|
1699
1708
|
authRoutes.post("/password", authMiddleware, async (c) => {
|
|
1700
1709
|
const body = await c.req.json();
|
|
1701
1710
|
if (!body.current || !body.next) {
|
|
@@ -2559,6 +2568,19 @@ var ACCEPTED_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2559
2568
|
"artifact",
|
|
2560
2569
|
"table"
|
|
2561
2570
|
]);
|
|
2571
|
+
function assertAcceptedType(type) {
|
|
2572
|
+
if (type === "artifact" && !config.TYPE_ARTIFACT_ENABLED) {
|
|
2573
|
+
throw new ValidationError("Artifact nodes are disabled on this server");
|
|
2574
|
+
}
|
|
2575
|
+
if (type === "table" && !config.TYPE_TABLE_ENABLED) {
|
|
2576
|
+
throw new ValidationError("Table nodes are disabled on this server");
|
|
2577
|
+
}
|
|
2578
|
+
if (!ACCEPTED_NODE_TYPES.has(type)) {
|
|
2579
|
+
throw new ValidationError(
|
|
2580
|
+
`unknown node type "${type}"; allowed: ${[...ACCEPTED_NODE_TYPES].sort().join(", ")}`
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2562
2584
|
function toNodeResponse(node) {
|
|
2563
2585
|
const fm = node.frontmatter;
|
|
2564
2586
|
const title = fm.title === void 0 || fm.title === null ? "" : String(fm.title);
|
|
@@ -3009,17 +3031,7 @@ async function createNodeOnce(nestId, input, userEmail, opts = {}) {
|
|
|
3009
3031
|
}
|
|
3010
3032
|
const tags = requireTags(input.tags);
|
|
3011
3033
|
const requestedType = input.type || "document";
|
|
3012
|
-
|
|
3013
|
-
throw new ValidationError("Artifact nodes are disabled on this server");
|
|
3014
|
-
}
|
|
3015
|
-
if (requestedType === "table" && !config.TYPE_TABLE_ENABLED) {
|
|
3016
|
-
throw new ValidationError("Table nodes are disabled on this server");
|
|
3017
|
-
}
|
|
3018
|
-
if (!ACCEPTED_NODE_TYPES.has(requestedType)) {
|
|
3019
|
-
throw new ValidationError(
|
|
3020
|
-
`unknown node type "${requestedType}"; allowed: ${[...ACCEPTED_NODE_TYPES].sort().join(", ")}`
|
|
3021
|
-
);
|
|
3022
|
-
}
|
|
3034
|
+
assertAcceptedType(requestedType);
|
|
3023
3035
|
if (input.schedule !== void 0 && !isRunnableType(input.type)) {
|
|
3024
3036
|
throw new ValidationError(
|
|
3025
3037
|
`schedule is only valid for runnable node types (${RUNNABLE_NODE_TYPES.join(
|
|
@@ -3282,6 +3294,20 @@ async function updateNode(nestId, nodeId, patch, userEmail, opts = {}) {
|
|
|
3282
3294
|
};
|
|
3283
3295
|
opInput.title = patch.title;
|
|
3284
3296
|
}
|
|
3297
|
+
if (patch.type !== void 0 && patch.type !== node.frontmatter.type) {
|
|
3298
|
+
assertAcceptedType(patch.type);
|
|
3299
|
+
if (patch.type === "skill") {
|
|
3300
|
+
opInput.trigger = `when asked to ${String(node.frontmatter.title || nodeId).toLowerCase()}`;
|
|
3301
|
+
}
|
|
3302
|
+
if (isRunnableType(node.frontmatter.type) && !isRunnableType(patch.type)) {
|
|
3303
|
+
const metadata = { ...node.frontmatter.metadata };
|
|
3304
|
+
delete metadata.schedule;
|
|
3305
|
+
node = { ...node, frontmatter: { ...node.frontmatter, metadata } };
|
|
3306
|
+
opInput.metadata = { ...opInput.metadata, schedule: null };
|
|
3307
|
+
}
|
|
3308
|
+
node = { ...node, frontmatter: { ...node.frontmatter, type: patch.type } };
|
|
3309
|
+
opInput.type = patch.type;
|
|
3310
|
+
}
|
|
3285
3311
|
if (patch.schedule !== void 0) {
|
|
3286
3312
|
if (!isRunnableType(node.frontmatter.type)) {
|
|
3287
3313
|
throw new ValidationError(
|
|
@@ -4590,12 +4616,19 @@ sharingRoutes.patch("/reader-mode", async (c) => {
|
|
|
4590
4616
|
import { Hono as Hono5 } from "hono";
|
|
4591
4617
|
import { v4 as uuid7 } from "uuid";
|
|
4592
4618
|
var grantRoutes = new Hono5();
|
|
4593
|
-
async function requireManage(c, nestId) {
|
|
4594
|
-
if (
|
|
4619
|
+
async function requireManage(c, nestId, opts = {}) {
|
|
4620
|
+
if (config.AUTH_MODE === "open") return;
|
|
4621
|
+
const access = await resolveNestAccess(nestId, c.get("userId"));
|
|
4622
|
+
if (access.rawPermission !== "owner" && access.rawPermission !== "admin") {
|
|
4595
4623
|
throw new ForbiddenError(
|
|
4596
4624
|
"Only a nest admin, the nest owner, or the server admin can manage sharing."
|
|
4597
4625
|
);
|
|
4598
4626
|
}
|
|
4627
|
+
if (access.archived && !opts.readOnly) {
|
|
4628
|
+
throw new ForbiddenError(
|
|
4629
|
+
"This nest is archived, so it is read-only. Restore it to change sharing."
|
|
4630
|
+
);
|
|
4631
|
+
}
|
|
4599
4632
|
}
|
|
4600
4633
|
async function resolveGrantee(email) {
|
|
4601
4634
|
const db = getDb();
|
|
@@ -4685,7 +4718,7 @@ grantRoutes.post("/", async (c) => {
|
|
|
4685
4718
|
});
|
|
4686
4719
|
grantRoutes.get("/", async (c) => {
|
|
4687
4720
|
const nestId = c.req.param("nestId");
|
|
4688
|
-
await requireManage(c, nestId);
|
|
4721
|
+
await requireManage(c, nestId, { readOnly: true });
|
|
4689
4722
|
const grants = await listGrants(nestId);
|
|
4690
4723
|
const db = getDb();
|
|
4691
4724
|
const withEmail = await Promise.all(
|
|
@@ -6208,7 +6241,7 @@ nodeRoutes.post("/", async (c) => {
|
|
|
6208
6241
|
nodeRoutes.get("/:nodeId{.+}/stewards", async (c) => {
|
|
6209
6242
|
const nestId = c.req.param("nestId");
|
|
6210
6243
|
const nodeId = c.req.param("nodeId");
|
|
6211
|
-
const { resolveStewardsWithFallback: resolveStewardsWithFallback2 } = await import("./stewardship-service-
|
|
6244
|
+
const { resolveStewardsWithFallback: resolveStewardsWithFallback2 } = await import("./stewardship-service-4DHNRULG.js");
|
|
6212
6245
|
const { stewards, fallbackToOwner, ownerEmail } = await resolveStewardsWithFallback2(
|
|
6213
6246
|
nestId,
|
|
6214
6247
|
nodeId
|
|
@@ -6229,7 +6262,7 @@ nodeRoutes.get("/:nodeId{.+}/stewards", async (c) => {
|
|
|
6229
6262
|
nodeRoutes.get("/:nodeId{.+}/reviews", async (c) => {
|
|
6230
6263
|
const nestId = c.req.param("nestId");
|
|
6231
6264
|
const nodeId = c.req.param("nodeId");
|
|
6232
|
-
const { getReviewHistory: getReviewHistory2 } = await import("./review-service-
|
|
6265
|
+
const { getReviewHistory: getReviewHistory2 } = await import("./review-service-HHGOTO6P.js");
|
|
6233
6266
|
const history = await getReviewHistory2(nestId, nodeId);
|
|
6234
6267
|
return c.json({ reviews: history });
|
|
6235
6268
|
});
|
|
@@ -6368,7 +6401,8 @@ nodeRoutes.get("/:nodeId{.+}", async (c) => {
|
|
|
6368
6401
|
}
|
|
6369
6402
|
}
|
|
6370
6403
|
response.myGrant = await resolveNodeGrant(nestId, userId, nodeId);
|
|
6371
|
-
|
|
6404
|
+
const approvedOnly = c.req.query("approved_only") === "1" || c.req.query("approved_only") === "true";
|
|
6405
|
+
if (publicReader || approvedOnly) {
|
|
6372
6406
|
const approved = await getApprovedVersion(nestId, nodeId);
|
|
6373
6407
|
if (approved != null) {
|
|
6374
6408
|
let approvedRaw = "";
|
|
@@ -6399,6 +6433,9 @@ nodeRoutes.get("/:nodeId{.+}", async (c) => {
|
|
|
6399
6433
|
}
|
|
6400
6434
|
return c.json({ node: response });
|
|
6401
6435
|
}
|
|
6436
|
+
if (approvedOnly) {
|
|
6437
|
+
throw new NotFoundError("This document has no approved version yet.");
|
|
6438
|
+
}
|
|
6402
6439
|
}
|
|
6403
6440
|
response.status = node.pendingChange ? "external_edit_pending" : await getDisplayStatus(nestId, nodeId);
|
|
6404
6441
|
if (response.status === "pending_review") {
|
|
@@ -6423,6 +6460,9 @@ nodeRoutes.patch("/:nodeId{.+}", async (c) => {
|
|
|
6423
6460
|
const nestId = c.req.param("nestId");
|
|
6424
6461
|
const nodeId = c.req.param("nodeId");
|
|
6425
6462
|
const body = await c.req.json();
|
|
6463
|
+
if (body.type !== void 0 && typeof body.type !== "string") {
|
|
6464
|
+
throw new ValidationError("type must be a string");
|
|
6465
|
+
}
|
|
6426
6466
|
const baseVersionHeader = c.req.header("X-Base-Version");
|
|
6427
6467
|
if (baseVersionHeader) {
|
|
6428
6468
|
const baseVersion = parseInt(baseVersionHeader, 10);
|
|
@@ -6453,7 +6493,8 @@ nodeRoutes.patch("/:nodeId{.+}", async (c) => {
|
|
|
6453
6493
|
status: body.status,
|
|
6454
6494
|
changeNote: body.changeNote,
|
|
6455
6495
|
schedule: body.schedule,
|
|
6456
|
-
prime: body.prime
|
|
6496
|
+
prime: body.prime,
|
|
6497
|
+
type: body.type
|
|
6457
6498
|
},
|
|
6458
6499
|
authorEmail
|
|
6459
6500
|
);
|
|
@@ -6957,7 +6998,18 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
6957
6998
|
"ok",
|
|
6958
6999
|
"please",
|
|
6959
7000
|
"thanks",
|
|
6960
|
-
"thank"
|
|
7001
|
+
"thank",
|
|
7002
|
+
// Greetings and fillers a chat turn opens with — never a topic.
|
|
7003
|
+
"hello",
|
|
7004
|
+
"hi",
|
|
7005
|
+
"hey",
|
|
7006
|
+
"there",
|
|
7007
|
+
"good",
|
|
7008
|
+
"morning",
|
|
7009
|
+
"afternoon",
|
|
7010
|
+
"evening",
|
|
7011
|
+
"bye",
|
|
7012
|
+
"cheers"
|
|
6961
7013
|
]);
|
|
6962
7014
|
function tokenizePrompt(prompt) {
|
|
6963
7015
|
const words = prompt.toLowerCase().split(/[^a-z0-9_-]+/).filter(Boolean).filter((w) => w.length >= 3 && !STOPWORDS.has(w));
|
|
@@ -6999,6 +7051,34 @@ async function compilePrompt(prompt, nestId, titles) {
|
|
|
6999
7051
|
unmatched
|
|
7000
7052
|
};
|
|
7001
7053
|
}
|
|
7054
|
+
function scorePrompt(doc, tokens) {
|
|
7055
|
+
if (tokens.length === 0) return { score: 0, via: null };
|
|
7056
|
+
const title = doc.frontmatter.title || "";
|
|
7057
|
+
const tags = (doc.frontmatter.tags || []).map(normalizeTag);
|
|
7058
|
+
const body = (doc.body || "").toLowerCase();
|
|
7059
|
+
let sum = 0;
|
|
7060
|
+
let via = null;
|
|
7061
|
+
for (const t of tokens) {
|
|
7062
|
+
const inTitle = titleScore(title, t);
|
|
7063
|
+
if (inTitle > 0) {
|
|
7064
|
+
sum += inTitle;
|
|
7065
|
+
via = "title";
|
|
7066
|
+
} else if (tags.some((tag) => tag.startsWith(t))) {
|
|
7067
|
+
sum += 2;
|
|
7068
|
+
if (via !== "title") via = "tag";
|
|
7069
|
+
} else if (startsWord(body, t)) {
|
|
7070
|
+
sum += 1;
|
|
7071
|
+
via ??= "fulltext";
|
|
7072
|
+
}
|
|
7073
|
+
}
|
|
7074
|
+
return { score: sum / (4 * tokens.length), via };
|
|
7075
|
+
}
|
|
7076
|
+
function startsWord(text, term) {
|
|
7077
|
+
for (let i = text.indexOf(term); i !== -1; i = text.indexOf(term, i + 1)) {
|
|
7078
|
+
if (i === 0 || !/[a-z0-9]/.test(text[i - 1])) return true;
|
|
7079
|
+
}
|
|
7080
|
+
return false;
|
|
7081
|
+
}
|
|
7002
7082
|
|
|
7003
7083
|
// src/nodes/readable-body.ts
|
|
7004
7084
|
async function resolveReadableBody(nestId, nodeId, userId, workingBody) {
|
|
@@ -7393,7 +7473,8 @@ queryRoutes.post("/context", async (c) => {
|
|
|
7393
7473
|
const baseUrl = requestBaseUrl(c.req.url);
|
|
7394
7474
|
const { query: queryEngine, storage } = await engineCache.get(nestId);
|
|
7395
7475
|
const maxTokens = Math.max(50, body.max_tokens ?? 4e3);
|
|
7396
|
-
const
|
|
7476
|
+
const promptPath = !body.selector?.trim() && !!body.prompt;
|
|
7477
|
+
const hops = body.hops ?? (promptPath ? 0 : 2);
|
|
7397
7478
|
const includeDrafts = body.include_drafts === true;
|
|
7398
7479
|
const isVisible = (d) => includeDrafts || d.frontmatter.status === "published";
|
|
7399
7480
|
let selector = body.selector?.trim() || null;
|
|
@@ -7427,7 +7508,7 @@ queryRoutes.post("/context", async (c) => {
|
|
|
7427
7508
|
compileDetail.matchedTitles.map((t) => t.toLowerCase())
|
|
7428
7509
|
);
|
|
7429
7510
|
titleMatches = allDocs.filter(
|
|
7430
|
-
(d) => matchedLower.has(d.frontmatter.title.toLowerCase())
|
|
7511
|
+
(d) => matchedLower.has(d.frontmatter.title.toLowerCase()) && isVisible(d)
|
|
7431
7512
|
);
|
|
7432
7513
|
}
|
|
7433
7514
|
}
|
|
@@ -7491,6 +7572,27 @@ queryRoutes.post("/context", async (c) => {
|
|
|
7491
7572
|
}
|
|
7492
7573
|
}
|
|
7493
7574
|
}
|
|
7575
|
+
const relevance = /* @__PURE__ */ new Map();
|
|
7576
|
+
let fallback = null;
|
|
7577
|
+
if (compileDetail) {
|
|
7578
|
+
const tokens = tokenizePrompt(body.prompt);
|
|
7579
|
+
const compiled = new Set(documents.map((d) => d.id));
|
|
7580
|
+
for (const d of allDocs) {
|
|
7581
|
+
const { score, via } = scorePrompt(d, tokens);
|
|
7582
|
+
if (compiled.has(d.id)) relevance.set(d.id, { score, via: via ?? "hop" });
|
|
7583
|
+
else if (score > 0 && isVisible(d)) {
|
|
7584
|
+
relevance.set(d.id, { score, via });
|
|
7585
|
+
documents.push(d);
|
|
7586
|
+
}
|
|
7587
|
+
}
|
|
7588
|
+
if (compiled.size === 0 && documents.length > 0) fallback = "fulltext";
|
|
7589
|
+
const top = Math.max(0, ...documents.map((d) => relevance.get(d.id).score));
|
|
7590
|
+
documents = documents.filter((d) => {
|
|
7591
|
+
const r = relevance.get(d.id);
|
|
7592
|
+
return compiled.has(d.id) || r.via === "hop" || r.score >= top / 2;
|
|
7593
|
+
});
|
|
7594
|
+
documents.sort((a, b) => relevance.get(b.id).score - relevance.get(a.id).score);
|
|
7595
|
+
}
|
|
7494
7596
|
const userId = c.get("userId");
|
|
7495
7597
|
const userEmail = await resolveCallerEmail(userId);
|
|
7496
7598
|
const beforePermission = documents.length;
|
|
@@ -7508,10 +7610,14 @@ queryRoutes.post("/context", async (c) => {
|
|
|
7508
7610
|
for (const doc of readable) {
|
|
7509
7611
|
const block = formatContextBlock(doc, doc.url);
|
|
7510
7612
|
const blockTokens = approxTokens(block);
|
|
7511
|
-
if (tokenCount + blockTokens > maxTokens
|
|
7613
|
+
if (tokenCount + blockTokens > maxTokens) continue;
|
|
7512
7614
|
included.push(doc);
|
|
7513
7615
|
tokenCount += blockTokens;
|
|
7514
7616
|
}
|
|
7617
|
+
if (included.length === 0 && readable.length > 0) {
|
|
7618
|
+
included.push(readable[0]);
|
|
7619
|
+
tokenCount = approxTokens(formatContextBlock(readable[0], readable[0].url));
|
|
7620
|
+
}
|
|
7515
7621
|
const truncatedByBudget = accessible.length - included.length;
|
|
7516
7622
|
const contextMarkdown = included.map((n) => formatContextBlock(n, n.url)).join("\n\n---\n\n");
|
|
7517
7623
|
trackEvent("context.resolve", {
|
|
@@ -7528,7 +7634,8 @@ queryRoutes.post("/context", async (c) => {
|
|
|
7528
7634
|
title: n.frontmatter.title,
|
|
7529
7635
|
url: n.url,
|
|
7530
7636
|
tags: n.frontmatter.tags || [],
|
|
7531
|
-
type: n.frontmatter.type || "document"
|
|
7637
|
+
type: n.frontmatter.type || "document",
|
|
7638
|
+
...relevance.get(n.id)
|
|
7532
7639
|
})),
|
|
7533
7640
|
trace: {
|
|
7534
7641
|
selector_used: selector,
|
|
@@ -7536,7 +7643,8 @@ queryRoutes.post("/context", async (c) => {
|
|
|
7536
7643
|
compiled: compileDetail ? {
|
|
7537
7644
|
matched_tags: compileDetail.matchedTags,
|
|
7538
7645
|
matched_titles: compileDetail.matchedTitles,
|
|
7539
|
-
unmatched_tokens: compileDetail.unmatched
|
|
7646
|
+
unmatched_tokens: compileDetail.unmatched,
|
|
7647
|
+
fallback
|
|
7540
7648
|
} : null,
|
|
7541
7649
|
total_matched: documents.length,
|
|
7542
7650
|
permission_filtered: permissionFiltered,
|
|
@@ -7721,11 +7829,11 @@ queryRoutes.get("/export", async (c) => {
|
|
|
7721
7829
|
let tokens = 0;
|
|
7722
7830
|
for (const f of fields) {
|
|
7723
7831
|
const t = approxTokens(nodeToMarkdown(f));
|
|
7724
|
-
if (tokens + t > maxTokens
|
|
7832
|
+
if (tokens + t > maxTokens) continue;
|
|
7725
7833
|
kept.push(f);
|
|
7726
7834
|
tokens += t;
|
|
7727
7835
|
}
|
|
7728
|
-
included = kept;
|
|
7836
|
+
included = kept.length > 0 ? kept : fields.slice(0, 1);
|
|
7729
7837
|
}
|
|
7730
7838
|
trackEvent("nest.export", {
|
|
7731
7839
|
nestId,
|
|
@@ -10256,21 +10364,20 @@ ${list}${more}
|
|
|
10256
10364
|
Submit one with \`context_submit_review\`.`;
|
|
10257
10365
|
}
|
|
10258
10366
|
case "context_submit_review": {
|
|
10259
|
-
const
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
if (!node) return toolError(`Node not found: ${args.title}`, "DOCUMENT_NOT_FOUND");
|
|
10367
|
+
const resolved = await resolveTargetNode(storage, args);
|
|
10368
|
+
if (typeof resolved === "string") return toolError(resolved, "DOCUMENT_NOT_FOUND");
|
|
10369
|
+
const node = resolved;
|
|
10370
|
+
const nodeLabel = args.title || args.id;
|
|
10264
10371
|
const submitCheck = await canUserEdit(ctx.nestId, node.id, userEmail);
|
|
10265
10372
|
if (!submitCheck.allowed) {
|
|
10266
10373
|
return toolError(
|
|
10267
|
-
`You don't have permission to submit "${
|
|
10374
|
+
`You don't have permission to submit "${nodeLabel}" for review: ${submitCheck.reason}`,
|
|
10268
10375
|
"UNAUTHORIZED_ACTION"
|
|
10269
10376
|
);
|
|
10270
10377
|
}
|
|
10271
10378
|
const currentVersion = await getCurrentVersion(ctx.nestId, node.id);
|
|
10272
10379
|
if (currentVersion === 0)
|
|
10273
|
-
return toolError(`No versions found for "${
|
|
10380
|
+
return toolError(`No versions found for "${nodeLabel}"`, "VERSION_NOT_FOUND");
|
|
10274
10381
|
try {
|
|
10275
10382
|
const request = await submitForReview({
|
|
10276
10383
|
nestId: ctx.nestId,
|
|
@@ -10281,16 +10388,16 @@ Submit one with \`context_submit_review\`.`;
|
|
|
10281
10388
|
priority: args.priority,
|
|
10282
10389
|
baseUrl: ctx.baseUrl
|
|
10283
10390
|
});
|
|
10284
|
-
const
|
|
10391
|
+
const resolved2 = await resolveStewardsForNode(
|
|
10285
10392
|
ctx.nestId,
|
|
10286
10393
|
node.id
|
|
10287
10394
|
);
|
|
10288
|
-
const stewardList =
|
|
10395
|
+
const stewardList = resolved2.length > 0 ? `
|
|
10289
10396
|
|
|
10290
10397
|
**Stewards who can review:**
|
|
10291
|
-
${
|
|
10398
|
+
${resolved2.map((r) => `- ${r.steward.userEmail} (${r.source})`).join("\n")}` : "";
|
|
10292
10399
|
return {
|
|
10293
|
-
text: `Submitted "${
|
|
10400
|
+
text: `Submitted "${nodeLabel}" v${currentVersion} for review (${request.priority} priority).${stewardList}`,
|
|
10294
10401
|
// No catalog shape to conform to — `context_submit_review` is a
|
|
10295
10402
|
// governance-namespace operation and that namespace is declared but
|
|
10296
10403
|
// unimplemented in the engine (`api/index.ts`, NAMESPACES). So this
|
|
@@ -10311,7 +10418,7 @@ ${resolved.map((r) => `- ${r.steward.userEmail} (${r.source})`).join("\n")}` : "
|
|
|
10311
10418
|
requested_by: request.requestedBy,
|
|
10312
10419
|
requested_at: request.requestedAt
|
|
10313
10420
|
},
|
|
10314
|
-
reviewers:
|
|
10421
|
+
reviewers: resolved2.map((r) => r.steward.userEmail)
|
|
10315
10422
|
}
|
|
10316
10423
|
};
|
|
10317
10424
|
} catch (err) {
|
|
@@ -10456,8 +10563,8 @@ ${list}`,
|
|
|
10456
10563
|
if (!target) return "target is required (a node id or folder prefix).";
|
|
10457
10564
|
if (!["read", "write"].includes(role)) return "role must be read or write.";
|
|
10458
10565
|
try {
|
|
10459
|
-
const { createGrant: createGrant2 } = await import("./grants-service-
|
|
10460
|
-
const db = (await import("./client-
|
|
10566
|
+
const { createGrant: createGrant2 } = await import("./grants-service-UT3EQ3R7.js");
|
|
10567
|
+
const db = (await import("./client-KEY4PYJH.js")).getDb();
|
|
10461
10568
|
const { normalizeEmail: normalizeEmail2 } = await import("./email-R7DFS6E5.js");
|
|
10462
10569
|
const e = normalizeEmail2(String(args.email || ""));
|
|
10463
10570
|
if (!e) return "email is required.";
|
|
@@ -11747,7 +11854,20 @@ import { Hono as Hono23 } from "hono";
|
|
|
11747
11854
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11748
11855
|
import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport2 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
11749
11856
|
import { z as z2 } from "zod";
|
|
11750
|
-
var SERVER_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
11857
|
+
var SERVER_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
11858
|
+
"nest_create",
|
|
11859
|
+
"context_create",
|
|
11860
|
+
"context_update",
|
|
11861
|
+
"context_delete",
|
|
11862
|
+
"context_move",
|
|
11863
|
+
"context_submit_review"
|
|
11864
|
+
]);
|
|
11865
|
+
var FANOUT_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
11866
|
+
"context_search",
|
|
11867
|
+
"context_list",
|
|
11868
|
+
"context_query",
|
|
11869
|
+
"context_overview"
|
|
11870
|
+
]);
|
|
11751
11871
|
async function getUserEmail3(userId) {
|
|
11752
11872
|
const db = getDb();
|
|
11753
11873
|
const user = await db.get("SELECT email FROM users WHERE id = ?", [
|
|
@@ -11847,77 +11967,83 @@ var NEST_ARG = {
|
|
|
11847
11967
|
description: "Target nest \u2014 id, or exact name/slug (case-insensitive)"
|
|
11848
11968
|
}
|
|
11849
11969
|
};
|
|
11970
|
+
var NEST_ARG_OPTIONAL = {
|
|
11971
|
+
nest: {
|
|
11972
|
+
type: "string",
|
|
11973
|
+
description: "Target nest \u2014 id or name. Omit to search/list across ALL accessible nests."
|
|
11974
|
+
}
|
|
11975
|
+
};
|
|
11850
11976
|
var SERVER_TOOL_DEFINITIONS = [
|
|
11851
11977
|
{
|
|
11852
11978
|
name: "nest_index",
|
|
11853
|
-
description: "Grounded index of every nest these credentials can read: name, id, permission, document count. Call this FIRST
|
|
11979
|
+
description: "Grounded index of every nest these credentials can read: name, id, permission, document count. Call this FIRST.",
|
|
11854
11980
|
inputSchema: { type: "object", properties: {} }
|
|
11855
11981
|
},
|
|
11982
|
+
// ─── Fan-out reads (nest optional) ─────────────────────────────────────
|
|
11856
11983
|
{
|
|
11857
11984
|
name: "context_query",
|
|
11858
|
-
description: "Run a structured selector query
|
|
11985
|
+
description: "Run a structured selector query. Omit `nest` to query ALL accessible nests. Selector: #tag, type:X, [[Title]], scope:X, +AND, |OR, -NOT.",
|
|
11859
11986
|
inputSchema: {
|
|
11860
11987
|
type: "object",
|
|
11861
11988
|
properties: {
|
|
11862
|
-
...
|
|
11989
|
+
...NEST_ARG_OPTIONAL,
|
|
11863
11990
|
query: { type: "string", description: "Selector query" },
|
|
11864
11991
|
hops: {
|
|
11865
11992
|
type: "number",
|
|
11866
11993
|
description: "Graph traversal depth from the matches (default: 2)"
|
|
11867
11994
|
}
|
|
11868
11995
|
},
|
|
11869
|
-
required: ["
|
|
11996
|
+
required: ["query"]
|
|
11870
11997
|
}
|
|
11871
11998
|
},
|
|
11872
11999
|
{
|
|
11873
12000
|
name: "context_search",
|
|
11874
|
-
description: "Full-text keyword search
|
|
12001
|
+
description: "Full-text keyword search. Omit `nest` to search ALL accessible nests. Returns count and first `limit` hits.",
|
|
11875
12002
|
inputSchema: {
|
|
11876
12003
|
type: "object",
|
|
11877
12004
|
properties: {
|
|
11878
|
-
...
|
|
12005
|
+
...NEST_ARG_OPTIONAL,
|
|
11879
12006
|
query: { type: "string", description: "Search terms" },
|
|
11880
12007
|
limit: {
|
|
11881
12008
|
type: "number",
|
|
11882
12009
|
description: "Max hits to return (default 20, max 200)"
|
|
11883
12010
|
}
|
|
11884
12011
|
},
|
|
11885
|
-
required: ["
|
|
11886
|
-
}
|
|
11887
|
-
},
|
|
11888
|
-
{
|
|
11889
|
-
name: "context_get",
|
|
11890
|
-
description: "Get the FULL content of a node in a nest by title or id.",
|
|
11891
|
-
inputSchema: {
|
|
11892
|
-
type: "object",
|
|
11893
|
-
properties: {
|
|
11894
|
-
...NEST_ARG,
|
|
11895
|
-
title: { type: "string", description: "Title of the node" },
|
|
11896
|
-
id: { type: "string", description: "ID of the node" }
|
|
11897
|
-
},
|
|
11898
|
-
required: ["nest"]
|
|
12012
|
+
required: ["query"]
|
|
11899
12013
|
}
|
|
11900
12014
|
},
|
|
11901
12015
|
{
|
|
11902
12016
|
name: "context_list",
|
|
11903
|
-
description: "Browse
|
|
12017
|
+
description: "Browse contents with optional type, tag, or limit filters. Omit `nest` to list ALL accessible nests.",
|
|
11904
12018
|
inputSchema: {
|
|
11905
12019
|
type: "object",
|
|
11906
12020
|
properties: {
|
|
11907
|
-
...
|
|
12021
|
+
...NEST_ARG_OPTIONAL,
|
|
11908
12022
|
type: { type: "string", description: "Filter by node type" },
|
|
11909
12023
|
tag: { type: "string", description: "Filter by tag" },
|
|
11910
12024
|
limit: { type: "number", description: "Max nodes to return" }
|
|
11911
|
-
}
|
|
11912
|
-
required: ["nest"]
|
|
12025
|
+
}
|
|
11913
12026
|
}
|
|
11914
12027
|
},
|
|
11915
12028
|
{
|
|
11916
12029
|
name: "context_overview",
|
|
11917
|
-
description: "Map of
|
|
12030
|
+
description: "Map of nests: node count, types, tags, title+snippet per node. Omit `nest` for ALL accessible nests.",
|
|
11918
12031
|
inputSchema: {
|
|
11919
12032
|
type: "object",
|
|
11920
|
-
properties: { ...
|
|
12033
|
+
properties: { ...NEST_ARG_OPTIONAL }
|
|
12034
|
+
}
|
|
12035
|
+
},
|
|
12036
|
+
// ─── Targeted reads (nest required) ────────────────────────────────────
|
|
12037
|
+
{
|
|
12038
|
+
name: "context_get",
|
|
12039
|
+
description: "Get the FULL content of a node in a nest by title or id.",
|
|
12040
|
+
inputSchema: {
|
|
12041
|
+
type: "object",
|
|
12042
|
+
properties: {
|
|
12043
|
+
...NEST_ARG,
|
|
12044
|
+
title: { type: "string", description: "Title of the node" },
|
|
12045
|
+
id: { type: "string", description: "ID of the node" }
|
|
12046
|
+
},
|
|
11921
12047
|
required: ["nest"]
|
|
11922
12048
|
}
|
|
11923
12049
|
},
|
|
@@ -11969,9 +12095,10 @@ var SERVER_TOOL_DEFINITIONS = [
|
|
|
11969
12095
|
required: ["name"]
|
|
11970
12096
|
}
|
|
11971
12097
|
},
|
|
12098
|
+
// ─── Writes (nest required) ────────────────────────────────────────────
|
|
11972
12099
|
{
|
|
11973
12100
|
name: "context_create",
|
|
11974
|
-
description: "Create a new
|
|
12101
|
+
description: "Create a new document in a nest. Needs write permission. A `folder` path creates the folder implicitly.",
|
|
11975
12102
|
inputSchema: {
|
|
11976
12103
|
type: "object",
|
|
11977
12104
|
properties: {
|
|
@@ -11988,6 +12115,66 @@ var SERVER_TOOL_DEFINITIONS = [
|
|
|
11988
12115
|
},
|
|
11989
12116
|
required: ["nest", "title", "content"]
|
|
11990
12117
|
}
|
|
12118
|
+
},
|
|
12119
|
+
{
|
|
12120
|
+
name: "context_update",
|
|
12121
|
+
description: "Update an existing node by title or id. Needs write permission. Use `content` to replace, `append` to add.",
|
|
12122
|
+
inputSchema: {
|
|
12123
|
+
type: "object",
|
|
12124
|
+
properties: {
|
|
12125
|
+
...NEST_ARG,
|
|
12126
|
+
title: { type: "string", description: "Title of node to update" },
|
|
12127
|
+
id: { type: "string", description: "Node id (use when title is ambiguous)" },
|
|
12128
|
+
content: { type: "string", description: "New content (replaces)" },
|
|
12129
|
+
append: { type: "string", description: "Content to append" },
|
|
12130
|
+
tags: { type: "array", items: { type: "string" }, description: "Tags to add" },
|
|
12131
|
+
status: { type: "string", description: "New status: draft|pending_review|approved|published" },
|
|
12132
|
+
changeNote: { type: "string", description: "Why this changed \u2014 recorded in history" }
|
|
12133
|
+
},
|
|
12134
|
+
required: ["nest"]
|
|
12135
|
+
}
|
|
12136
|
+
},
|
|
12137
|
+
{
|
|
12138
|
+
name: "context_delete",
|
|
12139
|
+
description: "Delete a node by title or id. Needs write permission. Irreversible.",
|
|
12140
|
+
inputSchema: {
|
|
12141
|
+
type: "object",
|
|
12142
|
+
properties: {
|
|
12143
|
+
...NEST_ARG,
|
|
12144
|
+
title: { type: "string", description: "Title of node to delete" },
|
|
12145
|
+
id: { type: "string", description: "Node id (use when title is ambiguous)" }
|
|
12146
|
+
},
|
|
12147
|
+
required: ["nest"]
|
|
12148
|
+
}
|
|
12149
|
+
},
|
|
12150
|
+
{
|
|
12151
|
+
name: "context_move",
|
|
12152
|
+
description: "Move a node to a different folder. The id changes; history and links follow. Needs write permission.",
|
|
12153
|
+
inputSchema: {
|
|
12154
|
+
type: "object",
|
|
12155
|
+
properties: {
|
|
12156
|
+
...NEST_ARG,
|
|
12157
|
+
title: { type: "string", description: "Title of node to move" },
|
|
12158
|
+
id: { type: "string", description: "Node id (use when title is ambiguous)" },
|
|
12159
|
+
folder: { type: "string", description: 'Destination folder (e.g. "gtm/deals"); "" for root' }
|
|
12160
|
+
},
|
|
12161
|
+
required: ["nest", "folder"]
|
|
12162
|
+
}
|
|
12163
|
+
},
|
|
12164
|
+
{
|
|
12165
|
+
name: "context_submit_review",
|
|
12166
|
+
description: "Submit a node for steward review. Stewards will be notified. Use on governed nests.",
|
|
12167
|
+
inputSchema: {
|
|
12168
|
+
type: "object",
|
|
12169
|
+
properties: {
|
|
12170
|
+
...NEST_ARG,
|
|
12171
|
+
title: { type: "string", description: "Title of node to submit" },
|
|
12172
|
+
id: { type: "string", description: "Node id (use when title is ambiguous)" },
|
|
12173
|
+
note: { type: "string", description: "Note for the reviewer" },
|
|
12174
|
+
priority: { type: "string", description: "Priority: low, normal, high, urgent" }
|
|
12175
|
+
},
|
|
12176
|
+
required: ["nest"]
|
|
12177
|
+
}
|
|
11991
12178
|
}
|
|
11992
12179
|
];
|
|
11993
12180
|
async function handleServerToolCall(toolName, args, userId, userEmail, nestScope, baseUrl) {
|
|
@@ -12021,8 +12208,49 @@ async function handleServerToolCall(toolName, args, userId, userEmail, nestScope
|
|
|
12021
12208
|
throw err;
|
|
12022
12209
|
}
|
|
12023
12210
|
}
|
|
12024
|
-
const
|
|
12025
|
-
|
|
12211
|
+
const nestArg = args.nest;
|
|
12212
|
+
if (!nestArg && FANOUT_READ_TOOLS.has(toolName)) {
|
|
12213
|
+
const nests = await buildNestIndex(userId, nestScope);
|
|
12214
|
+
if (!nests.length) return "No nests accessible with these credentials.";
|
|
12215
|
+
const nonEmpty = nests.filter((n) => (n.document_count ?? 0) > 0);
|
|
12216
|
+
if (!nonEmpty.length) {
|
|
12217
|
+
return "All accessible nests are empty. Use context_create to add documents first.";
|
|
12218
|
+
}
|
|
12219
|
+
const results = await Promise.allSettled(
|
|
12220
|
+
nonEmpty.map(async (n) => {
|
|
12221
|
+
const engine2 = await engineCache.get(n.id);
|
|
12222
|
+
const result = await handleToolCall(toolName, args, {
|
|
12223
|
+
storage: engine2.storage,
|
|
12224
|
+
queryEngine: engine2.query,
|
|
12225
|
+
versionManager: engine2.versions,
|
|
12226
|
+
nestId: n.id,
|
|
12227
|
+
userId,
|
|
12228
|
+
userEmail,
|
|
12229
|
+
baseUrl
|
|
12230
|
+
});
|
|
12231
|
+
return { nest: n, result };
|
|
12232
|
+
})
|
|
12233
|
+
);
|
|
12234
|
+
const parts = [];
|
|
12235
|
+
for (const r of results) {
|
|
12236
|
+
if (r.status === "fulfilled") {
|
|
12237
|
+
const { nest: n, result } = r.value;
|
|
12238
|
+
const text = typeof result === "string" ? result : result.text;
|
|
12239
|
+
parts.push(`## ${n.name} (${n.id})
|
|
12240
|
+
|
|
12241
|
+
${text}`);
|
|
12242
|
+
}
|
|
12243
|
+
}
|
|
12244
|
+
if (!parts.length) {
|
|
12245
|
+
return "No results found across any accessible nests.";
|
|
12246
|
+
}
|
|
12247
|
+
return parts.join("\n\n---\n\n");
|
|
12248
|
+
}
|
|
12249
|
+
const inaccessible = `Nest not found or not accessible: "${nestArg}". Use nest_index to see what these credentials can read.`;
|
|
12250
|
+
if (!nestArg) {
|
|
12251
|
+
return "This tool requires a `nest` argument. Use nest_index to see available nests.";
|
|
12252
|
+
}
|
|
12253
|
+
const nest = await resolveNestRef(String(nestArg), userId);
|
|
12026
12254
|
if (!nest) return inaccessible;
|
|
12027
12255
|
if (nestScope && nest.id !== nestScope) return inaccessible;
|
|
12028
12256
|
if (!await canRead(nest.id, userId)) return inaccessible;
|
|
@@ -12164,26 +12392,47 @@ Other read endpoints (all under \`${B}/nests/<nest-id>\`):
|
|
|
12164
12392
|
- \`GET /nodes/<node-id>?format=raw\` \u2014 direct consumption path: the node BODY as plain text (no JSON envelope; add \`&frontmatter=1\` for the stored YAML block)
|
|
12165
12393
|
- \`GET /export?format=markdown\` \u2014 whole nest as one markdown file (optional \`selector\`, \`max_tokens\`)
|
|
12166
12394
|
- \`GET /graph\` \u2014 ontology graph (nodes, links, tags, stewards)
|
|
12395
|
+
- \`GET /folders\` \u2014 folder tree; \`GET /nodes?folder=<path>\` \u2014 one folder level
|
|
12396
|
+
- \`POST /table-query\` \u2014 exact rows from a table node: \`{ "table": "[[Title]]", "query": "where <col> <op> <value> [select cols] [limit N]" }\`
|
|
12397
|
+
- \`GET /nodes/<node-id>/versions\` \u2014 history (who, when, approval state); \`/versions/<n>\` \u2014 one version
|
|
12398
|
+
- \`GET /nodes/<node-id>/comments\` \u2014 review threads (anchored quotes, replies, open/resolved)
|
|
12167
12399
|
|
|
12168
12400
|
Write endpoints (need write permission):
|
|
12169
12401
|
- \`POST /nodes\` \u2014 \`{ "title", "content", "tags"?, "type"?, "folder"? }\`
|
|
12170
12402
|
- \`PATCH /nodes/<node-id>\` \u2014 \`{ "content"? , "append"?, "tags"?, "title"? }\`
|
|
12171
|
-
- \`DELETE /nodes/<node-id
|
|
12172
|
-
-
|
|
12403
|
+
- \`DELETE /nodes/<node-id>\`; \`POST /nodes/<id>/request-deletion\` when you lack delete rights
|
|
12404
|
+
- \`POST /nodes/<id>/move\` \u2014 \`{ "folder" }\`; history and comments follow
|
|
12405
|
+
- \`POST /nodes/<id>/revert\` \u2014 \`{ "targetVersion" }\` restores a past version as a new one
|
|
12406
|
+
- \`POST /nodes/<id>/comments\` \u2014 leave or reply to a review comment
|
|
12407
|
+
- Governance: \`POST /nodes/<id>/submit-review\`, \`/approve\`, \`/reject\`, \`/cancel-review\`
|
|
12408
|
+
- \`GET /review-queue\` \u2014 what is waiting on a steward in this nest
|
|
12409
|
+
|
|
12410
|
+
## Your inbox (server-level)
|
|
12411
|
+
|
|
12412
|
+
- \`GET ${B}/me/work\` \u2014 reviews waiting on you and handoffs, across every nest
|
|
12413
|
+
- \`GET ${B}/me/drafts\` \u2014 your documents never submitted for review
|
|
12414
|
+
- \`GET ${B}/me/notifications\` \u2014 watcher and review notifications
|
|
12173
12415
|
|
|
12174
12416
|
## MCP (Model Context Protocol)
|
|
12175
12417
|
|
|
12176
12418
|
Streamable HTTP, stateless. Two endpoints:
|
|
12177
12419
|
|
|
12178
|
-
- \`${B}/mcp\` \u2014 server-level, one connection for everything
|
|
12179
|
-
Tools: \`nest_index\` (call first), then
|
|
12180
|
-
|
|
12181
|
-
|
|
12182
|
-
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
- \`${B}/nests/<nest-id>/mcp\` \u2014 per-nest, full toolset
|
|
12186
|
-
|
|
12420
|
+
- \`${B}/mcp\` \u2014 server-level, one connection for everything.
|
|
12421
|
+
Tools: \`nest_index\` (call first), then:
|
|
12422
|
+
- Fan-out reads (omit \`nest\` to search/list ALL nests): \`context_search\`,
|
|
12423
|
+
\`context_list\`, \`context_query\`, \`context_overview\`
|
|
12424
|
+
- Targeted reads (\`nest\` required): \`context_get\`, \`context_resolve\`, \`context_comments\`
|
|
12425
|
+
- Writes (\`nest\` required): \`nest_create\`, \`context_create\`, \`context_update\`,
|
|
12426
|
+
\`context_delete\`, \`context_move\`, \`context_submit_review\`
|
|
12427
|
+
- \`${B}/nests/<nest-id>/mcp\` \u2014 per-nest, the full toolset. Same names as the
|
|
12428
|
+
server-level tools without the \`nest\` argument, plus:
|
|
12429
|
+
- read: \`context_init\` (operating instructions \u2014 call first), \`context_export\`, \`context_table_query\`, \`context_versions\`
|
|
12430
|
+
- write: \`context_update\`, \`context_delete\`, \`context_move\`, \`context_request_deletion\`
|
|
12431
|
+
- review: \`context_submit_review\`, \`context_approve\`, \`context_reject\`, \`context_review_queue\`, \`context_my_drafts\`
|
|
12432
|
+
- comments: \`context_comment\`, \`context_resolve_comment\`
|
|
12433
|
+
- people: \`context_stewards\`, \`context_assign_steward\`, \`context_share_nest\`, \`context_grant\`
|
|
12434
|
+
- admin: \`context_deletion_queue\`, \`context_resolve_deletion\`, \`context_unsynced_list\`, \`context_sync_folder\`
|
|
12435
|
+
- \`workflow_*\` (runs, edges, edge types) when the nest's workflow plane is on
|
|
12187
12436
|
Sharing a slice is one call: \`context_grant\` with
|
|
12188
12437
|
\`{ "email", "target_type": "document" | "folder", "target", "role": "read" | "write" }\`
|
|
12189
12438
|
\u2014 \`target\` is a node id (\`nodes/win-loss-log\`) or a folder prefix
|
|
@@ -13022,6 +13271,7 @@ governanceRoutes.get("/external-edits", async (c) => {
|
|
|
13022
13271
|
await scanNestForDrift(nestId, "user:refresh");
|
|
13023
13272
|
}
|
|
13024
13273
|
const entries = await listNestExternalEdits(nestId);
|
|
13274
|
+
if (c.req.query("count") === "1") return c.json({ total: entries.length });
|
|
13025
13275
|
return c.json({ entries, total: entries.length });
|
|
13026
13276
|
});
|
|
13027
13277
|
governanceRoutes.post("/external-edits/scan", async (c) => {
|
|
@@ -13488,6 +13738,21 @@ async function ensureAnonymousUser() {
|
|
|
13488
13738
|
return ANON_USER_ID;
|
|
13489
13739
|
}
|
|
13490
13740
|
|
|
13741
|
+
// src/shared/posthog-key.ts
|
|
13742
|
+
var POSTHOG_KEY_MIN_TOKEN = 20;
|
|
13743
|
+
function posthogKeyError(value) {
|
|
13744
|
+
const v = value.trim();
|
|
13745
|
+
if (!v) return null;
|
|
13746
|
+
if (!v.startsWith("phc_"))
|
|
13747
|
+
return "PostHog project key must start with phc_ \u2014 copy the project API key from PostHog settings.";
|
|
13748
|
+
const token = v.slice(4);
|
|
13749
|
+
if (!/^[A-Za-z0-9]*$/.test(token))
|
|
13750
|
+
return "PostHog project key may only contain letters and digits after phc_ \u2014 check for spaces or punctuation.";
|
|
13751
|
+
if (token.length < POSTHOG_KEY_MIN_TOKEN)
|
|
13752
|
+
return `PostHog project key looks too short \u2014 a real key is about 47 characters long (phc_ plus ${POSTHOG_KEY_MIN_TOKEN}+ letters and digits). Copy the whole key from PostHog settings.`;
|
|
13753
|
+
return null;
|
|
13754
|
+
}
|
|
13755
|
+
|
|
13491
13756
|
// src/app.ts
|
|
13492
13757
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
13493
13758
|
import { fileURLToPath } from "url";
|
|
@@ -13901,7 +14166,9 @@ function createApp() {
|
|
|
13901
14166
|
}
|
|
13902
14167
|
if ("posthog_key" in body) {
|
|
13903
14168
|
const v = String(body.posthog_key ?? "").trim();
|
|
13904
|
-
|
|
14169
|
+
const keyError = posthogKeyError(v);
|
|
14170
|
+
if (keyError) addError("posthog_key", keyError);
|
|
14171
|
+
else pending.push({ name: "POSTHOG_KEY", value: v || null });
|
|
13905
14172
|
}
|
|
13906
14173
|
if ("posthog_host" in body) {
|
|
13907
14174
|
const v = String(body.posthog_host ?? "").trim().replace(/\/$/, "");
|
|
@@ -14666,7 +14933,7 @@ function createApp() {
|
|
|
14666
14933
|
const isCommentAction = /\/comments$/.test(path) || /\/comments\/[^/]+\/resolve$/.test(path);
|
|
14667
14934
|
const isDeletionRequestAction = /\/request-deletion$/.test(path) || /\/deletion-requests\/[^/]+\/withdraw$/.test(path);
|
|
14668
14935
|
const isStewardRoster = path.includes("/stewards") && !path.includes("/nodes/");
|
|
14669
|
-
if (isStewardRoster &&
|
|
14936
|
+
if (isStewardRoster && access.rawPermission !== "owner" && access.rawPermission !== "admin") {
|
|
14670
14937
|
return c.json(
|
|
14671
14938
|
{
|
|
14672
14939
|
error: "You don't have permission to manage stewards. Only a nest admin, the nest owner, or the server admin can do this."
|
|
@@ -14674,6 +14941,13 @@ function createApp() {
|
|
|
14674
14941
|
403
|
|
14675
14942
|
);
|
|
14676
14943
|
}
|
|
14944
|
+
if (isStewardRoster && access.archived && c.req.method !== "GET") {
|
|
14945
|
+
return deny(
|
|
14946
|
+
403,
|
|
14947
|
+
"This nest is archived, so it is read-only. Restore it to change stewards.",
|
|
14948
|
+
{ archived: true }
|
|
14949
|
+
);
|
|
14950
|
+
}
|
|
14677
14951
|
const resource = parts[1];
|
|
14678
14952
|
if (resource === "visibility" || resource === "reader-mode") {
|
|
14679
14953
|
required = "admin";
|
|
@@ -14712,6 +14986,13 @@ function createApp() {
|
|
|
14712
14986
|
const grantLevel = grantRole ? permissionLevel(grantRole) : grantNestVisible ? permissionLevel("read") : 0;
|
|
14713
14987
|
const effectiveLevel = Math.max(permissionLevel(permission), grantLevel);
|
|
14714
14988
|
if (!stewardEditorBypass && effectiveLevel < permissionLevel(required)) {
|
|
14989
|
+
if (access.archived && permissionLevel(access.rawPermission) >= permissionLevel(required)) {
|
|
14990
|
+
return deny(
|
|
14991
|
+
403,
|
|
14992
|
+
"This nest is archived, so it is read-only. Restore it to make changes.",
|
|
14993
|
+
{ archived: true }
|
|
14994
|
+
);
|
|
14995
|
+
}
|
|
14715
14996
|
return deny(
|
|
14716
14997
|
403,
|
|
14717
14998
|
`You don't have access to perform this action on this nest. Required permission: '${required}', your permission: '${permission}'. Ask the nest owner or a server admin to grant you ${required} access.`,
|