@promptowl/contextnest-community 1.11.0 → 1.12.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 +4 -0
- package/README.md +3 -61
- package/dist/index.js +43 -33
- package/dist/web3/assets/{index-DekN5OmU.js → index-CUVRdYvH.js} +254 -254
- package/dist/web3/index.html +1 -1
- package/package.json +1 -1
package/CONFIGURATION.md
CHANGED
|
@@ -76,11 +76,15 @@ The server prints a loud warning at startup when `AUTH_MODE=open` is active.
|
|
|
76
76
|
| `MAX_BODY_BYTES` | `10485760` (10 MB) | Reject requests whose `Content-Length` exceeds this. Prevents giant-payload DoS. |
|
|
77
77
|
| `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
78
|
| `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
|
+
| `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`). |
|
|
80
|
+
| `TYPE_TABLE_ENABLED` | `true` | Same as above for **table** nodes. |
|
|
79
81
|
| `FEATURE_WORKFLOW_PLANE` | _(unset — off)_ | Enables the workflow plane: typed edges, edge-type registry, governed runs. Optional feature; also toggleable from Settings. |
|
|
80
82
|
| `FEATURE_SUBAGENT_RUNS` | _(unset — off)_ | Lets runs spawn nested sub-agent runs (recursion). Gated separately from the plane; requires `FEATURE_WORKFLOW_PLANE`. Also toggleable from Settings → Advanced (turning the plane off forces this off too). Opens a recursion surface — enable only after reviewing the depth/fan-out caps. |
|
|
81
83
|
| `SUBAGENT_MAX_DEPTH` | `8` | Max sub-agent nesting depth (clamped 1..32) — bounds the call tree's HEIGHT so it stays finite/haltable. |
|
|
82
84
|
| `SUBAGENT_MAX_CHILDREN` | `16` | Max direct children a single run may spawn (clamped 1..128) — bounds the call tree's WIDTH. Together with `SUBAGENT_MAX_DEPTH` this caps total tree size so a runner can't fork-bomb the DB. |
|
|
83
85
|
| `RUN_MAX_STEPS` | `10000` | Max steps a single run may accumulate (clamped 10..100000) — bounds a runaway/hostile runner's step log. |
|
|
86
|
+
| `RUN_MAX_CONCURRENT_ROOTS` | `50` | Max concurrently-running root (depth-0) runs per nest (clamped 1..1000). The subagent caps bound a single tree; this bounds how many trees can run at once, so triggers can't flood a nest. |
|
|
87
|
+
| `ANTHROPIC_API_KEY` | _(unset)_ | Server-wide default runner key for workflow-plane agent runs. Never returned by the API — the health endpoint reports presence only, and Settings shows a masked tail. |
|
|
84
88
|
| `SLACK_WEBHOOK_URL` | _(unset — connector off)_ | Slack incoming-webhook URL for governance-event notifications (review submitted/approved/rejected, collaborator added). `https://` only — the URL embeds a secret. Also editable from Settings in the UI. |
|
|
85
89
|
| `SMTP_URL` | _(unset — connector off)_ | SMTP connection URL for email notifications (`smtp://` or `smtps://`, credentials inline). Requires `NOTIFY_EMAIL_FROM` and `NOTIFY_EMAIL_TO`. Also editable from Settings. |
|
|
86
90
|
| `NOTIFY_EMAIL_FROM` | _(unset)_ | From address for notification emails. |
|
package/README.md
CHANGED
|
@@ -113,67 +113,9 @@ For redistribution, hosted-service, OEM, or regulated-industry licensing, contac
|
|
|
113
113
|
|
|
114
114
|
For Enterprise pricing and features, contact **hoot@promptowl.ai** or visit <https://promptowl.ai/contextnest/>.
|
|
115
115
|
|
|
116
|
-
## What's new
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
- **Configurable `NEST_STORAGE_ROOT`** — store nest vault files (markdown + version history) outside `DATA_ROOT` — e.g. on a Cloud Storage (GCS) volume mount — while the database stays on `DATA_ROOT`. Defaults to `<DATA_ROOT>/nests`, so existing deployments are unchanged. On a GCS FUSE mount, set `DRIFT_SCAN_INTERVAL_MS=0` and run a single instance.
|
|
120
|
-
- **Security fix** — the admin guard on `/admin/settings` (GET/PATCH) is now correctly awaited; without it a non-admin could read or change server-wide settings in key mode.
|
|
121
|
-
- **AdminLoginModal respects `PROMPTOWL_SIGN_IN_GATE`** — the "Sign in with PromptOwl" affordance follows the configured gate (`open` / `admin-only` / `disabled`).
|
|
122
|
-
- **Drift scanner toggle** — `DRIFT_SCAN_INTERVAL_MS=0` now actually disables the drift scanner (previously reset to the 30s default).
|
|
123
|
-
- **Safer folder sync** — unsynced-folder detection no longer treats a configured nest storage root as a sync candidate, preventing accidental duplication/removal of live nests when `NEST_STORAGE_ROOT` is nested under `DATA_ROOT`.
|
|
124
|
-
|
|
125
|
-
Full history in [CHANGELOG.md](./CHANGELOG.md).
|
|
126
|
-
|
|
127
|
-
## What's new in 1.3.0
|
|
128
|
-
|
|
129
|
-
- **Server super-admins** — emails listed in `access.yaml: super_admins` administer every nest (change visibility, manage collaborators and stewards) without being added per-nest. Owner-only operations (delete, transfer) still require the nest owner. See [STEWARDSHIP.md](./STEWARDSHIP.md).
|
|
130
|
-
- **License persistence under `DATA_ROOT`** — `PROMPTOWL_KEY` now writes to `$DATA_ROOT/.env` (was `$cwd/.env`, which is root-owned `/app` in the official Docker image and silently lost the key on container recreate). The License Setup Page also surfaces write failures instead of failing quietly. Override via `ENV_FILE_PATH`. See [CONFIGURATION.md](./CONFIGURATION.md).
|
|
131
|
-
- **Case-insensitive email** across registration, login, invite, and access checks. Migration dedupes existing accounts that differ only in case; fixes "I can't log in because I capitalized my email" and access-guard / lockout mismatches.
|
|
132
|
-
- **Document version history + inline diff** — every saved version is browsable in the document detail view, and an inline diff highlights what changed between any two versions.
|
|
133
|
-
- **Nest Overview / index landing** — every nest opens on a grouped table-of-contents with a "recently edited" strip instead of a flat list.
|
|
134
|
-
- **Public/private toggle on the nest header** — visibility is set directly from the header, not buried in a dialog.
|
|
135
|
-
- **Review queue surfaces submitted-for-review edits** — reviewers see the pending versions a contributor staged, not just net-new documents.
|
|
136
|
-
- **Internal-folder sync over MCP** — agents can list a nest's unsynced filesystem folders and pull them into the nest on demand. New `unsynced-service.ts` + MCP tools, covered by `test/unsynced-folder.test.ts` and `test/mcp-unsynced.test.ts`.
|
|
137
|
-
- **Telemetry `batch_id` + user email** — usage events carry a stable `batch_id` (de-duped on the receiver) and the authenticated user email alongside the user id, so the PromptOwl ingest bridge can meter credits to the right account.
|
|
138
|
-
- **Hyperlink navigation fix** — hyperlinks in the editor and viewer navigate to their configured target URL correctly.
|
|
139
|
-
- **Repo / DX** — `CLAUDE.md` at the repo root with project-specific guidance for Claude Code; `claude.yml` (PR assistant) and `claude-code-review.yml` (auto review on PRs) GitHub Actions workflows.
|
|
140
|
-
|
|
141
|
-
Full history in [CHANGELOG.md](./CHANGELOG.md).
|
|
142
|
-
|
|
143
|
-
## What's new in 1.2.0
|
|
144
|
-
|
|
145
|
-
- **Admin user management (no email server needed)** — `POST /auth/admin/reset-password/:userId` sets a user's password or returns a one-time temp password; `DELETE /auth/users/:userId` removes a user and revokes their API keys, sessions, and role rows. New self-service **Change password** in the user menu blocks reusing the current password.
|
|
146
|
-
- **`PROMPTOWL_SIGN_IN_GATE`** — `open` / `admin-only` / `disabled` restricts "Sign in with PromptOwl" (admin reaches it via `?admin=1`). See [CONFIGURATION.md](./CONFIGURATION.md).
|
|
147
|
-
- **Plain-text agent view** — `?format=markdown` on a node returns frontmatter + body as `text/markdown` for LLM-friendly consumption.
|
|
148
|
-
- **Steward version revert** — `POST .../revert` restores an earlier version as a new one; pending-review docs are now editable (saving withdraws the review).
|
|
149
|
-
- **Editor — always-editable surface** — Notion-style buttery headline, selection bubble toolbar (format-on-highlight), turn-into block conversion, Cmd+F find & replace, rich blocks (tables, callouts, toggles, syntax-highlighted code, columns), and wiki/code-safety fixes.
|
|
150
|
-
- **Wiki usability** — backlinks panel ("Linked from N documents"), outline/TOC for 3+-heading docs, hover-preview on `[[wiki links]]`, multi-tag AND filtering with clickable chips, per-doc copy link, rename-safety warning for title-form links, link-health report (broken links + orphans), recently-edited strip.
|
|
151
|
-
- **Sharing clarity** — share button reads "Share nest"; dialog clarifies nest-wide scope; deep links open documents directly.
|
|
152
|
-
- **Security** — hardened scanner gate (PolinRider + Shai-Hulud preflight) in `bin/`.
|
|
153
|
-
|
|
154
|
-
Full history in [CHANGELOG.md](./CHANGELOG.md).
|
|
155
|
-
|
|
156
|
-
## What's new in 1.1.0
|
|
157
|
-
|
|
158
|
-
- **Vault import** — import an existing folder of markdown files into a new nest in one step, from the dashboard ("Import folder") or via the API. Frontmatter, wiki links, and folder structure are preserved.
|
|
159
|
-
- **Nest sharing + collaborators** — set per-nest visibility and add collaborators with read or write access. A Share affordance is now inline in the document view.
|
|
160
|
-
- **Public read-only nests** — flip a nest to `visibility=public` to serve it to unauthenticated readers (bypasses auth for GETs only); writes still require a key.
|
|
161
|
-
- **Custom logo / branding** — set `LOGO_URL` to show your own logo in the UI header and login screen. See [CONFIGURATION.md](./CONFIGURATION.md).
|
|
162
|
-
- **Governance** — nest owners can self-approve their own changes; new per-nest auto-approve toggle; role-based action gating (buttons reflect the viewer's role); stewards can edit scope during a role change.
|
|
163
|
-
- **Editor** — `[[wikilink]]` autocomplete, broken-link click creates a new doc with the title pre-filled, tag chips filter the nest list, Notion-style H1 title, click-to-edit, and an unsaved-changes warning.
|
|
164
|
-
- **Stability** — added an error boundary so a render error in one view no longer blanks the whole app; bumped `uuid` to 14.
|
|
165
|
-
|
|
166
|
-
Full history in [CHANGELOG.md](./CHANGELOG.md).
|
|
167
|
-
|
|
168
|
-
## What's new in 1.0.1
|
|
169
|
-
|
|
170
|
-
- **Document hashing pipeline** — external-edit detection, conflict-aware safe-publish, and inline version diffs powered by `@promptowl/contextnest-engine`. When a file is edited outside the UI, the editor shows an "External edit detected" banner with a side-by-side diff and an adopt / keep choice.
|
|
171
|
-
- **Markdown rendering** — new `DocumentViewer` (react-markdown + remark-gfm + wikilink support) renders CLI/MCP-authored markdown correctly, with a view/edit toggle.
|
|
172
|
-
- **License revocation now blocks writes synchronously** — revoke flips an in-process "writes blocked" flag immediately; the next write returns `503`.
|
|
173
|
-
- **Dashboard stats** — new `GET /stats` endpoint surfaces nest / document / user counts.
|
|
174
|
-
- **Docker fix** — Dockerfile now installs via npm against the shipped `package-lock.json`; base image bumped to `node:22-slim`.
|
|
175
|
-
|
|
176
|
-
Full history in [CHANGELOG.md](./CHANGELOG.md).
|
|
116
|
+
## What's new
|
|
117
|
+
|
|
118
|
+
Release notes live in [CHANGELOG.md](./CHANGELOG.md).
|
|
177
119
|
|
|
178
120
|
## Licensing
|
|
179
121
|
|
package/dist/index.js
CHANGED
|
@@ -194,6 +194,12 @@ async function createSession(userId, userAgent) {
|
|
|
194
194
|
VALUES (?, ?, ?, ?)`,
|
|
195
195
|
[id, userId, expiryIso(), userAgent || null]
|
|
196
196
|
);
|
|
197
|
+
try {
|
|
198
|
+
await db.run("UPDATE users SET is_invited = 0 WHERE id = ? AND is_invited = 1", [
|
|
199
|
+
userId
|
|
200
|
+
]);
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
197
203
|
return id;
|
|
198
204
|
}
|
|
199
205
|
async function resolveSession(id) {
|
|
@@ -974,12 +980,6 @@ authRoutes.post("/login", async (c) => {
|
|
|
974
980
|
console.log(`[auth] login OK \u2014 counter reset ip=${ip} email=${emailLower}`);
|
|
975
981
|
if (hasIp) clear(ipKey);
|
|
976
982
|
clear(emailKey);
|
|
977
|
-
if (user.is_invited === 1) {
|
|
978
|
-
try {
|
|
979
|
-
await db.run("UPDATE users SET is_invited = 0 WHERE id = ?", [user.id]);
|
|
980
|
-
} catch {
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
983
|
if (check.needsRehash) {
|
|
984
984
|
try {
|
|
985
985
|
const newHash = await hashPassword(body.password);
|
|
@@ -1051,9 +1051,7 @@ authRoutes.post("/keys", authMiddleware, async (c) => {
|
|
|
1051
1051
|
);
|
|
1052
1052
|
});
|
|
1053
1053
|
authRoutes.post("/keys/rotate", authMiddleware, async (c) => {
|
|
1054
|
-
const body = await c.req.json().catch(
|
|
1055
|
-
() => ({})
|
|
1056
|
-
);
|
|
1054
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1057
1055
|
const db = getDb();
|
|
1058
1056
|
const userId = c.get("userId");
|
|
1059
1057
|
const apiKey = generateApiKey();
|
|
@@ -1062,6 +1060,7 @@ authRoutes.post("/keys/rotate", authMiddleware, async (c) => {
|
|
|
1062
1060
|
"SELECT label, nest_id FROM api_keys WHERE user_id = ?",
|
|
1063
1061
|
[userId]
|
|
1064
1062
|
);
|
|
1063
|
+
const nestScope = "nest_id" in body ? body.nest_id || null : prior?.nest_id ?? null;
|
|
1065
1064
|
await db.transaction(async (tx) => {
|
|
1066
1065
|
await tx.run("DELETE FROM api_keys WHERE user_id = ?", [userId]);
|
|
1067
1066
|
await tx.run(
|
|
@@ -1071,7 +1070,7 @@ authRoutes.post("/keys/rotate", authMiddleware, async (c) => {
|
|
|
1071
1070
|
userId,
|
|
1072
1071
|
hashApiKey(apiKey),
|
|
1073
1072
|
getKeyPrefix(apiKey),
|
|
1074
|
-
|
|
1073
|
+
nestScope,
|
|
1075
1074
|
body.label ?? prior?.label ?? null
|
|
1076
1075
|
]
|
|
1077
1076
|
);
|
|
@@ -10712,18 +10711,31 @@ function createApp() {
|
|
|
10712
10711
|
const nestId = parts[0];
|
|
10713
10712
|
const userId = c.get("userId");
|
|
10714
10713
|
const nestScope = c.get("nestScope");
|
|
10714
|
+
const isMcpTransport = parts.length === 2 && parts[1] === "mcp";
|
|
10715
|
+
const deny = async (status, error, extra) => {
|
|
10716
|
+
if (!isMcpTransport) return c.json({ error, ...extra }, status);
|
|
10717
|
+
let id = null;
|
|
10718
|
+
try {
|
|
10719
|
+
id = (await c.req.json())?.id ?? null;
|
|
10720
|
+
} catch {
|
|
10721
|
+
}
|
|
10722
|
+
const message = typeof extra?.reason === "string" ? `${error} \u2014 ${extra.reason}` : error;
|
|
10723
|
+
return c.json(
|
|
10724
|
+
{ jsonrpc: "2.0", id, error: { code: -32e3, message } },
|
|
10725
|
+
status
|
|
10726
|
+
);
|
|
10727
|
+
};
|
|
10715
10728
|
if (nestScope && nestScope !== nestId) {
|
|
10716
|
-
return
|
|
10729
|
+
return deny(
|
|
10730
|
+
403,
|
|
10731
|
+
`API key not authorized for this nest \u2014 this key is scoped to nest "${nestScope}". Use that nest's MCP URL, or mint a user-level key in the Connect dialog.`
|
|
10732
|
+
);
|
|
10717
10733
|
}
|
|
10718
10734
|
if (isSuspended() && c.req.method !== "GET") {
|
|
10719
|
-
return
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
contact: "support@promptowl.ai"
|
|
10724
|
-
},
|
|
10725
|
-
503
|
|
10726
|
-
);
|
|
10735
|
+
return deny(503, "Server suspended by PromptOwl", {
|
|
10736
|
+
reason: getSuspensionReason(),
|
|
10737
|
+
contact: "support@promptowl.ai"
|
|
10738
|
+
});
|
|
10727
10739
|
}
|
|
10728
10740
|
{
|
|
10729
10741
|
const path2 = c.req.path;
|
|
@@ -10735,14 +10747,10 @@ function createApp() {
|
|
|
10735
10747
|
if (needsLicense) {
|
|
10736
10748
|
const lic = getCurrentLicense();
|
|
10737
10749
|
if (!lic?.valid) {
|
|
10738
|
-
return
|
|
10739
|
-
|
|
10740
|
-
|
|
10741
|
-
|
|
10742
|
-
setup_url: "/setup"
|
|
10743
|
-
},
|
|
10744
|
-
503
|
|
10745
|
-
);
|
|
10750
|
+
return deny(503, "License required", {
|
|
10751
|
+
reason: "This server has no valid PromptOwl license installed. Ask the server admin to install one via the setup screen or POST /license/install.",
|
|
10752
|
+
setup_url: "/setup"
|
|
10753
|
+
});
|
|
10746
10754
|
}
|
|
10747
10755
|
}
|
|
10748
10756
|
}
|
|
@@ -10770,7 +10778,9 @@ function createApp() {
|
|
|
10770
10778
|
grantNestVisible = await hasAnyGrant(nestId, userId);
|
|
10771
10779
|
}
|
|
10772
10780
|
if (!grantRole && !grantNestVisible) {
|
|
10773
|
-
return
|
|
10781
|
+
return deny(404, "Nest not found", {
|
|
10782
|
+
reason: "The nest id may be wrong, or these credentials have no access to it. Ask the nest owner to share the nest with you, then reconnect."
|
|
10783
|
+
});
|
|
10774
10784
|
}
|
|
10775
10785
|
}
|
|
10776
10786
|
let required = "read";
|
|
@@ -10796,7 +10806,7 @@ function createApp() {
|
|
|
10796
10806
|
required = c.req.method === "GET" ? "write" : "admin";
|
|
10797
10807
|
} else if (parts[1] === "hooks") {
|
|
10798
10808
|
required = "write";
|
|
10799
|
-
} else if (c.req.method !== "GET" && !isStewardActionPath && !isCommentAction && !isAnnotationAction && !isReadQuery) {
|
|
10809
|
+
} else if (c.req.method !== "GET" && !isStewardActionPath && !isCommentAction && !isAnnotationAction && !isReadQuery && !isMcpTransport) {
|
|
10800
10810
|
required = "write";
|
|
10801
10811
|
}
|
|
10802
10812
|
const isNodeAction = c.req.method === "POST" && parts.length >= 4 && (parts[parts.length - 1] === "revert" || parts[parts.length - 1] === "move");
|
|
@@ -10825,13 +10835,13 @@ function createApp() {
|
|
|
10825
10835
|
const grantLevel = grantRole ? permissionLevel(grantRole) : grantNestVisible ? permissionLevel("read") : 0;
|
|
10826
10836
|
const effectiveLevel = Math.max(permissionLevel(permission), grantLevel);
|
|
10827
10837
|
if (!stewardEditorBypass && effectiveLevel < permissionLevel(required)) {
|
|
10828
|
-
return
|
|
10838
|
+
return deny(
|
|
10839
|
+
403,
|
|
10840
|
+
`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.`,
|
|
10829
10841
|
{
|
|
10830
|
-
error: `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.`,
|
|
10831
10842
|
required_permission: required,
|
|
10832
10843
|
your_permission: permission
|
|
10833
|
-
}
|
|
10834
|
-
403
|
|
10844
|
+
}
|
|
10835
10845
|
);
|
|
10836
10846
|
}
|
|
10837
10847
|
c.set(
|