@rubytech/create-maxy 1.0.876 → 1.0.877
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/neo4j/edge-annotations.json +11 -3
- package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +11 -5
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +5 -1
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +88 -9
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +1 -1
- package/payload/platform/plugins/docs/references/admin-session.md +80 -0
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -0
- package/payload/platform/plugins/memory/PLUGIN.md +4 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +127 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js +97 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js +184 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +89 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +542 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.d.ts +41 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.js +116 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.js.map +1 -0
- package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +159 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +3 -2
- package/payload/server/chunk-GOZP57CX.js +1373 -0
- package/payload/server/chunk-I4AQMEJA.js +11265 -0
- package/payload/server/chunk-LU6TUP3E.js +2169 -0
- package/payload/server/chunk-RRVBWC66.js +667 -0
- package/payload/server/client-pool-VYDOIFG7.js +34 -0
- package/payload/server/cloudflare-task-tracker-M7APAYEF.js +20 -0
- package/payload/server/maxy-edge.js +6 -5
- package/payload/server/public/assets/{Checkbox-BsqexMy3.js → Checkbox-m3yLBLrp.js} +1 -1
- package/payload/server/public/assets/{admin-pIeHRytz.js → admin-DEm0CCga.js} +6 -6
- package/payload/server/public/assets/data-BkbjVYwP.js +1 -0
- package/payload/server/public/assets/graph-Cic-rDfg.js +1 -0
- package/payload/server/public/assets/{graph-labels-t_04n4zX.js → graph-labels-C13OVh5P.js} +1 -1
- package/payload/server/public/assets/{jsx-runtime-CGCRFPeX.css → jsx-runtime-DJwgVAMg.css} +1 -1
- package/payload/server/public/assets/{page-qI0NJSs6.js → page-BLRjaAoU.js} +1 -1
- package/payload/server/public/assets/{page-BM9O7QN8.js → page-p-Fj8Guk.js} +1 -1
- package/payload/server/public/assets/{public-oNo_2gt0.js → public-4udeVi_T.js} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-DVVSQc-9.js → useVoiceRecorder-JwwBC5pd.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +53 -23
- package/payload/server/public/assets/data-rhAG7W2b.js +0 -1
- package/payload/server/public/assets/graph-DVAWZmkb.js +0 -1
- /package/payload/server/public/assets/{jsx-runtime-B8sGPXtT.js → jsx-runtime-Bd3TJ8Bg.js} +0 -0
package/package.json
CHANGED
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
"note": "Flat document-to-chunk (alternative to HAS_SECTION then HAS_CHUNK)."
|
|
82
82
|
},
|
|
83
83
|
"REFERENCES": {
|
|
84
|
-
"direction": "(Message|KnowledgeDocument)-[:REFERENCES]->(*)",
|
|
85
|
-
"note": "Soft reference link."
|
|
84
|
+
"direction": "(Message|KnowledgeDocument|Task)-[:REFERENCES]->(*)",
|
|
85
|
+
"note": "Soft reference link. Task 892 added `Task` as a source: derived-insight tasks created from a `:Section:Conversation` chunk record their provenance via (:Task)-[:REFERENCES]->(:Section:Conversation) with a `contentHash` merge-key for idempotent re-runs."
|
|
86
86
|
},
|
|
87
87
|
"ABOUT": {
|
|
88
88
|
"direction": "(Review|Message)-[:ABOUT]->(*)",
|
|
@@ -102,7 +102,15 @@
|
|
|
102
102
|
},
|
|
103
103
|
"OBSERVED_IN": {
|
|
104
104
|
"direction": "(*)-[:OBSERVED_IN]->(Conversation)",
|
|
105
|
-
"note": "Observation provenance."
|
|
105
|
+
"note": "Observation provenance. Task 892: `:Section:Conversation` chunks (which carry the Conversation label) are valid OBSERVED_IN targets, so (:Preference)-[:OBSERVED_IN]->(:Section:Conversation) pattern-matches this annotation."
|
|
106
|
+
},
|
|
107
|
+
"MENTIONS": {
|
|
108
|
+
"direction": "(Section|Message|KnowledgeDocument)-[:MENTIONS]->(Person|Organization)",
|
|
109
|
+
"note": "Named entity reference. Task 892 added `Section` (typically Section:Conversation) as a source so chunk-anchored insight derivation can record who a transcript chunk mentions. KnowledgeDocument-source MENTIONS is the document-ingest path; Message-source MENTIONS is reserved for future per-message extraction."
|
|
110
|
+
},
|
|
111
|
+
"RELATED_TO": {
|
|
112
|
+
"direction": "(Person|Organization)-[:RELATED_TO]->(Person|Organization)",
|
|
113
|
+
"note": "Operator-confirmed relationship between two named entities derived from a transcript chunk (Task 892). Carries `operatorConfirmed: true` plus `relationshipType` naming the specific bond (`broker`, `colleague`, `referrer`, …). Distinct from typed edges like AUTHORED_BY or PARTICIPANT — RELATED_TO is the generic surface for relationships the operator confirmed at enrich time."
|
|
106
114
|
},
|
|
107
115
|
"HAS_IDENTITY": {
|
|
108
116
|
"direction": "(Agent)-[:HAS_IDENTITY]->(KnowledgeDocument)",
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Archive-ingest surface gate (Task 855, updated by Task 891).
|
|
2
|
+
# Archive-ingest surface gate (Task 855, updated by Task 891, Task 892).
|
|
3
3
|
#
|
|
4
4
|
# Five enforcements, one script — phase decided by `hook_event_name` on stdin.
|
|
5
5
|
# Task 855 narrows the database-operator subagent's effective surface during
|
|
6
6
|
# WhatsApp archive ingestion to exactly one Bash entry
|
|
7
7
|
# (`memory/bin/conversation-archive-ingest.sh`) plus read-only neighbours, by
|
|
8
8
|
# blocking the legacy MCP deviation tools mechanically. Task 891 retired the
|
|
9
|
-
# `whatsapp-export-insight-pass` tool
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
9
|
+
# `whatsapp-export-insight-pass` tool; Task 892 reintroduces Phase 2 as
|
|
10
|
+
# `mcp__memory__conversation-archive-derive-insights` — a read-only tool that
|
|
11
|
+
# walks :Section:Conversation chunks of one named :ConversationArchive in
|
|
12
|
+
# pages and emits per-row proposals. The new tool is NOT in any BLOCK list
|
|
13
|
+
# (the gate is allow-by-default for unrecognised tools) — its writes go
|
|
14
|
+
# through the existing graph-cypher-write surface, gated by the operator per
|
|
15
|
+
# row in the conversation-archive-enrich skill. Stale references to the
|
|
16
|
+
# retired Phase 2 name (`whatsapp-export-insight-pass`) remain in the BLOCK
|
|
17
|
+
# list as a loud-denial breadcrumb for any operator-edited skill that still
|
|
18
|
+
# names them.
|
|
13
19
|
#
|
|
14
20
|
# 1. PreToolUse on the four legacy WhatsApp MCP tools — BLOCK unconditionally.
|
|
15
21
|
# The single deterministic Bash entry is the only supported path for
|
|
@@ -154,7 +154,11 @@ Then call `render-component` with `name: "cloudflare-setup-form"` and data conta
|
|
|
154
154
|
|
|
155
155
|
Wait for the user's submission. The `_componentDone` payload contains the `setup-tunnel.sh` output verbatim. Relay that output to the user — quote any `ACTION REQUIRED` block exactly. When the script exits zero, step-7 completion has already been persisted by the script itself — relay the output and stop. Do not call `onboarding-complete-step` with step 7; the script is the authority for step-7 completion, and any call you make after the script's restart dispatch would race the service restart and almost always lose. If the script failed (the endpoint returned `ok: false, field: "script"`), the form surfaced the error and stayed open — relay the output, cite `plugins/cloudflare/references/reset-guide.md` for recovery, and offer to re-render the form after any manual steps. Do not synthesise alternative recovery commands. If the user skipped (step 7 not reached), call `onboarding-complete-step` with step 7 so the next session resumes at step 8.
|
|
156
156
|
|
|
157
|
-
**Post-restart resume contract.** A successful Cloudflare setup arms a brand-service restart that kills the in-flight admin agent
|
|
157
|
+
**Post-restart resume contract.** A successful Cloudflare setup arms a brand-service restart that kills the in-flight admin agent. The operator's "Cloudflare setup completed" message is replayed by the chat client after the restart cycle completes. Two pathways converge on the same agent-visible outcome:
|
|
158
|
+
- **Default (Task 982).** The operator's admin sessionKey is a Task-653-style signed token (`v1.…` HMAC) that survives the restart. `validateSession` rehydrates the in-memory session from the token, the chat-route binds the prior `conversationId` via `getMostRecentAdminConversationForUser`, and the SDK's next cold-create passes `resume: <priorAgentSessionId>` — the marker turn lands in the SAME conversation with the SDK's JSONL transcript intact.
|
|
159
|
+
- **Fallback.** If the signed-token rehydrate fails (token tampered, TTL expired, pre-Task-982 legacy sessionKey), the chat client falls through to `POST /api/admin/sessions/<cid>/resume` via the surviving `__remote_session` cookie. Outcome from your view as the admin agent is identical.
|
|
160
|
+
|
|
161
|
+
By the time you receive the marker, `OnboardingState.currentStep` is already 7 (the script's filesystem flag was consumed by `consumeStep7FlagUI` on the way in). The operator told you "Cloudflare setup completed (actionId: …)" at currentStep=7. Acknowledge, then proceed to step 8 — do NOT re-ask the Cloudflare question, do NOT re-render the cloudflare-setup-form, do NOT call `onboarding-complete-step` with step 7 (already done). The marker turn is your single source of truth that step 7 finished cleanly; the script's flag-consume is the orthogonal proof that the state machine advanced.
|
|
158
162
|
|
|
159
163
|
## Step 8 — Anthropic API key
|
|
160
164
|
|
|
@@ -212,8 +212,16 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
212
212
|
# callback forever; subsequent setup-tunnel runs see a stale cert.pem
|
|
213
213
|
# landing asynchronously and race against the new URL-extraction pass.
|
|
214
214
|
CF_PIPELINE_PID=""
|
|
215
|
+
CHROMIUM_UNIT=""
|
|
215
216
|
cleanup_oauth() {
|
|
216
217
|
[ -n "${CF_PIPELINE_PID}" ] && kill "${CF_PIPELINE_PID}" 2>/dev/null || true
|
|
218
|
+
# Task 982 — stop the transient chromium unit on any early exit between
|
|
219
|
+
# browser-spawn and the explicit step=browser-close site below. Best-
|
|
220
|
+
# effort: no phase_line here because the EXIT trap fires on every path
|
|
221
|
+
# (including the happy one where step=browser-close already ran and
|
|
222
|
+
# auto-collected the unit). The `|| true` masks the inevitable "Unit
|
|
223
|
+
# not loaded" return on the happy path.
|
|
224
|
+
[ -n "${CHROMIUM_UNIT}" ] && systemctl --user stop "${CHROMIUM_UNIT}" 2>/dev/null || true
|
|
217
225
|
rm -f "${URL_FILE}" "${LAST_LINE_FILE}"
|
|
218
226
|
}
|
|
219
227
|
trap cleanup_oauth EXIT
|
|
@@ -276,12 +284,19 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
276
284
|
# Mechanically open the URL on the Pi VNC chromium (Task 858). Chromium
|
|
277
285
|
# is already running on this brand's ${BRAND_VNC_DISPLAY} with CDP enabled
|
|
278
286
|
# (vnc.sh start_chrome at boot); invoking the resolved binary <url> against
|
|
279
|
-
# a running instance IPCs the URL into it as a new tab.
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
#
|
|
287
|
+
# a running instance IPCs the URL into it as a new tab. Replaces
|
|
288
|
+
# cloudflared's own optimistic xdg-open, which does not reliably target
|
|
289
|
+
# the brand's VNC display in this environment.
|
|
290
|
+
#
|
|
291
|
+
# Task 982 — chromium is launched under a transient systemd-user unit so
|
|
292
|
+
# the full process tree (including any standalone chromium that lands
|
|
293
|
+
# when no existing instance is running for IPC) lives in its own cgroup.
|
|
294
|
+
# On cert.pem arrival the unit is stopped, SIGTERMing the whole cgroup
|
|
295
|
+
# atomically. Pre-Task-982 the spawn was `&` fire-and-forget with no
|
|
296
|
+
# tracked PID; the resulting orphan chromium on display :101 was the
|
|
297
|
+
# symptom in maxy-2 2026-05-12T10:06–10:08Z. `step=browser-close
|
|
298
|
+
# result=ok|orphan` records the teardown outcome at cert.pem mv site
|
|
299
|
+
# below.
|
|
285
300
|
#
|
|
286
301
|
# Binary path: SETUP_TUNNEL_CHROMIUM_BIN is read at startup from
|
|
287
302
|
# ${MAXY_PLATFORM_ROOT}/config/chromium-binary.path — `/usr/bin/chromium`
|
|
@@ -289,9 +304,34 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
289
304
|
# where the system chromium is snap-confined (Task 929). Hardcoding
|
|
290
305
|
# `/usr/bin/chromium` here would re-introduce the AppArmor SingletonLock
|
|
291
306
|
# failure on the laptop.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
307
|
+
CHROMIUM_UNIT="maxy-oauth-chromium-${BRAND}-$$.service"
|
|
308
|
+
CHROMIUM_LAUNCH_DISPLAY="${DISPLAY:-${BRAND_VNC_DISPLAY}}"
|
|
309
|
+
CHROMIUM_SPAWN_ERR="$(mktemp -t maxy-oauth-chromium-err.XXXXXX)"
|
|
310
|
+
if systemd-run --user \
|
|
311
|
+
--unit="${CHROMIUM_UNIT}" \
|
|
312
|
+
--description="Maxy OAuth chromium for ${BRAND}" \
|
|
313
|
+
--collect \
|
|
314
|
+
--setenv=DISPLAY="${CHROMIUM_LAUNCH_DISPLAY}" \
|
|
315
|
+
"${SETUP_TUNNEL_CHROMIUM_BIN}" "${AUTH_URL}" 2>"${CHROMIUM_SPAWN_ERR}"; then
|
|
316
|
+
rm -f "${CHROMIUM_SPAWN_ERR}"
|
|
317
|
+
phase_line setup-tunnel step=browser-spawn result=ok \
|
|
318
|
+
display="${CHROMIUM_LAUNCH_DISPLAY}" url_extracted=1 unit="${CHROMIUM_UNIT}"
|
|
319
|
+
else
|
|
320
|
+
SPAWN_RC=$?
|
|
321
|
+
SPAWN_STDERR="$(tr '\n' ' ' < "${CHROMIUM_SPAWN_ERR}" | head -c 300 || echo unavailable)"
|
|
322
|
+
rm -f "${CHROMIUM_SPAWN_ERR}"
|
|
323
|
+
# Loud-fail rather than fire-and-forget fallback: a systemd-run failure
|
|
324
|
+
# is the same class as the pre-Task-982 orphan (no teardown handle).
|
|
325
|
+
# Operator should see the bus-not-running / linger-not-enabled cause.
|
|
326
|
+
phase_line setup-tunnel step=browser-spawn result=error \
|
|
327
|
+
reason=systemd-run-failed exit="${SPAWN_RC}" stderr="${SPAWN_STDERR}" \
|
|
328
|
+
unit="${CHROMIUM_UNIT}"
|
|
329
|
+
echo "ERROR: systemd-run failed to spawn chromium under transient unit (exit=${SPAWN_RC})." >&2
|
|
330
|
+
echo " systemd-run stderr: ${SPAWN_STDERR}" >&2
|
|
331
|
+
echo " If stderr mentions 'Failed to connect to bus', enable user-scope" >&2
|
|
332
|
+
echo " systemd via 'loginctl enable-linger \$(whoami)' and retry." >&2
|
|
333
|
+
exit 1
|
|
334
|
+
fi
|
|
295
335
|
phase_line setup-tunnel step=browser-drive mode=operator-click url="${AUTH_URL}"
|
|
296
336
|
|
|
297
337
|
# Wait for cert.pem to land — cloudflared writes to ~/.cloudflared/cert.pem
|
|
@@ -335,6 +375,45 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
335
375
|
mv "${HOME}/.cloudflared/cert.pem" "${CFG_DIR}/cert.pem"
|
|
336
376
|
phase_line setup-tunnel step=oauth-login result=ok \
|
|
337
377
|
path="${CFG_DIR}/cert.pem" waited="${LOGIN_WAIT}s"
|
|
378
|
+
|
|
379
|
+
# Task 982 — SIGTERM the OAuth chromium cgroup now that cert.pem has
|
|
380
|
+
# landed. The transient unit was created above at step=browser-spawn; if
|
|
381
|
+
# chromium IPCs'd to a running brand-VNC instance and exited cleanly, the
|
|
382
|
+
# unit is already auto-collected and `systemctl stop` returns 0 (no-such-
|
|
383
|
+
# unit is a benign race, not an orphan). If chromium is still alive (no
|
|
384
|
+
# pre-existing brand-VNC instance to IPC into), SIGTERM tears the whole
|
|
385
|
+
# cgroup atomically. `result=ok` covers both clean paths; `result=orphan`
|
|
386
|
+
# fires only when the stop command itself fails (bus issue, race with
|
|
387
|
+
# auto-collect that returned non-zero) — operator-visible signal that an
|
|
388
|
+
# orphan chromium MAY still be alive on the VNC display.
|
|
389
|
+
CHROMIUM_STOP_ERR="$(mktemp -t maxy-oauth-chromium-stop-err.XXXXXX)"
|
|
390
|
+
if systemctl --user stop "${CHROMIUM_UNIT}" 2>"${CHROMIUM_STOP_ERR}"; then
|
|
391
|
+
rm -f "${CHROMIUM_STOP_ERR}"
|
|
392
|
+
phase_line setup-tunnel step=browser-close result=ok unit="${CHROMIUM_UNIT}"
|
|
393
|
+
else
|
|
394
|
+
STOP_RC=$?
|
|
395
|
+
STOP_STDERR="$(tr '\n' ' ' < "${CHROMIUM_STOP_ERR}" | head -c 300 || echo unavailable)"
|
|
396
|
+
rm -f "${CHROMIUM_STOP_ERR}"
|
|
397
|
+
# Distinguish benign "unit already auto-collected" from a true teardown
|
|
398
|
+
# failure via systemctl's exit-code taxonomy — never via stderr prose
|
|
399
|
+
# parsing, which breaks on non-English locales (no-stdout-parsing-for-
|
|
400
|
+
# control-flow doctrine). Exit code 5 is systemd's canonical "Unit not
|
|
401
|
+
# loaded" return; --collect auto-GCs a terminated unit between the
|
|
402
|
+
# chromium-side IPC-and-exit and our stop, producing exactly this code.
|
|
403
|
+
# Any other non-zero exit is a real teardown failure (bus down, permission,
|
|
404
|
+
# service still alive but stop hung).
|
|
405
|
+
if [ "${STOP_RC}" -eq 5 ]; then
|
|
406
|
+
phase_line setup-tunnel step=browser-close result=ok \
|
|
407
|
+
reason=unit-auto-collected unit="${CHROMIUM_UNIT}"
|
|
408
|
+
else
|
|
409
|
+
phase_line setup-tunnel step=browser-close result=orphan \
|
|
410
|
+
reason=stop-failed exit="${STOP_RC}" stderr="${STOP_STDERR}" \
|
|
411
|
+
unit="${CHROMIUM_UNIT}"
|
|
412
|
+
echo "WARNING: failed to stop transient chromium unit ${CHROMIUM_UNIT} (exit=${STOP_RC})." >&2
|
|
413
|
+
echo " An orphan chromium may remain on display ${CHROMIUM_LAUNCH_DISPLAY}." >&2
|
|
414
|
+
echo " systemctl stderr: ${STOP_STDERR}" >&2
|
|
415
|
+
fi
|
|
416
|
+
fi
|
|
338
417
|
fi
|
|
339
418
|
|
|
340
419
|
# --------------------------------------------------------------------------
|
|
@@ -22,7 +22,7 @@ Any Cloudflare action outside these four surfaces is a discipline violation —
|
|
|
22
22
|
|
|
23
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 brand's VNC chromium
|
|
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 brand's VNC chromium under a transient `systemd-run --user --unit=maxy-oauth-chromium-${BRAND}-$$.service` so the chromium process tree lives in its own cgroup (Task 982 — pre-Task-982 the spawn was `&` fire-and-forget and orphaned chromium on display `:101` when no pre-existing brand-VNC chromium was available for IPC). The launch uses the install-time-resolved binary (`SETUP_TUNNEL_CHROMIUM_BIN` from `${MAXY_PLATFORM_ROOT}/config/chromium-binary.path` so Ubuntu Noble laptop's snap-replaced Google Chrome is honoured per Task 929) — emitting `step=browser-spawn result=ok unit=<transient-unit>` 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; the wrapper then `systemctl --user stop`s the transient unit, emitting `step=browser-close result=ok` (or `result=orphan reason=stop-failed` when SIGTERM didn't reach the cgroup — operator-visible signal that an orphan chromium MAY still be alive). 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
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Admin Session — restart survival and SDK-resume contract
|
|
2
|
+
|
|
3
|
+
The admin PIN-gated session-store is the in-memory `Map<sessionKey, Session>` at [`platform/ui/app/lib/claude-agent/session-store.ts`](../../../ui/app/lib/claude-agent/session-store.ts). Every `systemctl --user restart {brand}.service` (notably the one `setup-tunnel.sh` arms 3 s after `step=done` via `systemd-run --on-active=3s`) wipes that Map. This reference documents how an admin session survives the restart without forcing PIN re-entry, and how the SDK conversation chain is preserved across the gap.
|
|
4
|
+
|
|
5
|
+
## Signed sessionKey
|
|
6
|
+
|
|
7
|
+
`POST /api/admin/session` mints sessionKeys as HMAC-signed tokens (same primitive as `remote-auth.ts`'s `__remote_session` cookie, generalised to `session-store.ts`):
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
v1.<base64url(payloadJson)>.<base64url(hmac-sha256(secret, payloadJson))>
|
|
11
|
+
|
|
12
|
+
payload = { v: "adm", a: <accountId>, u: <userId>, c: <createdAtMs>, n: <16-byte hex nonce> }
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The HMAC secret lives at `~/.${brand}/credentials/admin-session-secret` (mode `0o600`, parent dir `0o700`), self-provisioned on first read via the `wx`-create pattern from [`remote-auth.ts::getSecret`](../../../ui/app/lib/remote-auth.ts). The secret is separate from `REMOTE_SESSION_SECRET_FILE` (the `__remote_session` cookie key) — two token surfaces, two security domains, no token-confusion attack across schemas.
|
|
16
|
+
|
|
17
|
+
**Validation path.** `validateSession(sessionKey, 'admin')`:
|
|
18
|
+
|
|
19
|
+
1. In-memory `Map.get(sessionKey)` hit → existing behaviour (age check, grant check, return ok).
|
|
20
|
+
2. Map miss AND `agentType === 'admin'` → `tryRehydrateAdminSession(sessionKey)`:
|
|
21
|
+
- Parse `v1.…` token, verify HMAC against the on-disk secret (timing-safe compare).
|
|
22
|
+
- Schema-validate the payload (`v === 'adm'`, all required fields present).
|
|
23
|
+
- TTL check: `Date.now() - payload.c <= 24h`. Expired → return `{kind: 'expired', ageMs}` → caller projects onto the existing `session-expired-age` rejection.
|
|
24
|
+
- Otherwise, re-register the in-memory entry with `{agentType: 'admin', accountId, userId, wantsPriorConversation: true}` and return `{kind: 'ok', …}`.
|
|
25
|
+
3. Map miss with no valid token → `session-not-registered` (existing legacy `crypto.randomUUID()` sessionKeys land here).
|
|
26
|
+
|
|
27
|
+
**Observability.** `[session-rehydrate-from-token] sessionKey=… accountId=… userId=… ageMs=…` fires once per successful rehydrate in `server.log`. A tampered token fails HMAC and returns `invalid-token` silently (no log line — would otherwise be a noise/attack-amplifier surface); the request's downstream `[session] middleware-reject status=401 code=session-not-registered` is sufficient.
|
|
28
|
+
|
|
29
|
+
## SDK-resume contract on PIN-rebind
|
|
30
|
+
|
|
31
|
+
The Anthropic SDK is stateless against its own JSONL: every cold-create with `resume: <agentSessionId>` reads `${CLAUDE_CONFIG_DIR}/projects/<encoded-cwd>/<agentSessionId>.jsonl` verbatim. The Maxy graph (`Conversation.agentSessionId`) is just the pointer into the JSONL, not a parallel transcript. The PIN-rebind contract preserves this:
|
|
32
|
+
|
|
33
|
+
1. **Rehydrate** restores `(accountId, userId)` into the in-memory session. `conversationId` and `agentSessionId` are empty.
|
|
34
|
+
2. **Chat-route first POST** (`platform/ui/server/routes/admin/chat.ts`):
|
|
35
|
+
- `consumeWantsPriorConversation(sessionKey)` returns true → look up prior admin conversation.
|
|
36
|
+
- `getMostRecentAdminConversationForUser(accountId, userId)` returns `{conversationId, agentSessionId}` for the most recent admin Conversation node carrying a non-null `agentSessionId`.
|
|
37
|
+
- `setConversationIdForSession(sessionKey, conversationId)` binds the prior `conversationId` so the operator's chat lands in the SAME conversation, not a new one.
|
|
38
|
+
- `setAgentSessionId(sessionKey, agentSessionId)` seeds the in-memory session's pointer; subsequent `invokeAdminAgent`'s `getAgentSessionId(sessionKey)` returns this value naturally.
|
|
39
|
+
3. **`invokeAdminAgent`** passes `resume: <priorAgentSessionId>` to the SDK options. The SDK opens its on-disk JSONL for that session id and continues from there.
|
|
40
|
+
|
|
41
|
+
No `<previous-context>` prompt-stuffing. No Neo4j transcript replay. The SDK reads its own JSONL — the only canonical source of multi-block content (`thinking` with signed signatures, `tool_use`/`tool_result` chains, multi-modal blocks) the graph cannot losslessly reconstruct.
|
|
42
|
+
|
|
43
|
+
## PIN-rebind vs `/sessions/new`
|
|
44
|
+
|
|
45
|
+
The `wantsPriorConversation` marker is the discriminator between continuity and explicit fresh-start:
|
|
46
|
+
|
|
47
|
+
| Trigger | Marker set? | Chat-route behaviour |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| Signed-token rehydrate post-restart (`tryRehydrateAdminSession`) | yes | Resume prior conversation |
|
|
50
|
+
| Explicit PIN re-entry (`POST /api/admin/session`, `createAdminSession`) | yes | Resume prior conversation |
|
|
51
|
+
| Operator clicks "New conversation" (`POST /api/admin/sessions/new`) | NO | Cold-mint new conversation |
|
|
52
|
+
|
|
53
|
+
Single-shot: the chat-route consumes the marker on the first POST, so subsequent chats in the same session continue with the bound `conversationId` naturally.
|
|
54
|
+
|
|
55
|
+
## Observability summary
|
|
56
|
+
|
|
57
|
+
Per chat POST, one `[client-acquire]` line lands in the per-conversation stream log at `{accountDir}/logs/claude-agent-stream-<conversationId>.log`:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
[client-acquire] sessionKey=<sk12>… resume=<priorAgentSessionId8|none> reason=<warm|cold|pin-rebind>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- `warm` — pool entry still alive (no restart in between)
|
|
64
|
+
- `cold` — fresh mint (no prior admin conversation, or `/sessions/new`)
|
|
65
|
+
- `pin-rebind` — post-restart resume of prior admin conversation
|
|
66
|
+
|
|
67
|
+
The pre-existing `[client-cold-create resumedFrom=<id>]` line from `client-pool.ts` fires once per actual SDK subprocess spawn; combined with `[client-acquire]` it gives a complete shape of every acquire-and-spawn event.
|
|
68
|
+
|
|
69
|
+
Diagnostic grep:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
grep -E '\[session-rehydrate-from-token\]|\[client-acquire\] reason=pin-rebind' ~/.${brand}/logs/server.log
|
|
73
|
+
grep '\[client-acquire\]' ~/.${brand}/logs/claude-agent-stream-*.log
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Out of scope
|
|
77
|
+
|
|
78
|
+
- Public/WhatsApp/Telegram sessions — out of scope. Their sessionKeys remain `crypto.randomUUID()` and follow the existing rejection-on-restart contract.
|
|
79
|
+
- Multi-user PIN identity carry-forward across the same signed token — the token is bound to one `userId`; rotating PINs mints a fresh token.
|
|
80
|
+
- Replacing `systemctl restart` with in-process cloudflared reload — separate task. The restart-survival contract documented here is the operator's safety net while that rewrite is pending.
|
|
@@ -65,7 +65,7 @@ There is no dashboard, no settings panel, no menus. Everything is done through c
|
|
|
65
65
|
|
|
66
66
|
The chat input auto-grows as you type — it expands to fit your message and shrinks back when you delete text. You can also drag the resize handle above the input to set a custom height.
|
|
67
67
|
|
|
68
|
-
The admin interface is a three-pane layout: a sidebar on the left with your brand mark, navigation (Chat, People, Agents, Projects, Tasks, Artefacts), and your recent conversations; the chat in the middle; and an artefact pane on the right that opens when you select a document, click a project, or open Browser, Data, or Graph from the menu — holding the surface side-by-side with the conversation so the chat stays live while you work in it. The sidebar's nav rows swap the list view in place — Chat shows recent conversations, Projects shows your active work projects, and Artefacts lists every KnowledgeDocument plus this account's agent templates (your admin agent's IDENTITY, SOUL, and KNOWLEDGE files plus one entry per enabled specialist). The People, Agents, and Tasks rows are graph shortcuts: clicking each opens the artefact-pane Graph filtered to every Person, every public Agent, or every Task in your account respectively, with no side-list — the graph itself is the result. Public agents become first-class graph entities the moment you create them, with edges to their IDENTITY/SOUL/KNOWLEDGE files, edges to every knowledge document they have access to, and edges from every conversation they have handled, so a single Agents click reveals the whole shape of who knows what and who has been talking to whom. Click an artefact row to open the document. KnowledgeDocuments and your admin agent's templates are editable — type in the document and changes save automatically; specialist agent templates are read-only because they ship with Maxy and your edits would be overwritten on the next install. PDF artefacts render inline so you can read them without leaving the pane. If your browser doesn't have a built-in PDF viewer, a Download button appears instead. Artefacts that have no readable file backing them (orphan rows, files removed from disk, unsupported content types) show a one-line banner explaining the skip instead of opening to a blank pane. Click a project row to open the Graph view focused on that project's neighbourhood — clicking a second project swaps the focus rather than stacking on top. The chat / artefact divider is drag-resizable — drag the line between the columns to make either side wider; double-click it to reset to half of the available width (viewport minus sidebar), clamped to the chat / artefact min-width floors. Your chosen width is remembered across reloads. On wider screens (>1280px) all three panes are visible. The sidebar narrows at 1280px, the artefact pane hides at 1080px (Browser, Data, and Graph then open as full-window pages instead), and the sidebar collapses to a 56px icon rail at 820px. On phones (<720px) the sidebar slides in as a drawer from the left when you tap the menu icon in the chat header. When the sidebar is collapsed to the 56px icon rail, clicking the Artefacts icon expands the rail back open so the artefact list is visible — the row was previously a silent no-op in collapsed state.
|
|
68
|
+
The admin interface is a three-pane layout: a sidebar on the left with your brand mark, navigation (Chat, People, Agents, Projects, Tasks, Artefacts), and your recent conversations; the chat in the middle; and an artefact pane on the right that opens when you select a document, click a project, or open Browser, Data, or Graph from the menu — holding the surface side-by-side with the conversation so the chat stays live while you work in it. The sidebar's nav rows swap the list view in place — Chat shows recent conversations, Projects shows your active work projects, and Artefacts lists every KnowledgeDocument plus this account's agent templates (your admin agent's IDENTITY, SOUL, and KNOWLEDGE files plus one entry per enabled specialist). The People, Agents, and Tasks rows are graph shortcuts: clicking each opens the artefact-pane Graph filtered to every Person, every public Agent, or every Task in your account respectively, with no side-list — the graph itself is the result. Public agents become first-class graph entities the moment you create them, with edges to their IDENTITY/SOUL/KNOWLEDGE files, edges to every knowledge document they have access to, and edges from every conversation they have handled, so a single Agents click reveals the whole shape of who knows what and who has been talking to whom. Click an artefact row to open the document. KnowledgeDocuments and your admin agent's templates are editable — type in the document and changes save automatically; specialist agent templates are read-only because they ship with Maxy and your edits would be overwritten on the next install. PDF artefacts render inline so you can read them without leaving the pane. If your browser doesn't have a built-in PDF viewer, a Download button appears instead. Artefacts that have no readable file backing them (orphan rows, files removed from disk, unsupported content types) show a one-line banner explaining the skip instead of opening to a blank pane. Click a project row to open the Graph view focused on that project's neighbourhood — clicking a second project swaps the focus rather than stacking on top. The chat / artefact divider is drag-resizable — drag the line between the columns to make either side wider; double-click it to reset to half of the available width (viewport minus sidebar), clamped to the chat / artefact min-width floors. Your chosen width is remembered across reloads. On wider screens (>1280px) all three panes are visible. The sidebar narrows at 1280px, the artefact pane hides at 1080px (Browser, Data, and Graph then open as full-window pages instead), and the sidebar collapses to a 56px icon rail at 820px. On phones (<720px) the sidebar slides in as a drawer from the left when you tap the menu icon in the chat header — the drawer animation only fires on tap (220ms slide in or out); resizing your window across the 720px boundary snaps the layout without animation, so you never see a half-open flash. Breakpoint summary: >1280px = full sidebar + chat + artefact pane (drag-resizable divider); 1280px→1080px = sidebar narrows; 1080px→820px = artefact pane hides (Browser/Data/Graph open as full-window pages instead); 820px→720px = sidebar collapses to 56px icon rail; ≤720px = sidebar becomes off-canvas drawer (vertical stack of brand mark, nav, recents list, foot — same shape as the desktop sidebar, just on top of the chat instead of beside it). When the sidebar is collapsed to the 56px icon rail, clicking the Artefacts icon expands the rail back open so the artefact list is visible — the row was previously a silent no-op in collapsed state.
|
|
69
69
|
|
|
70
70
|
Page titles are brand-aware: the browser tab shows your product name (e.g. `Real Agent` instead of `Maxy`) on every shell — chat, graph, and data — so a non-default brand never leaks the default name in tab strips or browser history.
|
|
71
71
|
|
|
@@ -41,6 +41,7 @@ These are enabled during onboarding and can be added or removed at any time. Som
|
|
|
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
43
|
| `memory/skills/conversation-archive` | Source-agnostic conversation transcript ingest. One skill for WhatsApp `_chat.txt`, Telegram, Signal, LinkedIn DMs, Zoom transcript, meeting minutes, iMessage, Slack — `--source <enum>` selects the per-source normaliser. Single Bash entry — `bash platform/plugins/memory/bin/conversation-archive-ingest.sh <archive> --source <enum> --owner-element-id <id> --participant-person-ids <csv> --scope <admin\|public>` — runs normalise → 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'`, `source=<enum>`. Re-imports are delta-append. Auto-creating participants is forbidden — any sender outside the operator-confirmed closed set LOUD-FAILs with `parser-miss`. Phase 0 ships only `whatsapp`; other normalisers land per-source. Distinct from the live `whatsapp` plugin (Baileys). | Database operator |
|
|
44
|
+
| `memory/skills/conversation-archive-enrich` | Phase 2 for any named `:ConversationArchive` — source-agnostic per-row insight derivation. Operator-triggered (never auto-fires on Phase 1 completion). Walks `:Section:Conversation` chunks in pages via the read-only MCP tool `mcp__memory__conversation-archive-derive-insights`; surfaces high-confidence claims for per-row operator gate (`wire / skip / reject`) over four kinds — `mention`, `task`, `preference`, `observed-relationship`. Idempotent on `(elementId(chunk), kind, contentHash)` — re-runs collapse identical claims. Haiku runs on OAuth (admin-side LLM never the API key); confidence floor is a hedging-avoidance instruction in the system prompt, not a numeric post-filter. | Database operator |
|
|
44
45
|
|
|
45
46
|
### Claude Official (marketplace)
|
|
46
47
|
|
|
@@ -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. Document ingestion (memory-classify + memory-ingest) supports two modes: `document` (default) for unstructured PDF/web content → KnowledgeDocument + Section, and `chat` for conversation transcripts → ConversationArchive + Section:Conversation chunks. Ships
|
|
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 conversation transcripts → ConversationArchive + Section:Conversation chunks. Conversation-archive Phase 2 (`conversation-archive-derive-insights`) walks chunks of one named archive and emits per-row claim proposals for operator-gated wiring; `conversation-archive-enrich-rejection` records (or undoes) durable per-row rejections so already-triaged claims do not re-surface on re-runs. Ships four skills: `conversational-memory`, `document-ingest`, `conversation-archive` (source-agnostic transcript ingest for WhatsApp, Telegram, Signal, LinkedIn DMs, Zoom, meeting minutes, iMessage, Slack), and `conversation-archive-enrich` (per-row operator-gated insight derivation over a named archive's chunks)."
|
|
4
4
|
tools:
|
|
5
5
|
- memory-search
|
|
6
6
|
- memory-rank
|
|
@@ -20,6 +20,8 @@ tools:
|
|
|
20
20
|
- memory-edit-attachment
|
|
21
21
|
- memory-rename-attachment
|
|
22
22
|
- memory-archive-write
|
|
23
|
+
- conversation-archive-derive-insights
|
|
24
|
+
- conversation-archive-enrich-rejection
|
|
23
25
|
- conversation-list
|
|
24
26
|
- conversation-search
|
|
25
27
|
- profile-read
|
|
@@ -36,6 +38,7 @@ skills:
|
|
|
36
38
|
- skills/conversational-memory/SKILL.md
|
|
37
39
|
- skills/document-ingest/SKILL.md
|
|
38
40
|
- skills/conversation-archive/SKILL.md
|
|
41
|
+
- skills/conversation-archive-enrich/SKILL.md
|
|
39
42
|
always: true
|
|
40
43
|
embed: false
|
|
41
44
|
---
|
|
@@ -36,6 +36,8 @@ import { graphPruneDenylistAdd } from "./tools/graph-prune-denylist-add.js";
|
|
|
36
36
|
import { graphPruneDenylistList } from "./tools/graph-prune-denylist-list.js";
|
|
37
37
|
import { graphPruneDenylistRemove } from "./tools/graph-prune-denylist-remove.js";
|
|
38
38
|
import { conversationMemoryExpunge } from "./tools/conversation-memory-expunge.js";
|
|
39
|
+
import { conversationArchiveDeriveInsights, } from "./tools/conversation-archive-derive-insights.js";
|
|
40
|
+
import { conversationArchiveEnrichRejection } from "./tools/conversation-archive-enrich-rejection.js";
|
|
39
41
|
import { getSession, closeDriver } from "./lib/neo4j.js";
|
|
40
42
|
import { embed } from "./lib/embeddings.js";
|
|
41
43
|
import { notTrashed } from "../../../../lib/graph-trash/dist/index.js";
|
|
@@ -415,6 +417,131 @@ server.tool("memory-rank", "Retrieve entities from the knowledge graph and rank
|
|
|
415
417
|
};
|
|
416
418
|
}
|
|
417
419
|
});
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
// Task 892 — conversation-archive Phase 2: chunk-anchored insight derivation.
|
|
422
|
+
// Read-only tool. Walks :Section:Conversation chunks of one named
|
|
423
|
+
// :ConversationArchive in pages and asks Haiku (via OAuth) for the
|
|
424
|
+
// high-confidence claims. Returns operator-facing proposals with the cypher
|
|
425
|
+
// needed to wire them; the conversation-archive-enrich skill drives the
|
|
426
|
+
// per-row operator gate. The tool itself NEVER writes; idempotency lives on
|
|
427
|
+
// the MERGE-key shape (chunkElementId, kind, contentHash) encoded in each
|
|
428
|
+
// proposal's mergeCypher.
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
server.tool("conversation-archive-derive-insights", "Walk the :Section:Conversation chunks of one named :ConversationArchive and emit high-confidence claim proposals (mention, task, preference, observed-relationship). Read-only: the caller drives a per-row operator gate; this tool never writes. Paginates via chunkOffset + chunkLimit (default 5). Operator-triggered, never automatic.", {
|
|
431
|
+
archiveElementId: z
|
|
432
|
+
.string()
|
|
433
|
+
.describe("elementId of the :ConversationArchive the operator named (required)."),
|
|
434
|
+
chunkOffset: z
|
|
435
|
+
.number()
|
|
436
|
+
.int()
|
|
437
|
+
.nonnegative()
|
|
438
|
+
.optional()
|
|
439
|
+
.describe("Zero-based chunk offset. Default 0. Increase per page."),
|
|
440
|
+
chunkLimit: z
|
|
441
|
+
.number()
|
|
442
|
+
.int()
|
|
443
|
+
.positive()
|
|
444
|
+
.max(20)
|
|
445
|
+
.optional()
|
|
446
|
+
.describe("Chunks per page (1..20). Default 5 — small pages keep MCP calls under SDK timeout windows."),
|
|
447
|
+
}, async ({ archiveElementId, chunkOffset, chunkLimit }) => {
|
|
448
|
+
try {
|
|
449
|
+
const result = await conversationArchiveDeriveInsights({
|
|
450
|
+
accountId,
|
|
451
|
+
archiveElementId,
|
|
452
|
+
chunkOffset,
|
|
453
|
+
chunkLimit,
|
|
454
|
+
sessionId: resolveSessionId(),
|
|
455
|
+
});
|
|
456
|
+
const proposalLines = result.proposals
|
|
457
|
+
.map((p, i) => {
|
|
458
|
+
const disambig = p.disambiguation && p.disambiguation.needsResolution.length > 0
|
|
459
|
+
? ` resolve:${p.disambiguation.needsResolution.map((r) => `${r.role}="${r.displayName}"`).join(",")}`
|
|
460
|
+
: "";
|
|
461
|
+
return [
|
|
462
|
+
`Proposal ${i + 1}/${result.proposals.length}:`,
|
|
463
|
+
` chunkElementId=${p.chunkElementId}`,
|
|
464
|
+
` kind=${p.kind}`,
|
|
465
|
+
` contentHash=${p.contentHash}`,
|
|
466
|
+
` evidence="${p.evidenceSnippet}"`,
|
|
467
|
+
` proposedAction=${p.proposedAction}${disambig}`,
|
|
468
|
+
` mergeCypher=${p.mergeCypher.replace(/\n/g, " ")}`,
|
|
469
|
+
` mergeParams=${JSON.stringify(p.mergeParams)}`,
|
|
470
|
+
].join("\n");
|
|
471
|
+
})
|
|
472
|
+
.join("\n\n");
|
|
473
|
+
const header = `archiveElementId=${result.archiveElementId} title=${JSON.stringify(result.archiveTitle ?? "")} totalChunks=${result.totalChunks} walked=${result.walkedFrom}..${result.walkedTo} proposals=${result.proposals.length} proposalsRemaining=${result.proposalsRemaining} emptyChunks=${result.emptyChunkElementIds.length}`;
|
|
474
|
+
return {
|
|
475
|
+
content: [
|
|
476
|
+
{
|
|
477
|
+
type: "text",
|
|
478
|
+
text: result.proposals.length === 0
|
|
479
|
+
? `${header}\n(no high-confidence claims in this page; advance chunkOffset to walk further)`
|
|
480
|
+
: `${header}\n\n${proposalLines}`,
|
|
481
|
+
},
|
|
482
|
+
],
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
catch (err) {
|
|
486
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
487
|
+
return {
|
|
488
|
+
content: [{ type: "text", text: msg }],
|
|
489
|
+
isError: true,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
process.stderr.write("[memory-mcp] registered tool=conversation-archive-derive-insights\n");
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// Task 980 — durable rejection memory for conversation-archive-enrich.
|
|
496
|
+
// Writes to a sidecar JSONL alongside the account's state directory; the
|
|
497
|
+
// filter step in conversation-archive-derive-insights reads the same file.
|
|
498
|
+
// Modes:
|
|
499
|
+
// record — append rejection line (idempotent on duplicate keys).
|
|
500
|
+
// undo — drop matching line via atomic-rename (called after a `wire`
|
|
501
|
+
// so a later wire of a once-rejected key cleans up its rejection).
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
503
|
+
server.tool("conversation-archive-enrich-rejection", "Record or undo a per-row rejection from conversation-archive-enrich. Writes a sidecar JSONL keyed on (chunkElementId, kind, contentHash). Call with mode='record' on operator `reject`; call with mode='undo' after a successful `wire` so a later wire of a once-rejected key clears its rejection. Idempotent: duplicate records are no-ops.", {
|
|
504
|
+
archiveElementId: z
|
|
505
|
+
.string()
|
|
506
|
+
.describe("elementId of the :ConversationArchive (carried on each line for diagnostics)."),
|
|
507
|
+
chunkElementId: z
|
|
508
|
+
.string()
|
|
509
|
+
.describe("elementId of the :Section:Conversation chunk the rejection applies to."),
|
|
510
|
+
kind: z
|
|
511
|
+
.string()
|
|
512
|
+
.describe("Proposal kind — one of mention|task|preference|observed-relationship."),
|
|
513
|
+
contentHash: z
|
|
514
|
+
.string()
|
|
515
|
+
.describe("The proposal's contentHash (sha256 over kind + payload)."),
|
|
516
|
+
mode: z
|
|
517
|
+
.enum(["record", "undo"])
|
|
518
|
+
.describe("record = append rejection; undo = remove rejection (called after `wire`)."),
|
|
519
|
+
}, async ({ archiveElementId, chunkElementId, kind, contentHash, mode }) => {
|
|
520
|
+
try {
|
|
521
|
+
const result = conversationArchiveEnrichRejection({
|
|
522
|
+
accountId,
|
|
523
|
+
archiveElementId,
|
|
524
|
+
chunkElementId,
|
|
525
|
+
kind,
|
|
526
|
+
contentHash,
|
|
527
|
+
mode,
|
|
528
|
+
sessionId: resolveSessionId(),
|
|
529
|
+
});
|
|
530
|
+
const summary = `mode=${result.mode} beforeCount=${result.beforeCount} afterCount=${result.afterCount} path=${result.path}`;
|
|
531
|
+
return {
|
|
532
|
+
content: [{ type: "text", text: summary }],
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
catch (err) {
|
|
536
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
537
|
+
process.stderr.write(`[conversation-archive-enrich] rejection-tool-fail mode=${mode} chunk=${chunkElementId} reason="${msg}"\n`);
|
|
538
|
+
return {
|
|
539
|
+
content: [{ type: "text", text: msg }],
|
|
540
|
+
isError: true,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
process.stderr.write("[memory-mcp] registered tool=conversation-archive-enrich-rejection\n");
|
|
418
545
|
if (!readOnly) {
|
|
419
546
|
server.tool("memory-write", "Create a new node in the knowledge graph with automatic embedding computation. Every node must be created with at least one relationship — call memory-search first to find target elementIds. Writes targeting :Person, :UserProfile, :AdminUser, :Organization, :LocalBusiness, :CloudflareTunnel, or :CloudflareHostname additionally require an inbound :PRODUCED edge from a :Task — pass the active Task's elementId as `producedByTaskId` and the edge will be composed automatically.", {
|
|
420
547
|
labels: z
|