@promptowl/contextnest-community 1.12.0 → 1.13.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/CONFIGURATION.md CHANGED
@@ -73,7 +73,8 @@ The server prints a loud warning at startup when `AUTH_MODE=open` is active.
73
73
  | `TELEMETRY_INTERVAL_MS` | `3600000` (1 hour) | How often buffered telemetry is flushed to PromptOwl. |
74
74
  | `TRACE_RETENTION_DAYS` | `14` | Activity-trace retention window in days (the `api_events` rows behind `GET /admin/trace` and `GET /nests/:id/trace`). Rows older than this are pruned opportunistically (every ~500 inserts). `0` = keep forever (pruning is skipped entirely). Capped at `3650`; invalid/negative values fall back to `14`. Also editable from Settings → Advanced. |
75
75
  | `CORS_ORIGINS` | `*` in open mode; `http://localhost:5173,http://localhost:3838` in key mode | Comma-separated allowlist. Set to `*` to allow any origin (**only** safe in open mode — in key mode with Bearer tokens this enables CSRF). |
76
- | `MAX_BODY_BYTES` | `10485760` (10 MB) | Reject requests whose `Content-Length` exceeds this. Prevents giant-payload DoS. |
76
+ | `MAX_BODY_BYTES` | `10485760` (10 MB) | Reject requests whose `Content-Length` exceeds this. Prevents giant-payload DoS. The asset-upload route (`POST /nests/:id/assets`) is exempt up to the video cap below. |
77
+ | `VIDEO_MAX_MB` | `30` | Max size (in MB) of a video uploaded into a doc. Default `30` keeps it under Cloud Run's ~32 MiB HTTP/1 request-body limit, so an oversized video is rejected with a clear message instead of a bare `413` from the platform. Raise only where the deployment can actually accept larger request bodies (not behind Cloud Run, or on HTTP/2 / direct-to-bucket upload). Images are fixed at 10 MB. |
77
78
  | `LOGO_URL` | _(unset)_ | Custom logo shown in the UI header + login screen. Must start with `https://`, `http://`, or `data:image/` — other schemes (`file://`, relative, `javascript:`) are rejected with a warning and the bundled icon is used. |
78
79
  | `PROMPTOWL_TEAMS_ENABLED` | _(unset — off)_ | Lets users who signed in with PromptOwl import their PromptOwl teams as local teams. Off by default; set `true` to enable, or toggle from Settings → Advanced. When off, `GET/POST /teams/promptowl` return `404` and the PromptOwl Teams panel is hidden. Independent of `PROMPTOWL_SIGN_IN_GATE`. |
79
80
  | `TYPE_ARTIFACT_ENABLED` | `true` | Set `false` to disable creation of **artifact** nodes server-wide (existing artifact nodes stay readable — never data loss). Runnable types (agent/skill/tool) are gated by `FEATURE_WORKFLOW_PLANE`, not here. Also editable from Settings (`/admin/settings`). |
package/README.md CHANGED
@@ -101,7 +101,7 @@ For redistribution, hosted-service, OEM, or regulated-industry licensing, contac
101
101
  | Custom logo / branding | ✅ | ✅ |
102
102
  | Admin password reset + user removal (in-platform) | ✅ | ✅ |
103
103
  | Wiki backlinks, outline, hover-preview, link health | ✅ | ✅ |
104
- | Rich editor — tables, callouts, toggles, code highlight, find/replace | ✅ | ✅ |
104
+ | Rich editor — tables, callouts, toggles, code highlight, find/replace, image & video upload | ✅ | ✅ |
105
105
  | Steward version revert | ✅ | ✅ |
106
106
  | MCP server for AI agents | ✅ | ✅ |
107
107
  | Centralized multi-tenant admin console | — | ✅ |
@@ -12,6 +12,7 @@ import { join, dirname } from "path";
12
12
  import { existsSync } from "fs";
13
13
  import { fileURLToPath } from "url";
14
14
  import dotenv from "dotenv";
15
+ import { z } from "zod";
15
16
  var __filename = fileURLToPath(import.meta.url);
16
17
  var __dirname = dirname(__filename);
17
18
  var envCandidates = [
@@ -29,8 +30,9 @@ var slackUrlWarned = false;
29
30
  var emailFromWarned = false;
30
31
  var emailToWarned = false;
31
32
  var oidcIssuerWarned = false;
33
+ var EMAIL_SCHEMA = z.email();
32
34
  function isEmailish(v) {
33
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) && !/[\r\n]/.test(v);
35
+ return EMAIL_SCHEMA.safeParse(v.trim()).success && !/[\r\n]/.test(v);
34
36
  }
35
37
  function isEmailListish(v) {
36
38
  return v.split(",").every((e) => isEmailish(e.trim()));
@@ -494,6 +496,17 @@ var config = {
494
496
  */
495
497
  get MAX_BODY_BYTES() {
496
498
  return parseInt(process.env.MAX_BODY_BYTES || String(10 * 1024 * 1024), 10);
499
+ },
500
+ /**
501
+ * Max video upload size in bytes. Default 30 MB — kept under Cloud Run's
502
+ * 32 MiB HTTP/1 request-body limit so a too-big video is rejected cleanly by
503
+ * our own check (clear message) instead of a bare 413 from the platform.
504
+ * Raise via VIDEO_MAX_MB only when the deployment can actually accept it
505
+ * (e.g. not behind Cloud Run, or on HTTP/2 / direct-to-bucket upload).
506
+ */
507
+ get VIDEO_MAX_BYTES() {
508
+ const mb = parseInt(process.env.VIDEO_MAX_MB || "30", 10);
509
+ return (Number.isFinite(mb) && mb > 0 ? mb : 30) * 1024 * 1024;
497
510
  }
498
511
  };
499
512
 
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-XQ46F76G.js";
5
5
  import {
6
6
  getDb
7
- } from "./chunk-3M7677XW.js";
7
+ } from "./chunk-5QQ7WKAI.js";
8
8
 
9
9
  // src/governance/version-service.ts
10
10
  import { createHash } from "crypto";
@@ -41,6 +41,38 @@ async function createVersion(params) {
41
41
  status: params.status
42
42
  };
43
43
  }
44
+ async function upsertVersion(params) {
45
+ const db = getDb();
46
+ const contentHash = hashContent(params.content);
47
+ const sql = insertOrReplace(
48
+ db,
49
+ `INSERT INTO node_versions (nest_id, node_id, version, content_hash, author, status, change_note, tags_json)
50
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
51
+ ["nest_id", "node_id", "version"],
52
+ // Reset created_at on conflict too: SQLite's INSERT OR REPLACE deletes+
53
+ // reinserts (created_at → now), so mirror that on Postgres — otherwise a
54
+ // draft edited in place would keep its original (import-time) "edited at".
55
+ `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)}`
56
+ );
57
+ await db.run(sql, [
58
+ params.nestId,
59
+ params.nodeId,
60
+ params.version,
61
+ contentHash,
62
+ params.author,
63
+ params.status,
64
+ params.changeNote || null,
65
+ params.tags ? JSON.stringify(params.tags) : null
66
+ ]);
67
+ return {
68
+ version: params.version,
69
+ content: params.content,
70
+ editedBy: params.author,
71
+ editedAt: (/* @__PURE__ */ new Date()).toISOString(),
72
+ changeNote: params.changeNote,
73
+ status: params.status
74
+ };
75
+ }
44
76
  async function getVersions(nestId, nodeId) {
45
77
  const db = getDb();
46
78
  const rows = await db.all(
@@ -172,6 +204,7 @@ export {
172
204
  SYSTEM_AUTHOR_PREFIX,
173
205
  systemAuthor,
174
206
  createVersion,
207
+ upsertVersion,
175
208
  getVersions,
176
209
  getVersion,
177
210
  getCurrentVersion,
@@ -2,12 +2,12 @@ import {
2
2
  grantCoversNode,
3
3
  listUserGrants,
4
4
  resolveNodeGrant
5
- } from "./chunk-ZMSU7BCC.js";
5
+ } from "./chunk-ZQS5W45U.js";
6
6
  import {
7
7
  createVersion,
8
8
  getApprovedVersion,
9
9
  setApprovedVersion
10
- } from "./chunk-JHIU6RZU.js";
10
+ } from "./chunk-EG77SQHO.js";
11
11
  import {
12
12
  buildDocContext,
13
13
  buildTitleMap,
@@ -18,7 +18,7 @@ import {
18
18
  isPublicReader,
19
19
  isStewardshipEnabled,
20
20
  resolveStewardsForNode
21
- } from "./chunk-F7EKYNXG.js";
21
+ } from "./chunk-Q5NRCDD4.js";
22
22
  import {
23
23
  ConflictError,
24
24
  NotFoundError,
@@ -31,7 +31,7 @@ import {
31
31
  import {
32
32
  config,
33
33
  getDb
34
- } from "./chunk-3M7677XW.js";
34
+ } from "./chunk-5QQ7WKAI.js";
35
35
 
36
36
  // src/governance/review-service.ts
37
37
  import { v4 as uuid3 } from "uuid";
@@ -1226,6 +1226,13 @@ async function getReviewQueue(params) {
1226
1226
  whereClauses.push("nest_id = ?");
1227
1227
  args.push(params.nestId);
1228
1228
  }
1229
+ if (params.nestIds) {
1230
+ if (params.nestIds.length === 0) return { requests: [], total: 0 };
1231
+ whereClauses.push(
1232
+ `nest_id IN (${params.nestIds.map(() => "?").join(",")})`
1233
+ );
1234
+ args.push(...params.nestIds);
1235
+ }
1229
1236
  if (params.status) {
1230
1237
  const statuses = Array.isArray(params.status) ? params.status : [params.status];
1231
1238
  whereClauses.push(
@@ -1234,10 +1241,12 @@ async function getReviewQueue(params) {
1234
1241
  args.push(...statuses);
1235
1242
  }
1236
1243
  const where = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
1237
- const total = (await db.get(
1238
- `SELECT COUNT(*) as c FROM review_requests ${where}`,
1239
- args
1240
- )).c;
1244
+ const total = Number(
1245
+ (await db.get(
1246
+ `SELECT COUNT(*) as c FROM review_requests ${where}`,
1247
+ args
1248
+ )).c
1249
+ );
1241
1250
  const limit = params.limit || 50;
1242
1251
  const offset = params.offset || 0;
1243
1252
  const rows = await db.all(