@promptowl/contextnest-community 1.23.0 → 1.25.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.
Files changed (91) hide show
  1. package/API.md +2195 -0
  2. package/CONFIGURATION.md +8 -2
  3. package/README.md +62 -10
  4. package/STEWARDSHIP.md +253 -0
  5. package/dist/{chunk-2YPY2HGZ.js → chunk-2KHM5C7V.js} +40 -5
  6. package/dist/{chunk-XYD6V2LH.js → chunk-32SZCH36.js} +28 -33
  7. package/dist/{chunk-QLXC6542.js → chunk-7FROMOZM.js} +69 -27
  8. package/dist/{chunk-MLQU4I5J.js → chunk-DTLMBF4W.js} +10 -8
  9. package/dist/{chunk-QWNXWRWO.js → chunk-JDAOA5KP.js} +67 -1
  10. package/dist/{chunk-UOMW7TT6.js → chunk-KPGNZLZL.js} +2 -2
  11. package/dist/{chunk-YVMSM7LS.js → chunk-LHWOJPFE.js} +7 -0
  12. package/dist/{chunk-5CVZMHHB.js → chunk-TI7HMP64.js} +99 -12
  13. package/dist/{client-LWDMNKX3.js → client-D44ZHDTJ.js} +1 -1
  14. package/dist/{engine-QFF2IA3G.js → engine-TER6FDBP.js} +3 -3
  15. package/dist/{external-edit-service-F7D3VSA2.js → external-edit-service-CAGENHDJ.js} +4 -4
  16. package/dist/{grants-service-TBGTCO5K.js → grants-service-2OTFS7EH.js} +3 -3
  17. package/dist/index.js +1120 -331
  18. package/dist/{migrations.postgres-APCVSYUE.js → migrations.postgres-23VJJJJX.js} +56 -5
  19. package/dist/{review-service-A7PNLWG5.js → review-service-4G2XUFS6.js} +7 -7
  20. package/dist/{stewardship-service-32UHIRQ5.js → stewardship-service-S22V2JKG.js} +6 -4
  21. package/dist/{version-service-RJ6CWZ3O.js → version-service-X4FAG2OZ.js} +4 -4
  22. package/dist/web3/assets/ActivityTracePage-D2iIbEph.js +1 -0
  23. package/dist/web3/assets/AgentDocsPage-Dkhgb1hk.js +1 -0
  24. package/dist/web3/assets/CollaboratorManager-Bo81D--Q.js +1 -0
  25. package/dist/web3/assets/CollaboratorsTab-DRCitQuX.js +1 -0
  26. package/dist/web3/assets/DocumentEditor-DV2WHYbj.js +36 -0
  27. package/dist/web3/assets/DocumentsTab-BUxBCN8o.js +6 -0
  28. package/dist/web3/assets/ExternalEditsTab-kIBimZyE.js +1 -0
  29. package/dist/web3/assets/MarkdownEditor-CsVusP2d.css +1 -0
  30. package/dist/web3/assets/MarkdownEditor-Dfk5uIE5.js +648 -0
  31. package/dist/web3/assets/NestPageHeader-yOK-OIYZ.js +1 -0
  32. package/dist/web3/assets/NestView-DwIagxwJ.js +68 -0
  33. package/dist/web3/assets/OverviewTab-DFOFMVyE.js +1 -0
  34. package/dist/web3/assets/PersonCombobox-D350WlrJ.js +1 -0
  35. package/dist/web3/assets/ReasonDialog-CN3ECAnQ.js +1 -0
  36. package/dist/web3/assets/ReviewActions-BXDTDPp1.js +6 -0
  37. package/dist/web3/assets/ReviewTab-CZewAYiz.js +1 -0
  38. package/dist/web3/assets/StewardsTab-AagNWcWS.js +1 -0
  39. package/dist/web3/assets/SubmitForReviewModal-BOZJxwRN.js +1 -0
  40. package/dist/web3/assets/alert-dialog-4SiLKlz8.js +7 -0
  41. package/dist/web3/assets/arrow-left-BAgIocLn.js +6 -0
  42. package/dist/web3/assets/backlinks-Cb8Uf8mw.js +24 -0
  43. package/dist/web3/assets/card-UDyMRcps.js +1 -0
  44. package/dist/web3/assets/chevron-left-XR4ReJ9Z.js +6 -0
  45. package/dist/web3/assets/circle-check-CSkZUEFK.js +6 -0
  46. package/dist/web3/assets/circle-x-in2o3aHU.js +6 -0
  47. package/dist/web3/assets/client-attribution-BraarYaP.js +1 -0
  48. package/dist/web3/assets/code-xml-pPQgdctI.js +6 -0
  49. package/dist/web3/assets/corner-down-right-CuxZWscX.js +6 -0
  50. package/dist/web3/assets/count-skeleton-DBWCDhRy.js +1 -0
  51. package/dist/web3/assets/dates-BCxbm4_q.js +1 -0
  52. package/dist/web3/assets/earth-CXOcThqn.js +6 -0
  53. package/dist/web3/assets/file-exclamation-point-CSphs1VD.js +6 -0
  54. package/dist/web3/assets/folder-input-BAwK9tFL.js +11 -0
  55. package/dist/web3/assets/folder-target-CUSWqImF.js +1 -0
  56. package/dist/web3/assets/index-A0_ymcqL.js +29 -0
  57. package/dist/web3/assets/index-B5hLclEq.js +389 -0
  58. package/dist/web3/assets/index-DrUAtoQM.css +1 -0
  59. package/dist/web3/assets/page-BHD4beun.js +1 -0
  60. package/dist/web3/assets/page-BSYUXYmO.js +11 -0
  61. package/dist/web3/assets/page-BYc2DIux.js +45 -0
  62. package/dist/web3/assets/page-BfibLg8e.js +1 -0
  63. package/dist/web3/assets/page-CGe8MtUu.js +9 -0
  64. package/dist/web3/assets/page-CIgPJmG6.js +2 -0
  65. package/dist/web3/assets/page-CUjuSs5Q.js +24 -0
  66. package/dist/web3/assets/page-Ck31nx9b.js +1 -0
  67. package/dist/web3/assets/page-Cpdj_11Y.js +1 -0
  68. package/dist/web3/assets/page-D0sN3GO6.js +1 -0
  69. package/dist/web3/assets/page-DEpH91X2.js +1 -0
  70. package/dist/web3/assets/page-l8-B7CGt.js +1 -0
  71. package/dist/web3/assets/page-mnSHmk6A.js +6 -0
  72. package/dist/web3/assets/page-title-BkqlUA0j.js +1 -0
  73. package/dist/web3/assets/page-xzYqkBUP.js +16 -0
  74. package/dist/web3/assets/play-B8xE9j5f.js +6 -0
  75. package/dist/web3/assets/refresh-cw-CsCtmWwX.js +6 -0
  76. package/dist/web3/assets/scroll-area-CbcH6yrZ.js +1 -0
  77. package/dist/web3/assets/scroll-area-dRWncRqa.css +1 -0
  78. package/dist/web3/assets/select-DrL0mpRN.js +6 -0
  79. package/dist/web3/assets/send-CvAgOUPG.js +6 -0
  80. package/dist/web3/assets/settings-EY4A_DYv.js +6 -0
  81. package/dist/web3/assets/share-2-vGmZUl90.js +6 -0
  82. package/dist/web3/assets/tag-Br_y1f00.js +11 -0
  83. package/dist/web3/assets/trash-2-B4hRXo-p.js +6 -0
  84. package/dist/web3/assets/triangle-alert-CsYGyfGZ.js +6 -0
  85. package/dist/web3/assets/user-plus-4CTYEkd3.js +6 -0
  86. package/dist/web3/assets/x-qxt1WrUi.js +6 -0
  87. package/dist/web3/assets/zap-BcsKR0ef.js +16 -0
  88. package/dist/web3/index.html +2 -2
  89. package/package.json +5 -3
  90. package/dist/web3/assets/index-BJ-LRNis.js +0 -1382
  91. package/dist/web3/assets/index-DPMEt-A_.css +0 -1
@@ -3,12 +3,12 @@ import {
3
3
  ForbiddenError,
4
4
  NotFoundError,
5
5
  ValidationError
6
- } from "./chunk-YVMSM7LS.js";
6
+ } from "./chunk-LHWOJPFE.js";
7
7
  import {
8
8
  config,
9
9
  getDb,
10
10
  isEmailish
11
- } from "./chunk-QWNXWRWO.js";
11
+ } from "./chunk-JDAOA5KP.js";
12
12
  import {
13
13
  ANON_USER_ID
14
14
  } from "./chunk-YB3LKF7U.js";
@@ -1296,8 +1296,11 @@ async function listNotifications(userEmail, opts = {}) {
1296
1296
  CASE WHEN dr.target_type = 'document' THEN dr.node_id END,
1297
1297
  -- A mention names the document directly; there's no request row.
1298
1298
  CASE WHEN n.kind = 'mention' THEN n.subject_id END
1299
- ) AS node_id
1299
+ ) AS node_id,
1300
+ -- Name resolved here so the inbox never fetches the nest list for it.
1301
+ COALESCE(ns.name, n.nest_id) AS nest_name
1300
1302
  FROM notifications n
1303
+ LEFT JOIN nests ns ON ns.id = n.nest_id
1301
1304
  LEFT JOIN review_requests rr ON rr.id = n.subject_id
1302
1305
  LEFT JOIN deletion_requests dr ON dr.id = n.subject_id
1303
1306
  WHERE LOWER(n.user_email) = LOWER(?)${opts.unreadOnly ? " AND n.read_at IS NULL" : ""}
@@ -2063,6 +2066,21 @@ async function setPrimeOnlyReview(nestId, enabled) {
2063
2066
  nestId
2064
2067
  ]);
2065
2068
  }
2069
+ async function nestCreatorIsReviewer(nestId) {
2070
+ const db = getDb();
2071
+ const row = await db.get(
2072
+ "SELECT creator_is_reviewer FROM nests WHERE id = ?",
2073
+ [nestId]
2074
+ );
2075
+ return !!row?.creator_is_reviewer;
2076
+ }
2077
+ async function setCreatorIsReviewer(nestId, enabled) {
2078
+ const db = getDb();
2079
+ await db.run("UPDATE nests SET creator_is_reviewer = ? WHERE id = ?", [
2080
+ enabled ? 1 : 0,
2081
+ nestId
2082
+ ]);
2083
+ }
2066
2084
  async function nestPrimeTags(nestId) {
2067
2085
  const db = getDb();
2068
2086
  const row = await db.get("SELECT prime_tags FROM nests WHERE id = ?", [
@@ -2488,6 +2506,9 @@ var FlatNestStorage = class extends NestStorage2 {
2488
2506
  }
2489
2507
  };
2490
2508
 
2509
+ // src/nodes/node-index.ts
2510
+ import { listSuggestions } from "@promptowl/contextnest-engine";
2511
+
2491
2512
  // src/shared/node-id.ts
2492
2513
  function assertSafeNodeId(rawId) {
2493
2514
  let id = rawId;
@@ -2616,13 +2637,21 @@ async function ensureNodeIndex(nestId) {
2616
2637
  return run;
2617
2638
  }
2618
2639
  async function rebuildNodeIndex(nestId) {
2619
- const { engineCache: engineCache2 } = await import("./engine-QFF2IA3G.js");
2620
- const { documentsWithSuggestions } = await import("./external-edit-service-F7D3VSA2.js");
2640
+ const { engineCache: engineCache2 } = await import("./engine-TER6FDBP.js");
2641
+ const { documentsWithSuggestions } = await import("./external-edit-service-CAGENHDJ.js");
2621
2642
  const { storage, dropDiscoveryCache } = await engineCache2.get(nestId);
2622
2643
  dropDiscoveryCache();
2623
2644
  const startedAt = writeGeneration.get(nestId) ?? 0;
2624
2645
  const documents = await storage.discoverDocuments({ includeRetired: true });
2625
2646
  const staged = await documentsWithSuggestions(nestId);
2647
+ const stagedMetas = (await Promise.all(
2648
+ [...staged].map(
2649
+ (id) => listSuggestions(storage, id).catch((err) => {
2650
+ console.error("[node-index] listSuggestions failed", nestId, id, err);
2651
+ return [];
2652
+ })
2653
+ )
2654
+ )).flat();
2626
2655
  const db = getDb();
2627
2656
  const insertSql = insertOrReplace(
2628
2657
  db,
@@ -2630,18 +2659,15 @@ async function rebuildNodeIndex(nestId) {
2630
2659
  ["nest_id", "node_id"],
2631
2660
  UPSERT_SET
2632
2661
  );
2633
- const stagedSql = insertOrIgnore(
2634
- db,
2635
- "INSERT INTO node_suggestions (nest_id, node_id) VALUES (?, ?)"
2636
- );
2662
+ const stagedSql = insertOrIgnore(db, SUGGESTION_INSERT);
2637
2663
  await db.transaction(async (tx) => {
2638
2664
  await tx.run("DELETE FROM node_index WHERE nest_id = ?", [nestId]);
2639
2665
  for (const doc of documents) {
2640
2666
  await tx.run(insertSql, [nestId, ...rowValues(doc)]);
2641
2667
  }
2642
2668
  await tx.run("DELETE FROM node_suggestions WHERE nest_id = ?", [nestId]);
2643
- for (const id of staged) {
2644
- await tx.run(stagedSql, [nestId, id]);
2669
+ for (const m of stagedMetas) {
2670
+ await tx.run(stagedSql, suggestionValues(nestId, m));
2645
2671
  }
2646
2672
  if ((writeGeneration.get(nestId) ?? 0) !== startedAt) return;
2647
2673
  await tx.run(
@@ -2651,30 +2677,42 @@ async function rebuildNodeIndex(nestId) {
2651
2677
  });
2652
2678
  return documents.length;
2653
2679
  }
2654
- async function setSuggestionFlag(nestId, nodeId, present) {
2680
+ var SUGGESTION_INSERT = "INSERT INTO node_suggestions (nest_id, node_id, suggestion_id, source, actor, detected_at, target_hash, proposed_hash, note) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
2681
+ var suggestionValues = (nestId, m) => [
2682
+ nestId,
2683
+ m.document_id,
2684
+ m.suggestion_id,
2685
+ m.source,
2686
+ m.actor,
2687
+ m.detected_at,
2688
+ m.target_hash,
2689
+ m.proposed_hash,
2690
+ m.note ?? null
2691
+ ];
2692
+ async function syncSuggestionRows(nestId, nodeId, metas) {
2655
2693
  try {
2656
2694
  const db = getDb();
2657
- if (!present) {
2658
- await db.run(
2695
+ const insertSql = insertOrIgnore(db, SUGGESTION_INSERT);
2696
+ await db.transaction(async (tx) => {
2697
+ await tx.run(
2659
2698
  "DELETE FROM node_suggestions WHERE nest_id = ? AND node_id = ?",
2660
2699
  [nestId, nodeId]
2661
2700
  );
2662
- return;
2663
- }
2664
- await db.run(
2665
- insertOrIgnore(
2666
- db,
2667
- "INSERT INTO node_suggestions (nest_id, node_id) VALUES (?, ?)"
2668
- ),
2669
- [nestId, nodeId]
2670
- );
2701
+ for (const m of metas) await tx.run(insertSql, suggestionValues(nestId, m));
2702
+ });
2671
2703
  } catch (err) {
2672
- console.error("[node-index] suggestion flag failed", nestId, nodeId, err);
2704
+ console.error("[node-index] suggestion rows failed", nestId, nodeId, err);
2673
2705
  }
2674
2706
  }
2707
+ async function listSuggestionRows(nestId) {
2708
+ return await getDb().all(
2709
+ "SELECT * FROM node_suggestions WHERE nest_id = ? ORDER BY detected_at DESC",
2710
+ [nestId]
2711
+ );
2712
+ }
2675
2713
  async function nodesWithSuggestions(nestId) {
2676
2714
  const rows = await getDb().all(
2677
- "SELECT node_id FROM node_suggestions WHERE nest_id = ?",
2715
+ "SELECT DISTINCT node_id FROM node_suggestions WHERE nest_id = ?",
2678
2716
  [nestId]
2679
2717
  );
2680
2718
  return new Set(rows.map((r) => r.node_id));
@@ -2846,7 +2884,8 @@ var engineCache = new NestEngineCache();
2846
2884
  var engineApi = createEngineApi();
2847
2885
  async function opContext(nestId, actor, onProgress) {
2848
2886
  const { storage, query, versions } = await engineCache.get(nestId);
2849
- return { storage, query, versions, actor, onProgress };
2887
+ const limits = { pdfMaxBytes: config.PDF_MAX_BYTES };
2888
+ return { storage, query, versions, actor, onProgress, limits };
2850
2889
  }
2851
2890
 
2852
2891
  export {
@@ -2894,6 +2933,8 @@ export {
2894
2933
  setAllowSelfApprove,
2895
2934
  nestReviewsPrimeOnly,
2896
2935
  setPrimeOnlyReview,
2936
+ nestCreatorIsReviewer,
2937
+ setCreatorIsReviewer,
2897
2938
  nestPrimeTags,
2898
2939
  setPrimeTags,
2899
2940
  disableStewardshipAndWipeGovernance,
@@ -2916,7 +2957,8 @@ export {
2916
2957
  markIndexStale,
2917
2958
  ensureNodeIndex,
2918
2959
  rebuildNodeIndex,
2919
- setSuggestionFlag,
2960
+ syncSuggestionRows,
2961
+ listSuggestionRows,
2920
2962
  nodesWithSuggestions,
2921
2963
  indexedNodeIds,
2922
2964
  readIndexedNodes,
@@ -4,19 +4,19 @@ import {
4
4
  resolveNestWideRoles,
5
5
  resolveStewardsForNode,
6
6
  stewardCoverageForUser
7
- } from "./chunk-2YPY2HGZ.js";
7
+ } from "./chunk-2KHM5C7V.js";
8
8
  import {
9
9
  grantCoversNode,
10
10
  listUserGrants,
11
11
  resolveNodeGrant
12
- } from "./chunk-UOMW7TT6.js";
12
+ } from "./chunk-KPGNZLZL.js";
13
13
  import {
14
14
  createVersion,
15
15
  getApprovedVersion,
16
16
  getApprovedVersions,
17
17
  getCurrentVersion,
18
18
  setApprovedVersion
19
- } from "./chunk-5CVZMHHB.js";
19
+ } from "./chunk-TI7HMP64.js";
20
20
  import {
21
21
  buildDocContext,
22
22
  buildTitleMap,
@@ -37,16 +37,16 @@ import {
37
37
  resolveNestPermission,
38
38
  sendEmailToRecipient,
39
39
  titleForNode
40
- } from "./chunk-QLXC6542.js";
40
+ } from "./chunk-7FROMOZM.js";
41
41
  import {
42
42
  ConflictError,
43
43
  NotFoundError,
44
44
  ValidationError
45
- } from "./chunk-YVMSM7LS.js";
45
+ } from "./chunk-LHWOJPFE.js";
46
46
  import {
47
47
  config,
48
48
  getDb
49
- } from "./chunk-QWNXWRWO.js";
49
+ } from "./chunk-JDAOA5KP.js";
50
50
 
51
51
  // src/governance/review-service.ts
52
52
  import { v4 as uuid2 } from "uuid";
@@ -788,7 +788,8 @@ async function submitForReview(params) {
788
788
  );
789
789
  if (!alreadySealed) {
790
790
  await versionManager.createVersion(node, params.requestedBy, {
791
- note: params.note || "Submitted for review"
791
+ note: params.note || "Submitted for review",
792
+ ...params.client ? { client: params.client } : {}
792
793
  });
793
794
  }
794
795
  } catch (err) {
@@ -955,7 +956,8 @@ async function approve(params) {
955
956
  author: params.approvedBy,
956
957
  status: "published",
957
958
  tags,
958
- changeNote: note
959
+ changeNote: note,
960
+ client: params.client
959
961
  });
960
962
  await setApprovedVersion(
961
963
  params.nestId,
@@ -356,6 +356,22 @@ var config = {
356
356
  get PEOPLE_SUGGEST_ALL_USERS() {
357
357
  return process.env.PEOPLE_SUGGEST_ALL_USERS === "true";
358
358
  },
359
+ /**
360
+ * This server hosts several unrelated organizations (the PromptOwl-hosted
361
+ * deployment) rather than one company (a self-hosted install).
362
+ *
363
+ * OFF BY DEFAULT. Nest visibility 'org' means "every authenticated account
364
+ * on this deployment" (src/shared/access.ts) — on a single-company server
365
+ * that IS the organization, so the tier is safe to flip in one click. On a
366
+ * shared server it is every customer, so with this on: flipping a nest to
367
+ * 'org' needs the same explicit acknowledgement 'public' always needs
368
+ * (PATCH /nests/:id/visibility 409s without it — the server enforces it, not only the UI), and
369
+ * the UI words both tiers to say so. Exposed on /health as
370
+ * `shared_deployment`. Superadmin-togglable at runtime via /admin/settings.
371
+ */
372
+ get SHARED_DEPLOYMENT() {
373
+ return process.env.SHARED_DEPLOYMENT === "true";
374
+ },
359
375
  /**
360
376
  * Path to the .env file the server reads its config from and the license
361
377
  * install flow persists PROMPTOWL_KEY into. Defaults UNDER DATA_ROOT (not
@@ -640,6 +656,17 @@ var config = {
640
656
  get VIDEO_MAX_BYTES() {
641
657
  const mb = parseInt(process.env.VIDEO_MAX_MB || "30", 10);
642
658
  return (Number.isFinite(mb) && mb > 0 ? mb : 30) * 1024 * 1024;
659
+ },
660
+ /**
661
+ * Max PDF upload size in bytes (POST /nests/:id/nodes/pdf). Default 25 MB —
662
+ * under Cloud Run's 32 MiB HTTP/1 request-body limit, with room for the
663
+ * multipart framing, so an oversized PDF is refused by our own check (413
664
+ * with a clear message) rather than by the platform. Read per request, so a
665
+ * change takes effect without a restart. Override via PDF_MAX_MB.
666
+ */
667
+ get PDF_MAX_BYTES() {
668
+ const mb = parseInt(process.env.PDF_MAX_MB || "25", 10);
669
+ return (Number.isFinite(mb) && mb > 0 ? mb : 25) * 1024 * 1024;
643
670
  }
644
671
  };
645
672
 
@@ -828,6 +855,9 @@ function runMigrations(db) {
828
855
  if (!nestCols.includes("prime_tags")) {
829
856
  db.exec("ALTER TABLE nests ADD COLUMN prime_tags TEXT NOT NULL DEFAULT 'prime-document'");
830
857
  }
858
+ if (!nestCols.includes("creator_is_reviewer")) {
859
+ db.exec("ALTER TABLE nests ADD COLUMN creator_is_reviewer INTEGER NOT NULL DEFAULT 0");
860
+ }
831
861
  if (!nestCols.includes("index_synced_at")) {
832
862
  db.exec("ALTER TABLE nests ADD COLUMN index_synced_at TEXT");
833
863
  }
@@ -1757,6 +1787,42 @@ function runMigrations(db) {
1757
1787
  recordMigration("042_community_sites");
1758
1788
  })();
1759
1789
  }
1790
+ if (!hasMigration("043_node_suggestion_details")) {
1791
+ db.transaction(() => {
1792
+ db.exec(`
1793
+ DROP TABLE IF EXISTS node_suggestions;
1794
+ CREATE TABLE node_suggestions (
1795
+ nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
1796
+ node_id TEXT NOT NULL,
1797
+ suggestion_id TEXT NOT NULL,
1798
+ source TEXT NOT NULL,
1799
+ actor TEXT NOT NULL,
1800
+ detected_at TEXT NOT NULL,
1801
+ target_hash TEXT NOT NULL,
1802
+ proposed_hash TEXT NOT NULL,
1803
+ note TEXT,
1804
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1805
+ PRIMARY KEY (nest_id, node_id, suggestion_id)
1806
+ );
1807
+ CREATE INDEX IF NOT EXISTS idx_node_suggestions_nest_detected
1808
+ ON node_suggestions(nest_id, detected_at DESC);
1809
+ UPDATE nests SET index_synced_at = NULL;
1810
+ `);
1811
+ recordMigration("043_node_suggestion_details");
1812
+ })();
1813
+ }
1814
+ if (!hasMigration("044_client_attribution")) {
1815
+ for (const [table, col] of [
1816
+ ["node_versions", "client_json"],
1817
+ ["api_events", "client_json"]
1818
+ ]) {
1819
+ const cols = db.prepare(`PRAGMA table_info(${table})`).all().map((c) => c.name);
1820
+ if (cols.length && !cols.includes(col)) {
1821
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${col} TEXT`);
1822
+ }
1823
+ }
1824
+ recordMigration("044_client_attribution");
1825
+ }
1760
1826
  }
1761
1827
  function fkViolationCounts(db) {
1762
1828
  const rows = db.pragma("foreign_key_check");
@@ -2031,7 +2097,7 @@ async function initDb() {
2031
2097
  if (config.DB_DRIVER === "postgres") {
2032
2098
  const { Pool } = await import("pg");
2033
2099
  const { PostgresAdapter } = await import("./adapter.postgres-6VZCMPQL.js");
2034
- const { runPostgresMigrations } = await import("./migrations.postgres-APCVSYUE.js");
2100
+ const { runPostgresMigrations } = await import("./migrations.postgres-23VJJJJX.js");
2035
2101
  const pool = new Pool(buildPgConfig());
2036
2102
  adapter = new PostgresAdapter(pool);
2037
2103
  await runPostgresMigrations(adapter);
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  ValidationError
3
- } from "./chunk-YVMSM7LS.js";
3
+ } from "./chunk-LHWOJPFE.js";
4
4
  import {
5
5
  getDb
6
- } from "./chunk-QWNXWRWO.js";
6
+ } from "./chunk-JDAOA5KP.js";
7
7
 
8
8
  // src/governance/grants-service.ts
9
9
  import { v4 as uuid } from "uuid";
@@ -31,6 +31,12 @@ var ValidationError = class extends AppError {
31
31
  this.name = "ValidationError";
32
32
  }
33
33
  };
34
+ var PayloadTooLargeError = class extends AppError {
35
+ constructor(message) {
36
+ super(413, message);
37
+ this.name = "PayloadTooLargeError";
38
+ }
39
+ };
34
40
  var ConflictError = class extends AppError {
35
41
  constructor(message) {
36
42
  super(409, message);
@@ -61,6 +67,7 @@ export {
61
67
  ForbiddenError,
62
68
  UnauthorizedError,
63
69
  ValidationError,
70
+ PayloadTooLargeError,
64
71
  ConflictError,
65
72
  LockedError,
66
73
  VaultBusyError,
@@ -2,13 +2,88 @@ import {
2
2
  buildTitleMap,
3
3
  insertOrReplace,
4
4
  nowExpr
5
- } from "./chunk-QLXC6542.js";
5
+ } from "./chunk-7FROMOZM.js";
6
+ import {
7
+ ValidationError
8
+ } from "./chunk-LHWOJPFE.js";
6
9
  import {
7
10
  getDb
8
- } from "./chunk-QWNXWRWO.js";
11
+ } from "./chunk-JDAOA5KP.js";
9
12
 
10
13
  // src/governance/version-service.ts
11
14
  import { createHash } from "crypto";
15
+
16
+ // src/shared/client-metadata.ts
17
+ import {
18
+ clientField,
19
+ clientMetadataSchema,
20
+ CLIENT_METADATA_MAX_VALUE_LENGTH
21
+ } from "@promptowl/contextnest-engine/api";
22
+ import { z } from "zod";
23
+ var clientToolField = {
24
+ client: z.any().optional().describe(clientField.client.description ?? "")
25
+ };
26
+ function boundedValue(raw) {
27
+ const cut = raw.slice(0, CLIENT_METADATA_MAX_VALUE_LENGTH);
28
+ const last = cut.charCodeAt(cut.length - 1);
29
+ return last >= 55296 && last <= 56319 ? cut.slice(0, -1) : cut;
30
+ }
31
+ function orAbsent(value) {
32
+ return Object.keys(value).length > 0 ? value : void 0;
33
+ }
34
+ function parseClientMetadata(raw) {
35
+ if (raw === void 0 || raw === null) return void 0;
36
+ const parsed = clientMetadataSchema.safeParse(raw);
37
+ if (!parsed.success) {
38
+ const detail = parsed.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join("; ");
39
+ throw new ValidationError(`Invalid client metadata \u2014 ${detail}`);
40
+ }
41
+ return orAbsent(parsed.data);
42
+ }
43
+ function mergeClient(derived, supplied) {
44
+ return orAbsent({ ...derived, ...supplied });
45
+ }
46
+ function derivedClient(c, connection) {
47
+ if (process.env.CONTEXTNEST_NO_ATTRIBUTION === "1") return {};
48
+ const agent = process.env.CONTEXTNEST_AGENT || connection?.agent || c.get("apiKeyLabel") || "";
49
+ const session = process.env.CONTEXTNEST_SESSION_ID || connection?.session_id || c.req.header("Mcp-Session-Id") || "";
50
+ return {
51
+ // Truncated to the engine's bound, not rejected: a derived value must never
52
+ // be the reason an otherwise valid call fails validation.
53
+ ...agent ? { agent: boundedValue(String(agent)) } : {},
54
+ ...session ? { session_id: boundedValue(String(session)) } : {}
55
+ };
56
+ }
57
+ function connectionClient(server) {
58
+ const name = server.server.getClientVersion()?.name;
59
+ const sessionId = server.server.transport?.sessionId;
60
+ return {
61
+ ...name ? { agent: name } : {},
62
+ ...sessionId ? { session_id: sessionId } : {}
63
+ };
64
+ }
65
+ function attributeRequest(c, supplied, connection) {
66
+ const client = mergeClient(
67
+ derivedClient(c, connection),
68
+ parseClientMetadata(supplied)
69
+ );
70
+ if (client) c.set("clientMetadata", client);
71
+ return client;
72
+ }
73
+ function serializeClient(value) {
74
+ return value ? JSON.stringify(value) : null;
75
+ }
76
+ function deserializeClient(raw) {
77
+ if (typeof raw !== "string" || !raw) return void 0;
78
+ try {
79
+ const parsed = JSON.parse(raw);
80
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? orAbsent(parsed) : void 0;
81
+ } catch {
82
+ return void 0;
83
+ }
84
+ }
85
+
86
+ // src/governance/version-service.ts
12
87
  function hashContent(content) {
13
88
  return createHash("sha256").update(content).digest("hex");
14
89
  }
@@ -20,8 +95,8 @@ async function createVersion(params) {
20
95
  const db = getDb();
21
96
  const contentHash = hashContent(params.content);
22
97
  await db.run(
23
- `INSERT INTO node_versions (nest_id, node_id, version, content_hash, author, status, change_note, tags_json)
24
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
98
+ `INSERT INTO node_versions (nest_id, node_id, version, content_hash, author, status, change_note, tags_json, client_json)
99
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
25
100
  [
26
101
  params.nestId,
27
102
  params.nodeId,
@@ -30,7 +105,8 @@ async function createVersion(params) {
30
105
  params.author,
31
106
  params.status,
32
107
  params.changeNote || null,
33
- params.tags ? JSON.stringify(params.tags) : null
108
+ params.tags ? JSON.stringify(params.tags) : null,
109
+ serializeClient(params.client)
34
110
  ]
35
111
  );
36
112
  return {
@@ -39,7 +115,8 @@ async function createVersion(params) {
39
115
  editedBy: params.author,
40
116
  editedAt: (/* @__PURE__ */ new Date()).toISOString(),
41
117
  changeNote: params.changeNote,
42
- status: params.status
118
+ status: params.status,
119
+ client: params.client
43
120
  };
44
121
  }
45
122
  async function upsertVersion(params) {
@@ -47,13 +124,13 @@ async function upsertVersion(params) {
47
124
  const contentHash = hashContent(params.content);
48
125
  const sql = insertOrReplace(
49
126
  db,
50
- `INSERT INTO node_versions (nest_id, node_id, version, content_hash, author, status, change_note, tags_json)
51
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
127
+ `INSERT INTO node_versions (nest_id, node_id, version, content_hash, author, status, change_note, tags_json, client_json)
128
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
52
129
  ["nest_id", "node_id", "version"],
53
130
  // Reset created_at on conflict too: SQLite's INSERT OR REPLACE deletes+
54
131
  // reinserts (created_at → now), so mirror that on Postgres — otherwise a
55
132
  // draft edited in place would keep its original (import-time) "edited at".
56
- `content_hash = excluded.content_hash, author = excluded.author, status = excluded.status, change_note = excluded.change_note, tags_json = excluded.tags_json, created_at = ${nowExpr(db)}`
133
+ `content_hash = excluded.content_hash, author = excluded.author, status = excluded.status, change_note = excluded.change_note, tags_json = excluded.tags_json, client_json = excluded.client_json, created_at = ${nowExpr(db)}`
57
134
  );
58
135
  await db.run(sql, [
59
136
  params.nestId,
@@ -63,7 +140,8 @@ async function upsertVersion(params) {
63
140
  params.author,
64
141
  params.status,
65
142
  params.changeNote || null,
66
- params.tags ? JSON.stringify(params.tags) : null
143
+ params.tags ? JSON.stringify(params.tags) : null,
144
+ serializeClient(params.client)
67
145
  ]);
68
146
  return {
69
147
  version: params.version,
@@ -71,7 +149,8 @@ async function upsertVersion(params) {
71
149
  editedBy: params.author,
72
150
  editedAt: (/* @__PURE__ */ new Date()).toISOString(),
73
151
  changeNote: params.changeNote,
74
- status: params.status
152
+ status: params.status,
153
+ client: params.client
75
154
  };
76
155
  }
77
156
  async function getVersions(nestId, nodeId) {
@@ -354,11 +433,19 @@ function rowToVersion(row) {
354
433
  editedBy: row.author,
355
434
  editedAt: row.created_at,
356
435
  changeNote: row.change_note || void 0,
357
- status: row.status
436
+ status: row.status,
437
+ client: deserializeClient(row.client_json)
358
438
  };
359
439
  }
360
440
 
361
441
  export {
442
+ clientToolField,
443
+ parseClientMetadata,
444
+ mergeClient,
445
+ connectionClient,
446
+ attributeRequest,
447
+ serializeClient,
448
+ deserializeClient,
362
449
  hashContent,
363
450
  SYSTEM_AUTHOR_PREFIX,
364
451
  systemAuthor,
@@ -2,7 +2,7 @@ import {
2
2
  getDb,
3
3
  initDb,
4
4
  resetDb
5
- } from "./chunk-QWNXWRWO.js";
5
+ } from "./chunk-JDAOA5KP.js";
6
6
  import "./chunk-YB3LKF7U.js";
7
7
  export {
8
8
  getDb,
@@ -2,9 +2,9 @@ import {
2
2
  engineApi,
3
3
  engineCache,
4
4
  opContext
5
- } from "./chunk-QLXC6542.js";
6
- import "./chunk-YVMSM7LS.js";
7
- import "./chunk-QWNXWRWO.js";
5
+ } from "./chunk-7FROMOZM.js";
6
+ import "./chunk-LHWOJPFE.js";
7
+ import "./chunk-JDAOA5KP.js";
8
8
  import "./chunk-YB3LKF7U.js";
9
9
  import "./chunk-FRQJWGN3.js";
10
10
  export {
@@ -11,10 +11,10 @@ import {
11
11
  scanNestForDrift,
12
12
  startDriftScanner,
13
13
  stopDriftScanner
14
- } from "./chunk-XYD6V2LH.js";
15
- import "./chunk-QLXC6542.js";
16
- import "./chunk-YVMSM7LS.js";
17
- import "./chunk-QWNXWRWO.js";
14
+ } from "./chunk-32SZCH36.js";
15
+ import "./chunk-7FROMOZM.js";
16
+ import "./chunk-LHWOJPFE.js";
17
+ import "./chunk-JDAOA5KP.js";
18
18
  import "./chunk-YB3LKF7U.js";
19
19
  import "./chunk-FRQJWGN3.js";
20
20
  export {
@@ -6,9 +6,9 @@ import {
6
6
  listGrants,
7
7
  listUserGrants,
8
8
  resolveNodeGrant
9
- } from "./chunk-UOMW7TT6.js";
10
- import "./chunk-YVMSM7LS.js";
11
- import "./chunk-QWNXWRWO.js";
9
+ } from "./chunk-KPGNZLZL.js";
10
+ import "./chunk-LHWOJPFE.js";
11
+ import "./chunk-JDAOA5KP.js";
12
12
  import "./chunk-YB3LKF7U.js";
13
13
  export {
14
14
  createGrant,