@rubytech/create-realagent 1.0.825 → 1.0.828
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/package.json +1 -1
- package/payload/platform/lib/task-secrets/dist/index.d.ts +40 -0
- package/payload/platform/lib/task-secrets/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/task-secrets/dist/index.js +44 -0
- package/payload/platform/lib/task-secrets/dist/index.js.map +1 -0
- package/payload/platform/lib/task-secrets/src/__tests__/redact-secrets.test.ts +127 -0
- package/payload/platform/lib/task-secrets/src/index.ts +77 -0
- package/payload/platform/lib/task-secrets/tsconfig.json +9 -0
- package/payload/platform/lib/task-secrets/vitest.config.ts +9 -0
- package/payload/platform/neo4j/schema.cypher +34 -2
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +19 -13
- package/payload/platform/plugins/admin/skills/business-profile/SKILL.md +2 -2
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +13 -12
- package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +4 -4
- package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +2 -2
- package/payload/platform/plugins/admin/skills/stream-log-review/SKILL.md +6 -6
- package/payload/platform/plugins/admin/skills/unzip-attachment/references/safety.md +1 -1
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +3 -3
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +4 -4
- package/payload/platform/plugins/docs/references/cloudflare.md +2 -2
- package/payload/platform/plugins/docs/references/internals.md +2 -2
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +2 -1
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +2 -2
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/connections.md +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +1 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +6 -41
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +51 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +19 -4
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +139 -56
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js +61 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +34 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +241 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/references/graph-primitives.md +5 -5
- package/payload/platform/plugins/memory/references/schema-base.md +6 -3
- package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +6 -6
- package/payload/platform/plugins/tasks/PLUGIN.md +1 -1
- package/payload/platform/plugins/tasks/mcp/dist/index.js +11 -2
- package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts +19 -2
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts.map +1 -1
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js +17 -1
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js.map +1 -1
- package/payload/platform/plugins/whatsapp-import/PLUGIN.md +17 -15
- package/payload/platform/plugins/whatsapp-import/bin/ingest.mjs +313 -366
- package/payload/platform/plugins/whatsapp-import/bin/whatsapp-ingest.sh +27 -60
- package/payload/platform/plugins/whatsapp-import/lib/dist/delta-cursor.d.ts +18 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/delta-cursor.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/delta-cursor.js +31 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/delta-cursor.js.map +1 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.d.ts +27 -12
- package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.d.ts.map +1 -1
- package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.js +40 -20
- package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.js.map +1 -1
- package/payload/platform/plugins/whatsapp-import/lib/dist/index.d.ts +7 -4
- package/payload/platform/plugins/whatsapp-import/lib/dist/index.d.ts.map +1 -1
- package/payload/platform/plugins/whatsapp-import/lib/dist/index.js +9 -6
- package/payload/platform/plugins/whatsapp-import/lib/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp-import/lib/dist/sessionize.d.ts +25 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/sessionize.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/sessionize.js +48 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/sessionize.js.map +1 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/to-classifier-input.d.ts +3 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/to-classifier-input.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/to-classifier-input.js +47 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/to-classifier-input.js.map +1 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/delta-append.test.ts +163 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/sessionize.test.ts +91 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/to-classifier-input.test.ts +59 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/delta-cursor.ts +54 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/derive-keys.ts +55 -32
- package/payload/platform/plugins/whatsapp-import/lib/src/index.ts +9 -6
- package/payload/platform/plugins/whatsapp-import/lib/src/sessionize.ts +81 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/to-classifier-input.ts +48 -0
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/SKILL.md +66 -73
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/conversation-archive-shape.md +143 -0
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/export-parse.md +2 -2
- package/payload/platform/templates/specialists/agents/database-operator.md +17 -18
- package/payload/server/chunk-T2OPNP3L.js +654 -0
- package/payload/server/chunk-ZTBTX3IO.js +642 -0
- package/payload/server/cloudflare-task-tracker-BAMJY4MH.js +17 -0
- package/payload/server/cloudflare-task-tracker-CR6TL4VL.js +19 -0
- package/payload/server/public/assets/{admin-DOkUspG1.js → admin-BNwPsMhJ.js} +2 -2
- package/payload/server/public/assets/{graph-LLMJa4Ch.js → graph-N_Bw-8oT.js} +1 -1
- package/payload/server/public/assets/{page-DoaF3DB0.js → page-BKLGP-th.js} +1 -1
- package/payload/server/public/graph.html +2 -2
- package/payload/server/public/index.html +2 -2
- package/payload/server/server.js +291 -172
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/filter-gate.test.ts +0 -172
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/ingest-idempotence.test.ts +0 -141
- package/payload/platform/plugins/whatsapp-import/lib/src/filter.ts +0 -136
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import-enrich/SKILL.md +0 -333
|
@@ -10,7 +10,7 @@ description: >
|
|
|
10
10
|
|
|
11
11
|
# Stream Log Review
|
|
12
12
|
|
|
13
|
-
Analyse Claude agent stream logs — the `claude-agent-stream-{conversationId}.log` files generated by the platform
|
|
13
|
+
Analyse Claude agent stream logs — the `claude-agent-stream-{conversationId}.log` files generated by the platform. Each file is scoped to exactly one conversation; a resumed conversation accumulates in one file across multiple process lifetimes, delimited by `[spawn]` / `[process-exit]`. Pre-conversation events (CDP auth-poll, module-init warnings) live in `preconversation-claude-agent-stream-{YYYY-MM-DD}.log` — a separate file per UTC day, read only when investigating boot-time failures.
|
|
14
14
|
|
|
15
15
|
Parse the log, identify problems, attempt diagnosis, and produce a structured report.
|
|
16
16
|
|
|
@@ -35,7 +35,7 @@ Parse the log, identify problems, attempt diagnosis, and produce a structured re
|
|
|
35
35
|
On the Pi, use the `logs-read` MCP tool with `conversationId` to retrieve a single conversation's log. From the dev machine (SSH), use the shell counterpart:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
# Read a single conversation's stream log (primary mode
|
|
38
|
+
# Read a single conversation's stream log (primary mode)
|
|
39
39
|
sshpass -p 'password' ssh admin@<hostname>.local "~/<installDir>/platform/scripts/logs-read.sh <conversationId> system"
|
|
40
40
|
|
|
41
41
|
# Tail the most-recently-active stream log (any conversation)
|
|
@@ -53,11 +53,11 @@ The `conversationId` is visible in the admin UI and appears on every `[spawn]` /
|
|
|
53
53
|
- `[tool-wait-diag]` at 15 / 30 / 45 / 60 s — confirms DNS/TCP/HTTP health across a stall window (not just at the timeout moment).
|
|
54
54
|
- `[tool-wait-proc]` at 30 s — subprocess open FDs, sockets by TCP state, RSS.
|
|
55
55
|
- `[subproc-stderr]` — line from the main Claude Code subprocess's stderr. Today the CLI is a bundled Bun binary that ignores Node's `NODE_DEBUG`, so this channel is normally silent — see `[subproc-debug-unavailable]` below. MCP server stderr lines arrive separately as `[mcp:<server>]`.
|
|
56
|
-
- `[subproc-stderr-tee-attached]` / `[subproc-stderr-tee-detached]`
|
|
57
|
-
- `[subproc-debug-unavailable]`
|
|
58
|
-
- `[tool-failure-diag]` — one-shot probe on the failure path
|
|
56
|
+
- `[subproc-stderr-tee-attached]` / `[subproc-stderr-tee-detached]` — lifecycle of the main-subprocess stderr tee. `bytes=0 lines=0` on detach means the tee worked but the subprocess emitted nothing. Absence of both markers next to a `[spawn]` means the tee infrastructure is broken — escalate.
|
|
57
|
+
- `[subproc-debug-unavailable]` — one line per spawn, `reason=bundled-bun-binary-ignores-node-debug`. This is the documented reason `[subproc-stderr]` lines are normally absent for the main subprocess. Treat its absence as a regression, not its presence as a problem.
|
|
58
|
+
- `[tool-failure-diag]` — one-shot probe on the failure path; complements the mid-flight `[tool-wait-diag]` with end-of-wait network state.
|
|
59
59
|
- `[mcp-tee-attach]` / `[mcp-tee-skip]` / `[mcp:<server>]` — MCP server stderr routed into the stream log; a missing attach marker explains missing server diagnostics.
|
|
60
|
-
- `[tool-result] error=true output="WEBFETCH_CANNOT_READ_JS_SPA: …"` — a WebFetch dispatch was short-circuited by the SPA preflight hook
|
|
60
|
+
- `[tool-result] error=true output="WEBFETCH_CANNOT_READ_JS_SPA: …"` — a WebFetch dispatch was short-circuited by the SPA preflight hook. The URL is a JS-SPA shell that WebFetch cannot extract content from. The agent should have responded to the user naming the failure and asking for a paste or screenshot; if instead an `[agent-dispatch]` (Playwright, research-assistant) follows immediately, the loud-failure guidance in IDENTITY.md did not land. The hook's per-invocation decision trail lives in `{accountDir}/logs/webfetch-preflight.log` (one `[webfetch-preflight] verdict=…` line per probe).
|
|
61
61
|
|
|
62
62
|
## Boundaries
|
|
63
63
|
|
|
@@ -78,4 +78,4 @@ All of these are from `unzip` (InfoZIP) + `coreutils` — installed on every Pi
|
|
|
78
78
|
|
|
79
79
|
## Why this is a skill, not a tool
|
|
80
80
|
|
|
81
|
-
Doctrine
|
|
81
|
+
Doctrine: the admin agent has `Bash`; the security-critical code path is a sequence of shell commands whose determinism is enforced by the shell primitives themselves, not by LLM reasoning. Wrapping this in an MCP tool would add a translation layer without adding enforcement — a tool wrapper is still LLM-mediated at the decision boundary. The shell script is the deterministic primitive; the skill tells the agent which primitive to invoke, in which order, against which argument.
|
|
@@ -217,7 +217,7 @@ cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns
|
|
|
217
217
|
2. Overwritten: `Added CNAME <hostname> which will route to this tunnel` (same shape as new; `--overwrite-dns` makes overwrite transparent)
|
|
218
218
|
3. Idempotent no-op: `<timestamp> INF <hostname> is already configured to route to your tunnel tunnelID=<UUID>`
|
|
219
219
|
|
|
220
|
-
Shape 3 is what a second clean run of setup-tunnel.sh against an already-configured hostname emits. Historically the shell script's stdout parser rejected this shape and exited 1 on the idempotent case
|
|
220
|
+
Shape 3 is what a second clean run of setup-tunnel.sh against an already-configured hostname emits. Historically the shell script's stdout parser rejected this shape and exited 1 on the idempotent case; the script now relies on cloudflared's exit code exclusively.
|
|
221
221
|
|
|
222
222
|
**If it fails with `zone not found`:** the hostname's parent domain isn't on this brand's Cloudflare account. Either add it in the dashboard (Websites → Add a site) and re-run, or sign into the account that already owns the domain.
|
|
223
223
|
|
|
@@ -325,14 +325,14 @@ You do **not** run `cloudflared` manually. The brand's existing user-space syste
|
|
|
325
325
|
systemctl --user restart "${BRAND}.service"
|
|
326
326
|
```
|
|
327
327
|
|
|
328
|
-
**Why the script dispatches the restart via `systemd-run` instead of a direct `systemctl restart
|
|
328
|
+
**Why the script dispatches the restart via `systemd-run` instead of a direct `systemctl restart`:** when the admin agent invokes `setup-tunnel.sh` via the Bash tool, the script runs *inside* `${BRAND}.service`'s cgroup. A direct `systemctl --user restart ${BRAND}.service` from that cgroup tells systemd to SIGTERM the entire cgroup — the node server, the claude subprocess, the Bash child, and the script itself all die simultaneously. cgroup membership is inherited: `setsid`, `nohup`, `disown`, and `&` all stay in the caller's cgroup, and `systemd-run --scope` runs in the caller's scope. Only `systemd-run --user --unit=<name> --on-active=<N>s` creates a genuinely new transient unit with its own cgroup. The script uses that primitive to arm the restart a few seconds after its own exit:
|
|
329
329
|
|
|
330
330
|
```
|
|
331
331
|
systemd-run --user --unit=maxy-tunnel-restart-<nonce>.service --on-active=3s --collect \
|
|
332
332
|
/bin/systemctl --user restart "${BRAND}.service"
|
|
333
333
|
```
|
|
334
334
|
|
|
335
|
-
The script then emits `[script:setup-tunnel] step=service-restart-dispatched` and `step=service-restart-armed exit=0` in the per-conversation stream log so operators see exactly when the restart was scheduled, exits 0, and the transient timer fires from outside the service's cgroup — semantically identical to this manual runbook's `systemctl --user restart`. (The `script:` prefix is
|
|
335
|
+
The script then emits `[script:setup-tunnel] step=service-restart-dispatched` and `step=service-restart-armed exit=0` in the per-conversation stream log so operators see exactly when the restart was scheduled, exits 0, and the transient timer fires from outside the service's cgroup — semantically identical to this manual runbook's `systemctl --user restart`. (The `script:` prefix is the chat-surface namespace — see `_stream-log.sh` header.)
|
|
336
336
|
|
|
337
337
|
When walking through manually you do **not** need `systemd-run` — your SSH shell already lives in a separate user-scope cgroup (`user@<uid>.service`), so the direct `systemctl restart` does not kill the caller. The script's extra indirection only matters when the caller *is* the service being restarted.
|
|
338
338
|
|
|
@@ -20,9 +20,9 @@ Any Cloudflare action outside these four surfaces is a discipline violation —
|
|
|
20
20
|
|
|
21
21
|
## 1. Autonomous path — `setup-tunnel.sh`
|
|
22
22
|
|
|
23
|
-
Use this when the operator wants Cloudflare set up (or re-set up) end-to-end on the device. The script handles OAuth login, tunnel creation, DNS routing for each subdomain, config.yml + tunnel.state, and dispatches the `${BRAND}.service` restart to a transient `systemd-run` unit
|
|
23
|
+
Use this when the operator wants Cloudflare set up (or re-set up) end-to-end on the device. The script handles OAuth login, tunnel creation, DNS routing for each subdomain, config.yml + tunnel.state, and dispatches the `${BRAND}.service` restart to a transient `systemd-run` unit — all in one invocation. The restart fires a few seconds after the script exits so the script does not kill its own cgroup when invoked via the Bash tool; the chat UI receives a `server_shutdown` SSE frame and reconnects automatically. Post-restart hostname verification is out of scope for the script (connector is not up when the script exits) — verify via the next admin turn or manually with `curl -I https://<hostname>`. Apex hostnames cannot be routed by the CLI; when one is passed, the script prints an `ACTION REQUIRED` block naming the exact dashboard record to edit.
|
|
24
24
|
|
|
25
|
-
Step 1's OAuth flow is a state machine over two observable variables: the brand-scoped cert path (`${CFG_DIR}/cert.pem`) and the OAuth-default cert path (`~/.cloudflared/cert.pem`). When the brand-scoped cert is missing but the default-path cert is present from any prior partial run, the wrapper promotes it (`mv`) and emits `step=oauth-login result=ok reason=cert-promoted-from-default-path` without re-spawning cloudflared. When both are missing, the wrapper spawns `cloudflared tunnel login`, extracts the argotunnel URL from its stdout, and the instant the URL surfaces, mechanically opens it on the Pi's VNC chromium (`DISPLAY=${DISPLAY:-:99} /usr/bin/chromium <url> &`) — emitting `step=browser-spawn result=ok` and `step=browser-drive mode=operator-click`. The operator clicks the zone row + Authorize on the VNC; cloudflared's callback writes `~/.cloudflared/cert.pem`; the wrapper's cert-poll (180 s budget) picks it up and `mv`s it to the brand-scoped path. There is no CDP auto-click, no DOM matcher, no consent-page driver — the wrapper's job is to faithfully relay `cloudflared tunnel login`, never to layer automation on top
|
|
25
|
+
Step 1's OAuth flow is a state machine over two observable variables: the brand-scoped cert path (`${CFG_DIR}/cert.pem`) and the OAuth-default cert path (`~/.cloudflared/cert.pem`). When the brand-scoped cert is missing but the default-path cert is present from any prior partial run, the wrapper promotes it (`mv`) and emits `step=oauth-login result=ok reason=cert-promoted-from-default-path` without re-spawning cloudflared. When both are missing, the wrapper spawns `cloudflared tunnel login`, extracts the argotunnel URL from its stdout, and the instant the URL surfaces, mechanically opens it on the Pi's VNC chromium (`DISPLAY=${DISPLAY:-:99} /usr/bin/chromium <url> &`) — emitting `step=browser-spawn result=ok` and `step=browser-drive mode=operator-click`. The operator clicks the zone row + Authorize on the VNC; cloudflared's callback writes `~/.cloudflared/cert.pem`; the wrapper's cert-poll (180 s budget) picks it up and `mv`s it to the brand-scoped path. There is no CDP auto-click, no DOM matcher, no consent-page driver — the wrapper's job is to faithfully relay `cloudflared tunnel login`, never to layer automation on top.
|
|
26
26
|
|
|
27
27
|
### How inputs reach the script
|
|
28
28
|
|
|
@@ -52,7 +52,7 @@ The agent does not invoke the script directly during onboarding — the endpoint
|
|
|
52
52
|
|
|
53
53
|
The endpoint returns `{ ok: false, field: "script", message, output }` and the form surfaces the error inline. Relay the output to the user, name the exit code, and cite `references/reset-guide.md` for the next action. Offer to re-render the form after any manual steps the script's error output named. Do not attempt a second invocation outside the form, a Playwright-driven dashboard inspection, or an alternative `cloudflared` command sequence. The discipline rule below applies.
|
|
54
54
|
|
|
55
|
-
When the failure reason is `timeout-waiting-cert` (
|
|
55
|
+
When the failure reason is `timeout-waiting-cert` (operator did not click Authorize within the 180 s budget), the form surfaces the timeout and the operator can re-submit. The page is still on the Pi VNC; the operator can click Authorize there and the next form submit will complete via the cert-promotion pre-flight (the cert lands in `~/.cloudflared/cert.pem` after consent, and the wrapper's `mv` runs on the next invocation). Do not suggest `~/reset-tunnel.sh` — the cert path is intact and a fresh attempt is the only remediation needed.
|
|
56
56
|
|
|
57
57
|
---
|
|
58
58
|
|
|
@@ -90,7 +90,7 @@ Example:
|
|
|
90
90
|
|
|
91
91
|
Use this when the operator needs to do something only the Cloudflare dashboard can do: sign in, switch accounts, add a site, edit an apex CNAME, verify zone nameservers, delete a tunnel after stopping its replicas. The guide has one numbered click-path per operation. Quote the relevant click-path verbatim — the operator follows it in the browser. The agent does not drive dashboard mutations via Playwright or Chrome DevTools.
|
|
92
92
|
|
|
93
|
-
The single exception is `list-cf-domains.sh
|
|
93
|
+
The single exception is `list-cf-domains.sh`, which reads the domains attached to the logged-in account to populate the `cloudflare-setup-form` dropdowns. That script is deterministic (bash + raw CDP, no LLM in the decision path), invoked only by the `/api/admin/cloudflare/domains` route, and produces only a JSON `string[]` on stdout; no dashboard state is changed. Any dashboard scrape that is not this exact script is forbidden — the agent does not extend this carve-out to new scripts it writes, hypothesises, or finds. Adding a new sanctioned scrape surface requires a code change reviewed as a doctrine change, not an inline agent decision.
|
|
94
94
|
|
|
95
95
|
---
|
|
96
96
|
|
|
@@ -22,7 +22,7 @@ Ask the agent to set up Cloudflare. The agent first confirms the domain is alrea
|
|
|
22
22
|
- **Proxy apex** — optional bare-domain hostname (e.g. `yourdomain.com`) that should also serve the public agent.
|
|
23
23
|
- **Admin password** — the password used to gate remote access to the admin surface.
|
|
24
24
|
|
|
25
|
-
When you submit, the `/api/admin/cloudflare/setup` endpoint runs — in strict order — `setRemotePassword`, launches a `cloudflare-setup` action (earlier platform fixes: `systemd-run --user` transient unit wrapping `setup-tunnel.sh <brand> <port> <hostname...>`), and registers a post-exit handler
|
|
25
|
+
When you submit, the `/api/admin/cloudflare/setup` endpoint runs — in strict order — `setRemotePassword`, launches a `cloudflare-setup` action (earlier platform fixes: `systemd-run --user` transient unit wrapping `setup-tunnel.sh <brand> <port> <hostname...>`), and registers a post-exit handler that writes alias-domains for every non-`public.*` public or apex hostname (so e.g. `chat.yourdomain.com` is classified as public by `isPublicHost`) and closes the audit Task. When the post-exit handler observes the systemd-run unit gone before its terminal status was sampled (a known systemd-run cgroup race), it consults `tunnel.state` on disk and verifies the tunnel identity matches the form you submitted; if so, the audit Task closes `completed` despite the missed observation. Otherwise it closes `failed` with `endpoint-died-pre-reconcile` and the boot reconciler may still recover the Task at next restart. The script runs end-to-end:
|
|
26
26
|
|
|
27
27
|
- `cloudflared tunnel login` — OAuth browser sign-in. The VNC browser opens the Cloudflare authorize page; pick the account that owns your domain, click Authorize. `cert.pem` lands.
|
|
28
28
|
- Tunnel resolution from operator-supplied identity (operator-selected-tunnel fix). The form populates a tunnel-select dropdown from `GET /api/admin/cloudflare/tunnels` (which calls `cloudflared tunnel list --output json` on your logged-in account). You either pick an existing tunnel from the list or type a name to create a new one. The form posts EXACTLY ONE of `{tunnelId, tunnelName}`; the script enforces the same constraint as defence in depth. Pre-fix the script derived `${BRAND}-$(hostname -s)` locally; that broke the operator-state-is-authoritative doctrine and silently created orphan tunnels whenever the device hostname changed. Stream log emits `step=tunnel-resolve source=operator-selected|operator-created tunnel_id=… tunnel_name=…` once the UUID is known.
|
|
@@ -30,7 +30,7 @@ When you submit, the `/api/admin/cloudflare/setup` endpoint runs — in strict o
|
|
|
30
30
|
- `cloudflared tunnel route dns` for each subdomain hostname. Apex hostnames cannot be routed this way — the script prints an **ACTION REQUIRED** block naming the exact dashboard record to add or edit. Stream log emits `step=route-dns hostname=… tunnel_id=…` before the call and `step=route-dns hostname=… result=ok|apex-skip|error` after; on error the bounded cloudflared stderr (≤400 chars) rides in the same phase line. **The script does not parse cloudflared's stdout** — exit code is the sole decision signal, so all three legitimate cloudflared output shapes (new record, overwrite, idempotent "already configured") are treated as success.
|
|
31
31
|
- `config.yml` and `tunnel.state` written under `${CFG_DIR}`.
|
|
32
32
|
- **Step-7 onboarding completion persisted** — the script writes `${ACCOUNT_DIR}/onboarding/step7-complete` (a JSON marker with the completion timestamp and tunnel ID) before arming the restart. Stream log: `step=onboarding-persist result=ok|error reason=<r>`. The marker is consumed by the next admin session's first state read and advances `OnboardingState.currentStep` to 7. Without this, the service restart below would SIGTERM the admin agent before it could persist step-7 completion, and the next session would re-ask the Cloudflare question you just finished. Both invocation surfaces (the form-driven action and the agent-via-Bash path) declare `ACCOUNT_DIR` explicitly because `systemd-run --user` does not inherit parent env — when ACCOUNT_DIR isn't reaching the script you'll see `result=skipped reason=no-account-dir` in the stream log instead of `result=ok`.
|
|
33
|
-
- **Post-restart resume contract** — when the
|
|
33
|
+
- **Post-restart resume contract** — when the script exits cleanly, the form fires a client-side resume event. The chat hook ([useAdminChat.ts](../../../../platform/ui/app/useAdminChat.ts)) waits for the brand-service to come back via `/api/health` (down-then-up), re-binds the conversation to the new server process, and sends the "Cloudflare setup completed" marker as a normal hidden chat POST that re-invokes the agent in a fresh session. No relay queue, no boot-drain, no banner. Diagnostic: `grep '\[admin-resume\] reason=post-restart' ~/{configDir}/logs/server.log` (expect one line per restart cycle), `grep '\[client-event\] kind=post-restart-resume' ~/{configDir}/logs/server.log` for the operator-visible client trace. See `.docs/web-chat.md` "Post-restart resume contract" for the full client/server contract.
|
|
34
34
|
- `systemctl --user restart ${BRAND}.service` — restarts the platform service so the new tunnel spawns via the service's `ExecStartPre=resume-tunnel.sh`.
|
|
35
35
|
- Post-restart verification — `ps -ef | grep '[c]loudflared'` confirms the connector is alive, then `curl -I https://<hostname>` against each subdomain (up to 60 s per host) confirms a non-530 response.
|
|
36
36
|
|
|
@@ -471,9 +471,9 @@ Each log entry includes the tool name and a truncated conversation ID for correl
|
|
|
471
471
|
|
|
472
472
|
Every durable action — onboarding, cloudflare tunnel-login, brand publish, future deterministic flows — emits a `:Task {kind:"<flow>"}` node carrying the action's lifecycle and a `:PRODUCED` edge to every entity the action created. This makes the graph traversable from the originating Conversation to every entity created during it via `(c)<-[:RAISED_DURING]-(t:Task)-[:PRODUCED]->(e)` — answering "what did this turn produce" in one Cypher hop.
|
|
473
473
|
|
|
474
|
-
The doctrine is enforced at the storage primitive: writes to `:Person`, `:UserProfile`, `:AdminUser`, `:Organization`, `:LocalBusiness`, `:CloudflareTunnel`, or `:CloudflareHostname` MUST include exactly one inbound `:PRODUCED` edge whose source is a `:Task`. Bootstrap writes (PIN-setup, schema migrations, lazy
|
|
474
|
+
The doctrine is enforced at the storage primitive: writes to `:Person`, `:UserProfile`, `:AdminUser`, `:Organization`, `:LocalBusiness`, `:CloudflareTunnel`, or `:CloudflareHostname` MUST include exactly one inbound `:PRODUCED` edge whose source is a `:Task`. Bootstrap writes (PIN-setup, schema migrations, lazy first-session UserProfile creation) are exempt via `createdBy.agent === 'system'`.
|
|
475
475
|
|
|
476
|
-
Two surfaces emit the lifecycle: agent-driven actions call `task-create`/`task-update`/`task-complete` over MCP (`task-create` accepts `kind`, `inputsProvided`
|
|
476
|
+
Two surfaces emit the lifecycle: agent-driven actions call `task-create`/`task-update`/`task-complete` over MCP (`task-create` accepts `kind`, the canonical `inputsProvided` call-shape record, `inputs` + `inputSchema` for the operator-meaningful form payload, and `raisedDuringConversationKey` to resolve the `RAISED_DURING` edge). Shell-driven actions wrap their script invocation in [platform/ui/app/lib/cloudflare-task-tracker.ts](../../../ui/app/lib/cloudflare-task-tracker.ts) (cloudflare is the first; installer / brand-publish / OAuth-login deferred). Both surfaces emit the same `[task] action-start|step|done` log lines so operators can grep one channel uniformly. Both also call the central `redactSecrets` primitive ([platform/lib/task-secrets/](../../../lib/task-secrets/)) to strip schema-tagged secret keys before persisting `inputs.<field>` props on the Task — see `.docs/neo4j.md § Audit Task input contract` for the contract that replaces per-kind allow-lists.
|
|
477
477
|
|
|
478
478
|
`memory-write` accepts an optional `producedByTaskId` parameter. When set, an inbound `:PRODUCED` edge from that Task is composed into the write's relationships before the gate runs — the typical agent-side pattern is to call `task-create` at the start of a flow, capture `taskId`, and pass it as `producedByTaskId` on every subsequent `memory-write` for an action-provenance-gated label. The gate verifies Task and write share the same `accountId`; mismatch is rejected loud.
|
|
479
479
|
|
|
@@ -40,7 +40,7 @@ These are enabled during onboarding and can be added or removed at any time. Som
|
|
|
40
40
|
| `waitlist` | Waitlist lifecycle — extract sign-ups from conversations, review | — |
|
|
41
41
|
| `replicate` | Image generation — three models for photorealistic, design, and fast draft images | Content producer, Research assistant |
|
|
42
42
|
| `linkedin-import` | Import a LinkedIn Basic Data Export — Profile and Connections today, more CSVs as references land | Database operator |
|
|
43
|
-
| `whatsapp-import` | Import a WhatsApp `_chat.txt` export
|
|
43
|
+
| `whatsapp-import` | Import a WhatsApp `_chat.txt` export as a chunked `:ConversationArchive` shape (the chunked-archive contract — supersedes the prior two-phase load+enrich contract). Single Bash entry — `bash platform/plugins/whatsapp-import/bin/whatsapp-ingest.sh <archive> --owner-element-id <id> --participant-person-ids <csv> --scope <admin\|public>` — runs parse → operator-confirms owner + every distinct sender → sessionize at gap-hours boundaries (default 12h) → classify each session via Haiku (`memory-classify` with `mode='chat'`) into topic-bounded `:Section:Conversation` chunks → memory-ingest with `parentLabel='ConversationArchive'`. The parent MERGEs on `conversationIdentity = sha256(accountId + ":" + sortedParticipantElementIds)` — stable across re-exports, identical for DM and group. Re-imports are delta-append: prior chunks never touched, only messages after `lastIngestedMessageHash` flow through. Auto-creating participants is forbidden — any parsed senderName outside the operator-confirmed closed set LOUD-FAILs with `parser-miss`. Phase 2 insight derivation (`:Observation` / `:Task` / `:Preference` / `:MENTIONS` against chunks) is deferred to a separate follow-up task. Distinct from the live `whatsapp` plugin which is a Baileys QR-pairing channel. | Database operator |
|
|
44
44
|
|
|
45
45
|
### Claude Official (marketplace)
|
|
46
46
|
|
|
@@ -192,7 +192,7 @@ The transient unit was auto-collected by systemd before the client subscribed. R
|
|
|
192
192
|
|
|
193
193
|
**Cloudflare-setup auto-relays "completed" but the next chat turn 502s.**
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
The current contract is a single client-driven resume: when the setup script exits cleanly, the form fires a resume event, the chat hook waits for `/api/health` to fail then succeed (the brand-service down-then-up cycle), re-binds the conversation to the new server process, and sends the "completed" marker as a normal hidden chat POST that re-invokes the agent in the operator's real session. No relay queue, no boot-drain, no banner.
|
|
196
196
|
|
|
197
197
|
Diagnostic recipe:
|
|
198
198
|
|
|
@@ -207,6 +207,7 @@ Failure modes:
|
|
|
207
207
|
- `[client-event] kind=post-restart-resume phase=start` absent: form's CustomEvent never reached the chat hook (regression in form `onExit` or chat-listener mount).
|
|
208
208
|
- `[client-event] phase=start` present, `[admin-resume] reason=post-restart` absent: `waitForRestartCycle` exhausted its bound (brand never restarted) or `/resume` rejected — check `phase=health-timeout` / `phase=resume-rejected`.
|
|
209
209
|
- All four present except `[persist] role=user … Cloudflare setup completed`: marker chat POST failed — check the chat surface for an inline error or a `phase=resume-error` line.
|
|
210
|
+
- Refresh after a successful Cloudflare setup shows a visible `Cloudflare setup completed (actionId: …)` user bubble: the resume mappers' synthetic-marker suppression broke or the marker shape drifted. Server-side `[admin-resume] syntheticHidden=<n>` field on the same line counts user rows the client should hide (`_componentDone` envelopes, `_lifecycle` envelopes, and the `Cloudflare setup completed (actionId: …)` literal) — a count of 0 against a refresh that should have hidden one means the helper at `platform/ui/app/lib/synthetic-marker.ts` no longer recognises the producing literal (check `CloudflareSetupForm.tsx` and `useAdminChat.ts` for shape drift).
|
|
210
211
|
|
|
211
212
|
---
|
|
212
213
|
|
|
@@ -51,7 +51,7 @@ When the owner is an external Person (non-operator archive), the anchor is the c
|
|
|
51
51
|
- LinkedIn skills → `:DefinedTerm` with `category: 'linkedin-skill'`.
|
|
52
52
|
- LinkedIn articles → `:CreativeWork` with `category: 'linkedin-article'`.
|
|
53
53
|
- LinkedIn recommendations → `:Review`.
|
|
54
|
-
- LinkedIn DMs → `:Message:LinkedInMessage` + `:Conversation:LinkedInConversation` (sublabels on the existing labels
|
|
54
|
+
- LinkedIn DMs → `:Message:LinkedInMessage` + `:Conversation:LinkedInConversation` (sublabels on the existing labels).
|
|
55
55
|
- Certifications → `:Credential` (genuinely new — no existing {{productName}} label fits `schema:EducationalOccupationalCredential`).
|
|
56
56
|
5. **Schema-base property names.** `givenName` / `familyName` / `telephone` / `dateSent` — not `firstName` / `phone` / `sentAt`. See [`platform/plugins/memory/references/schema-base.md`](../../../../plugins/memory/references/schema-base.md).
|
|
57
57
|
6. **Schema-base edge names.** `(:Person)-[:WORKS_FOR]->(:Organization)` for positions. Custom LinkedIn edges (`CONNECTED_ON_LINKEDIN`, `ENDORSED`) only where schema-base has no canonical name.
|
|
@@ -68,7 +68,7 @@ When the owner is an external Person (non-operator archive), the anchor is the c
|
|
|
68
68
|
|
|
69
69
|
**Doctrine:** raw Cypher and `cypher-shell` invocations are forbidden in this skill and its references. Writes route through `mcp__memory__memory-archive-write` (bulk archives) or `mcp__memory__memory-write` / `mcp__memory__memory-update` (single-node enrichments like `profile.md`). If a CSV needs a write shape no current MCP tool supports, file a task to extend `memory-archive-write` with a new `archiveType` handler — never improvise via Bash. See [database-operator's LOUD-FAIL prerogative](../../../../templates/specialists/agents/database-operator.md#prerogatives).
|
|
70
70
|
|
|
71
|
-
**LOUD-FAIL on parse errors (structurally enforced
|
|
71
|
+
**LOUD-FAIL on parse errors (structurally enforced).** When LinkedIn parser tools land that follow the `mcp__*__*-import-parse` naming convention, the harness-level `platform/plugins/admin/hooks/archive-ingest-gate.sh` will record an `isError: true` response and block every subsequent tool call this turn until the operator submits the next prompt. The hook also denies edits to `platform/plugins/*/lib/*` and JavaScript test runners (`vitest`, `bun test`, `npm test`, `npx jest`) unconditionally. The skill's "no Bash improvisation" doctrine above is the contract; the hook is the enforcement. See [.docs/hooks.md](../../../../../.docs/hooks.md) for the full gate surface.
|
|
72
72
|
|
|
73
73
|
## Selective-ingest threshold (bulk archives)
|
|
74
74
|
|
package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/connections.md
CHANGED
|
@@ -28,7 +28,7 @@ The real column header is **line 4**. Either skip the first three lines before p
|
|
|
28
28
|
| URL | `Person.linkedinUrl` (natural key) |
|
|
29
29
|
| Email Address | `Person.email` (only when non-empty) |
|
|
30
30
|
| Company | `Organization.name` |
|
|
31
|
-
| Position | `[:WORKS_FOR].title` AND `Person.currentTitle` (denormalised
|
|
31
|
+
| Position | `[:WORKS_FOR].title` AND `Person.currentTitle` (denormalised) |
|
|
32
32
|
| Connected On | `[:CONNECTED_ON_LINKEDIN].connectedOn` (ISO 8601) |
|
|
33
33
|
|
|
34
34
|
The `Position` column is written **twice**: once on the `[:WORKS_FOR]` edge as the source of truth (preserves the per-employer history when a connection later moves), and again as `Person.currentTitle` so Neo4j's `entity_search` BM25 index reaches it. Edge properties are invisible to a node-only fulltext index, so without the denormalisation an operator searching "director" against 5K connections returns near-zero hits regardless of how many directors are in the graph. The SET is unconditional — re-importing a row with an empty Position clears `currentTitle`, matching the LinkedIn export's "current snapshot" semantics. To filter your import to a specific role before writing, use the [Selective-ingest threshold](#selective-ingest-threshold) gate.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: memory
|
|
3
|
-
description: "Graph memory plugin. Provides memory-search, memory-rank, memory-write, and memory-update tools for reading from, writing to, and updating the Neo4j knowledge graph. Includes conversational memory — organic preference learning, evidence-backed recall, and transparent 'what do you know about me?' responses."
|
|
3
|
+
description: "Graph memory plugin. Provides memory-search, memory-rank, memory-write, and memory-update tools for reading from, writing to, and updating the Neo4j knowledge graph. Includes conversational memory — organic preference learning, evidence-backed recall, and transparent 'what do you know about me?' responses. Document ingestion (memory-classify + memory-ingest) supports two modes: `document` (default) for unstructured PDF/web content → KnowledgeDocument + Section, and `chat` for chat archives → ConversationArchive + Section:Conversation chunks (the chunked-archive contract)."
|
|
4
4
|
tools:
|
|
5
5
|
- memory-search
|
|
6
6
|
- memory-rank
|
|
@@ -16,7 +16,7 @@ import { memoryArchiveWrite } from "./tools/memory-archive-write.js";
|
|
|
16
16
|
import { whatsappExportParse } from "./tools/whatsapp-export-parse.js";
|
|
17
17
|
import { whatsappExportInsightWrite } from "./tools/whatsapp-export-insight-write.js";
|
|
18
18
|
import { whatsappExportPreview } from "./tools/whatsapp-export-preview.js";
|
|
19
|
-
|
|
19
|
+
// Task 891: whatsapp-export-insight-pass deleted (Phase 2 deferred).
|
|
20
20
|
import { memoryIngestWeb } from "./tools/memory-ingest-web.js";
|
|
21
21
|
import { memoryClassify } from "./tools/memory-classify.js";
|
|
22
22
|
import { memoryUpdate } from "./tools/memory-update.js";
|
|
@@ -1019,46 +1019,11 @@ if (!readOnly) {
|
|
|
1019
1019
|
};
|
|
1020
1020
|
}
|
|
1021
1021
|
});
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
"Idempotent — re-running collapses identical (conversationId, sourceMessageRef, kind, contentHash) into the same row. " +
|
|
1028
|
-
"Phase 1 (`whatsapp-ingest.sh`) writes ZERO observations; this tool is the only sanctioned LLM entry for " +
|
|
1029
|
-
"WhatsApp ingest. Operator triggers consciously via the `whatsapp-import-enrich` skill — never automatic on Phase 1 completion.", {
|
|
1030
|
-
conversationId: z
|
|
1031
|
-
.string()
|
|
1032
|
-
.min(1)
|
|
1033
|
-
.describe("conversationId of the already-loaded :Conversation:WhatsAppConversation. Phase 1 must have completed (`c.lastImportedAt IS NOT NULL`)."),
|
|
1034
|
-
sessionId: z
|
|
1035
|
-
.string()
|
|
1036
|
-
.optional()
|
|
1037
|
-
.describe("Skill-run UUID for provenance. Falls back to SESSION_ID env var when absent."),
|
|
1038
|
-
}, async (params) => {
|
|
1039
|
-
try {
|
|
1040
|
-
const result = await whatsappExportInsightPass({
|
|
1041
|
-
conversationId: params.conversationId,
|
|
1042
|
-
accountId,
|
|
1043
|
-
sessionId: resolveSessionId(params.sessionId),
|
|
1044
|
-
});
|
|
1045
|
-
return {
|
|
1046
|
-
content: [{
|
|
1047
|
-
type: "text",
|
|
1048
|
-
text: JSON.stringify(result),
|
|
1049
|
-
}],
|
|
1050
|
-
};
|
|
1051
|
-
}
|
|
1052
|
-
catch (err) {
|
|
1053
|
-
return {
|
|
1054
|
-
content: [{
|
|
1055
|
-
type: "text",
|
|
1056
|
-
text: `whatsapp-export-insight-pass failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1057
|
-
}],
|
|
1058
|
-
isError: true,
|
|
1059
|
-
};
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1022
|
+
// Task 891: `whatsapp-export-insight-pass` retired. Insight derivation
|
|
1023
|
+
// (:Observation / :Task / :Preference / :MENTIONS) is deferred to a
|
|
1024
|
+
// follow-up task that operates on :Section:Conversation chunks rather than
|
|
1025
|
+
// raw :Message rows. The tool source and the `whatsapp-import-enrich` skill
|
|
1026
|
+
// were deleted in the same commit; the surface gate's allow-list was updated.
|
|
1062
1027
|
server.tool("memory-ingest-web", "Adapter for web-content ingestion (Task 737). Accepts a URL and its pre-fetched readable content " +
|
|
1063
1028
|
"(the agent calls WebFetch first, then passes the text here), writes content to a temp file, and delegates " +
|
|
1064
1029
|
"to memory-ingest-extract — caching the text under a freshly-generated attachmentId. The skill then drives " +
|