@rubytech/create-maxy-code 0.1.147 → 0.1.150
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/graph-search/dist/index.d.ts +34 -11
- package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-search/dist/index.js +42 -13
- package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-search/src/__tests__/fulltext-coverage.test.ts +75 -40
- package/payload/platform/lib/graph-search/src/index.ts +45 -12
- package/payload/platform/lib/graph-style/dist/index.d.ts +5 -5
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +11 -9
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/index.ts +11 -9
- package/payload/platform/lib/graph-trash/dist/index.js +6 -6
- package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-trash/src/index.ts +6 -6
- package/payload/platform/neo4j/edge-annotations.json +4 -4
- package/payload/platform/neo4j/schema.cypher +143 -58
- package/payload/platform/plugins/admin/PLUGIN.md +1 -2
- package/payload/platform/plugins/admin/hooks/__tests__/pre-turn-graph-pass.test.sh +544 -0
- package/payload/platform/plugins/admin/hooks/__tests__/session-end-retrospective.test.sh +2 -2
- package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +2 -2
- package/payload/platform/plugins/admin/hooks/{post-turn-graph-pass.sh → pre-turn-graph-pass.sh} +83 -53
- package/payload/platform/plugins/admin/hooks/session-end-retrospective.sh +6 -3
- package/payload/platform/plugins/docs/references/linkedin-extension.md +1 -1
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +2 -2
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +5 -5
- package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +4 -4
- package/payload/platform/plugins/memory/bin/conversation-archive-ingest.mjs +5 -5
- package/payload/platform/plugins/memory/bin/conversation-archive-ingest.sh +1 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +35 -33
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js +4 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.js +90 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts +14 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js +27 -4
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.js +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +2 -2
- 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 +3 -4
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js +6 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js +17 -17
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.js +329 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-compiled-truth-rejection.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-compiled-truth-rejection.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-compiled-truth-rejection.test.js +39 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-compiled-truth-rejection.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +3 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +6 -6
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +9 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +6 -5
- 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 +30 -29
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js +4 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +1 -11
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +27 -18
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +83 -70
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +27 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
- package/payload/platform/plugins/memory/references/schema-base.md +7 -6
- package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +12 -12
- package/payload/platform/plugins/scheduling/skills/daily-prep/SKILL.md +2 -2
- package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
- package/payload/platform/plugins/x-import/PLUGIN.md +3 -3
- package/payload/platform/plugins/x-import/skills/x-import/SKILL.md +7 -7
- package/payload/platform/scripts/seed-neo4j.sh +3 -3
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +0 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +7 -7
- package/payload/platform/templates/specialists/agents/database-operator.md +6 -6
- package/payload/platform/templates/specialists/agents/librarian.md +2 -2
- package/payload/server/public/assets/{admin-_pm0RRTE.js → admin-CQMFJD81.js} +1 -1
- package/payload/server/public/assets/{data-b6q64Ot4.js → data-k-sUX-rP.js} +1 -1
- package/payload/server/public/assets/{graph-D1SVlBLb.js → graph-By7i71WM.js} +1 -1
- package/payload/server/public/assets/{graph-labels-BiDGzVs2.js → graph-labels-DemDBXA3.js} +1 -1
- package/payload/server/public/assets/{page-DwFigpyR.js → page-CHBpFW3X.js} +1 -1
- package/payload/server/public/assets/{page-BV6E4J2q.js → page-jqA-w9gF.js} +1 -1
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/server.js +29 -20
- package/payload/platform/plugins/admin/skills/commitment-followthrough/SKILL.md +0 -60
package/payload/platform/plugins/admin/hooks/{post-turn-graph-pass.sh → pre-turn-graph-pass.sh}
RENAMED
|
@@ -1,38 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
2
|
+
# UserPromptSubmit hook — pre-turn graph pass. Task 425.
|
|
3
3
|
#
|
|
4
|
-
# Fires
|
|
4
|
+
# Fires once per operator prompt submission. Dispatches the
|
|
5
5
|
# `database-operator` specialist with the full conversation (as
|
|
6
6
|
# `[role: text]` pairs) plus the actual content of every node already
|
|
7
7
|
# written under this `conversationId`. The specialist decides what new
|
|
8
8
|
# graph rows the conversation supports and writes them.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
10
|
+
# Supersedes Task 412's Stop-hook registration. The Stop hook fired on
|
|
11
|
+
# every assistant `end_turn`, including the agent's own ack after the
|
|
12
|
+
# dispatch, producing an unbounded loop. Firing on `UserPromptSubmit`
|
|
13
|
+
# fires once per operator turn and removes the loop primitive entirely.
|
|
14
|
+
# The agent's response to the most recent operator turn is not covered
|
|
15
|
+
# by a per-turn pass until the next operator turn; the
|
|
16
|
+
# `session-end-retrospective.sh` Stop hook walks the full transcript at
|
|
17
|
+
# `/end` and picks up the final assistant reply.
|
|
18
|
+
#
|
|
19
|
+
# The two doctrinal early exits remain:
|
|
12
20
|
# - MAXY_SESSION_ROLE must equal "admin" → reason=role-not-admin
|
|
13
21
|
# - MAXY_SPECIALIST must be empty → reason=is-specialist
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# operator's transcript via the dispatch prompt, not via its own PTY.)
|
|
17
|
-
#
|
|
18
|
-
# Duplicate writes between this background pass and the admin agent's
|
|
19
|
-
# discretionary inline `database-operator` dispatch are accepted at
|
|
20
|
-
# write time; cleanup is delegated to the dream-cycle / hygiene sweeps
|
|
21
|
-
# (Tasks 410, 411).
|
|
22
|
+
# `missing-transcript` is retained as a safety exit. `empty-stdin` is
|
|
23
|
+
# dropped — UserPromptSubmit always carries a JSON envelope.
|
|
22
24
|
#
|
|
23
|
-
# Input: Claude Code's
|
|
24
|
-
# { "session_id": "<intrinsic>", "transcript_path": "<jsonl path>",
|
|
25
|
+
# Input: Claude Code's UserPromptSubmit hook stdin shape
|
|
26
|
+
# { "session_id": "<intrinsic>", "transcript_path": "<jsonl path>",
|
|
27
|
+
# "cwd": "<…>", "hook_event_name": "UserPromptSubmit",
|
|
28
|
+
# "prompt": "<operator text>" }
|
|
29
|
+
# `prompt` is ignored — the conversation walk reads the JSONL, which
|
|
30
|
+
# already contains the just-submitted user message because
|
|
31
|
+
# UserPromptSubmit fires after the prompt is written.
|
|
25
32
|
#
|
|
26
|
-
# Output:
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
33
|
+
# Output: stdout JSON of the documented UserPromptSubmit shape:
|
|
34
|
+
# { "hookSpecificOutput": { "hookEventName": "UserPromptSubmit",
|
|
35
|
+
# "additionalContext": "<directive body>" } }
|
|
36
|
+
# exit 0. The directive carries the literal token
|
|
37
|
+
# `[system: pre-turn-graph-pass-hook]` so the agent's IDENTITY.md
|
|
38
|
+
# UserPromptSubmit-directive section can distinguish it from operator
|
|
39
|
+
# text. The agent performs the named Task dispatch as a background pass
|
|
40
|
+
# and then continues with its normal response to the operator.
|
|
30
41
|
|
|
31
42
|
set -uo pipefail
|
|
32
43
|
|
|
33
44
|
UI_PORT="${MAXY_UI_INTERNAL_PORT:-}"
|
|
34
45
|
if [ -z "$UI_PORT" ]; then
|
|
35
|
-
echo "[
|
|
46
|
+
echo "[pre-turn-graph-pass] trigger-skipped sessionId=unknown reason=missing-env env=MAXY_UI_INTERNAL_PORT" >&2
|
|
36
47
|
exit 0
|
|
37
48
|
fi
|
|
38
49
|
UI_BASE="http://127.0.0.1:${UI_PORT}"
|
|
@@ -46,7 +57,7 @@ emit_log() {
|
|
|
46
57
|
--max-time 2 \
|
|
47
58
|
--data "$(python3 -c '
|
|
48
59
|
import sys, json
|
|
49
|
-
print(json.dumps({"tag": "
|
|
60
|
+
print(json.dumps({"tag": "pre-turn-graph-pass", "level": "info", "line": sys.argv[1]}))
|
|
50
61
|
' "$line")" \
|
|
51
62
|
"$LOG_INGEST_URL" 2>/dev/null || true
|
|
52
63
|
}
|
|
@@ -62,14 +73,21 @@ try:
|
|
|
62
73
|
d = json.load(sys.stdin)
|
|
63
74
|
sid = d.get("session_id", "") or ""
|
|
64
75
|
tpath = d.get("transcript_path", "") or ""
|
|
65
|
-
|
|
76
|
+
event = d.get("hook_event_name", "") or ""
|
|
77
|
+
print(f"{sid}\t{tpath}\t{event}")
|
|
66
78
|
except Exception:
|
|
67
|
-
print("\t")
|
|
79
|
+
print("\t\t")
|
|
68
80
|
' 2>/dev/null)
|
|
69
81
|
SESSION_ID="${PARSED%% *}"
|
|
70
|
-
|
|
82
|
+
REST="${PARSED#* }"
|
|
83
|
+
TRANSCRIPT_PATH="${REST%% *}"
|
|
84
|
+
HOOK_EVENT_NAME="${REST#* }"
|
|
71
85
|
SESSION_ID_REPORTED="${SESSION_ID:-unknown}"
|
|
72
86
|
|
|
87
|
+
if [ -n "$HOOK_EVENT_NAME" ] && [ "$HOOK_EVENT_NAME" != "UserPromptSubmit" ]; then
|
|
88
|
+
emit_log "trigger-skipped sessionId=${SESSION_ID_REPORTED} reason=wrong-hook-event event=${HOOK_EVENT_NAME}"
|
|
89
|
+
exit 0
|
|
90
|
+
fi
|
|
73
91
|
if [ "${MAXY_SESSION_ROLE:-}" != "admin" ]; then
|
|
74
92
|
emit_log "trigger-skipped sessionId=${SESSION_ID_REPORTED} reason=role-not-admin"
|
|
75
93
|
exit 0
|
|
@@ -78,10 +96,6 @@ if [ -n "${MAXY_SPECIALIST:-}" ]; then
|
|
|
78
96
|
emit_log "trigger-skipped sessionId=${SESSION_ID_REPORTED} reason=is-specialist specialist=${MAXY_SPECIALIST}"
|
|
79
97
|
exit 0
|
|
80
98
|
fi
|
|
81
|
-
if [ -z "$INPUT" ]; then
|
|
82
|
-
emit_log "trigger-skipped sessionId=${SESSION_ID_REPORTED} reason=empty-stdin"
|
|
83
|
-
exit 0
|
|
84
|
-
fi
|
|
85
99
|
if [ -z "$SESSION_ID" ] || [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
|
|
86
100
|
emit_log "trigger-skipped sessionId=${SESSION_ID_REPORTED} reason=missing-transcript"
|
|
87
101
|
exit 0
|
|
@@ -91,9 +105,10 @@ CONVERSATION_ID="${CONVERSATION_NODE_ID:-}"
|
|
|
91
105
|
|
|
92
106
|
# Walk the transcript and emit the conversation as `[role: text]` pairs,
|
|
93
107
|
# oldest first. User turns whose only content is `tool_result` blocks are
|
|
94
|
-
# skipped (they carry
|
|
95
|
-
#
|
|
96
|
-
#
|
|
108
|
+
# skipped (they carry tool plumbing, not operator intent). Multi-block
|
|
109
|
+
# assistant turns concatenate their `text` blocks; `tool_use` blocks are
|
|
110
|
+
# dropped. The just-submitted operator prompt is already present in the
|
|
111
|
+
# JSONL by the time UserPromptSubmit fires.
|
|
97
112
|
CONVERSATION=$(TRANSCRIPT_PATH="$TRANSCRIPT_PATH" python3 - <<'PY'
|
|
98
113
|
import os, json, sys
|
|
99
114
|
|
|
@@ -225,27 +240,42 @@ fi
|
|
|
225
240
|
|
|
226
241
|
emit_log "dispatch sessionId=${SESSION_ID} conversationId=${CONVERSATION_ID:-<unset>} conversationTurns=${CONVERSATION_TURNS} priorWritesCount=${PRIOR_WRITES_COUNT} contextBytes=${CONTEXT_BYTES} ms=0"
|
|
227
242
|
|
|
228
|
-
# Emit the
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
243
|
+
# Emit the documented UserPromptSubmit JSON envelope on stdout. Python
|
|
244
|
+
# composes the directive body and handles JSON encoding so embedded
|
|
245
|
+
# newlines, quotes, and backslashes in the conversation and prior-writes
|
|
246
|
+
# blocks are escaped correctly.
|
|
247
|
+
ACCOUNT_ID_OUT="${ACCOUNT_ID:-<unset>}" \
|
|
248
|
+
SESSION_ID_OUT="${SESSION_ID}" \
|
|
249
|
+
CONVERSATION_ID_OUT="${CONVERSATION_ID:-<unset>}" \
|
|
250
|
+
CONVERSATION_OUT="${CONVERSATION}" \
|
|
251
|
+
PRIOR_WRITES_OUT="${PRIOR_WRITES}" \
|
|
252
|
+
python3 - <<'PY'
|
|
253
|
+
import os, json, sys
|
|
254
|
+
body = (
|
|
255
|
+
"[system: pre-turn-graph-pass-hook]\n"
|
|
256
|
+
"Before responding to the operator, dispatch the database-operator specialist via the Task tool with the parameters below. "
|
|
257
|
+
"This is a background graph-write pass; do not surface the dispatch in your reply to the operator. "
|
|
258
|
+
"After the specialist returns its one-line summary, continue with your normal response to the operator's prompt.\n\n"
|
|
259
|
+
"Agent dispatch parameters:\n"
|
|
260
|
+
" subagent_type: database-operator\n"
|
|
261
|
+
" prompt: |\n"
|
|
262
|
+
f" accountId: {os.environ['ACCOUNT_ID_OUT']}\n"
|
|
263
|
+
f" sessionId: {os.environ['SESSION_ID_OUT']}\n"
|
|
264
|
+
f" conversationId: {os.environ['CONVERSATION_ID_OUT']}\n\n"
|
|
265
|
+
" Background pre-turn graph pass. Given the conversation below and the list of nodes already written under this conversationId, derive any new graph rows the conversation supports (mentions, ideas, learnings, contacts, tasks — anything the schema admits) and write them. Skip writes the prior list already covers; rely on MERGE semantics for the rest. Return one line in the form: db-op: writes=<n> ms=<n>\n\n"
|
|
266
|
+
" <conversation>\n"
|
|
267
|
+
f" {os.environ['CONVERSATION_OUT']}\n"
|
|
268
|
+
" </conversation>\n\n"
|
|
269
|
+
" <prior-writes>\n"
|
|
270
|
+
f" {os.environ['PRIOR_WRITES_OUT']}\n"
|
|
271
|
+
" </prior-writes>\n"
|
|
272
|
+
)
|
|
273
|
+
sys.stdout.write(json.dumps({
|
|
274
|
+
"hookSpecificOutput": {
|
|
275
|
+
"hookEventName": "UserPromptSubmit",
|
|
276
|
+
"additionalContext": body,
|
|
277
|
+
}
|
|
278
|
+
}))
|
|
279
|
+
PY
|
|
250
280
|
|
|
251
|
-
exit
|
|
281
|
+
exit 0
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
#
|
|
21
21
|
# Sentinel detection. Completion is recognised by greping the operator's
|
|
22
22
|
# own JSONL (transcript_path on the Stop envelope) for a `tool_use` block
|
|
23
|
-
# whose `name` is exactly `
|
|
23
|
+
# whose `name` is exactly `mcp__admin__session-retrospective-mark-complete`
|
|
24
|
+
# — that is the prefixed form Claude Code writes for MCP tools in the
|
|
25
|
+
# JSONL; the unprefixed name only appears in the operator-facing
|
|
26
|
+
# instruction text below. The
|
|
24
27
|
# sentinel-grep doubles as the re-entry guard: every Stop that fires after
|
|
25
28
|
# the agent's sentinel call sees the sentinel and exits 0 with
|
|
26
29
|
# `gate-released`, so the agent's wrap-up turn never re-blocks.
|
|
@@ -128,12 +131,12 @@ fi
|
|
|
128
131
|
# 1. The latest end-intent token in the most recent real-user message,
|
|
129
132
|
# or empty if no real user message matches.
|
|
130
133
|
# 2. Whether any `tool_use` block in the transcript names the sentinel
|
|
131
|
-
# tool `
|
|
134
|
+
# tool `mcp__admin__session-retrospective-mark-complete`.
|
|
132
135
|
# Output one line: <intent-token-or-empty>\t<sentinel-present-yes|no>
|
|
133
136
|
INSPECTION=$(TRANSCRIPT_PATH="$TRANSCRIPT_PATH" python3 - <<'PY'
|
|
134
137
|
import os, json
|
|
135
138
|
|
|
136
|
-
SENTINEL_TOOL = "
|
|
139
|
+
SENTINEL_TOOL = "mcp__admin__session-retrospective-mark-complete"
|
|
137
140
|
INTENT_TOKENS = ("/end", "/archive", "end session", "archive this session")
|
|
138
141
|
|
|
139
142
|
def is_real_user(rec):
|
|
@@ -32,7 +32,7 @@ Every click produces **one `:KnowledgeDocument`** keyed on the page URL, holding
|
|
|
32
32
|
|
|
33
33
|
Soft signals ("interested in chatting", "would love to compare notes") stay in the document body. They are never promoted to graph nodes.
|
|
34
34
|
|
|
35
|
-
The plugin will never create `:Communication`,
|
|
35
|
+
The plugin will never create `:Communication`, `:ConversationArchive` rows (i.e. KnowledgeDocument nodes that carry `conversationIdentity`), or `:Message` nodes — those shapes are reserved for other flows (live chat, archive ingest).
|
|
36
36
|
|
|
37
37
|
## When the pill turns amber
|
|
38
38
|
|
|
@@ -54,7 +54,7 @@ Roles are installed during setup and listed when {{productName}} introduces itse
|
|
|
54
54
|
|
|
55
55
|
{{productName}} maintains a graph database (Neo4j) of everything you've told it. People, conversations, preferences, and context are stored as connected nodes. When you ask {{productName}} something, it searches this graph to retrieve relevant context before responding.
|
|
56
56
|
|
|
57
|
-
**The recording loop.**
|
|
57
|
+
**The recording loop.** When you submit a prompt, a UserPromptSubmit hook (`pre-turn-graph-pass.sh`) fires the `database-operator` specialist as a background dispatch before {{productName}} composes its reply. The dispatch carries the full conversation up to and including your just-submitted prompt as `[role: text]` pairs, plus the list of every node already written under this conversation, fetched via `GET /api/admin/post-turn-context`. The specialist decides what new graph rows the conversation supports — mentions of named entities, ideas, learnings, contacts, tasks, anything the schema admits — and writes them. Duplicate writes between this background pass and any inline write the admin agent dispatched mid-turn are accepted and cleaned up by the dream-cycle / hygiene sweeps. One dispatch per operator prompt; the dispatch fires before {{productName}}'s reply and runs in parallel with it. The admin agent can also dispatch `database-operator` inline at its own discretion when a write must complete before the assistant response ends. The final assistant reply of the session is not covered by the per-turn pass (no further operator prompt fires) and is picked up by the `session-end-retrospective.sh` Stop hook when you type `/end`. To disable the background pass for an account, remove the `pre-turn-graph-pass.sh` UserPromptSubmit-hook entry from that account's `~/.<brand>/.claude/settings.json`; there is no toggle.
|
|
58
58
|
|
|
59
59
|
The memory graph is stored on your Pi. It never leaves your network.
|
|
60
60
|
|
|
@@ -40,9 +40,9 @@ These are enabled during onboarding and can be added or removed at any time. Som
|
|
|
40
40
|
| `linkedin-import` | Import a LinkedIn Basic Data Export — Profile and Connections today, more CSVs as references land | Database operator |
|
|
41
41
|
| `notion-import` | Import a Notion workspace export (markdown + CSV) — pages, databases, hierarchy, attachments, schema-bounded relations, `@person` mentions account-filtered | Database operator |
|
|
42
42
|
| `obsidian-import` | Import an extracted Obsidian vault — pages map to `:KnowledgeDocument`, wikilinks resolve to intra-vault pages or existing entities, tags become `:DefinedTerm`, embedded images become `:DigitalDocument`. Two-phase tool (dry-run → operator disambiguation → commit). | Database operator |
|
|
43
|
-
| `x-import` | Import an X (Twitter) Basic Data Export — tweet stream renders as one chronological transcript and ingests as a single `:KnowledgeDocument` (`source='x'`); each DM `conversationId` ingests as one
|
|
43
|
+
| `x-import` | Import an X (Twitter) Basic Data Export — tweet stream renders as one chronological transcript and ingests as a single `:KnowledgeDocument` (`source='x'`); each DM `conversationId` ingests as one `:ConversationArchive` (`source='x-dm'`, keyed on `conversationIdentity`) via `conversation-archive-ingest.sh`. Mentions, replies, and quote-tweet authors resolve to `:Person` on lowercased `xHandle`; every handle and DM senderId confirms against existing nodes (no auto-create). Per-thread KD granularity and `:Post` / `:DirectMessage` labels are explicitly rejected. | Database operator |
|
|
44
44
|
| `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, X DMs — `--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` chunks → memory-ingest with `conversationIdentity` set (writes `:KnowledgeDocument`), `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`. Distinct from the live `whatsapp` plugin (Baileys). | Database operator |
|
|
45
|
-
| `memory/skills/conversation-archive-enrich` | Phase 2 for any named
|
|
45
|
+
| `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 the parent's `:Section` 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 |
|
|
46
46
|
|
|
47
47
|
### Claude Official (marketplace)
|
|
48
48
|
|
|
@@ -55,7 +55,7 @@ When the owner is an external Person (non-operator archive), the anchor is the c
|
|
|
55
55
|
- LinkedIn skills → `:DefinedTerm` with `category: 'linkedin-skill'`.
|
|
56
56
|
- LinkedIn articles → `:CreativeWork` with `category: 'linkedin-article'`.
|
|
57
57
|
- LinkedIn recommendations → `:Review`.
|
|
58
|
-
- LinkedIn DMs → `:
|
|
58
|
+
- LinkedIn DMs → routed to the `conversation-archive` skill (Task 397) as a `--source linkedin-messages` invocation. Each DM thread becomes one `:ConversationArchive` parent with `:Section` chunks; no bespoke LinkedIn sublabels.
|
|
59
59
|
- Certifications → `:Credential` (genuinely new — no existing {{productName}} label fits `schema:EducationalOccupationalCredential`).
|
|
60
60
|
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).
|
|
61
61
|
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.
|
|
@@ -116,10 +116,10 @@ Each entry maps to exactly one reference. Order reflects when a reference's edge
|
|
|
116
116
|
| 6 | `Company Follows.csv` | _pending_ | `(:AdminUser)-[:FOLLOWS {since}]->(:Organization)` |
|
|
117
117
|
| 6 | `Causes You Care About.csv` | _pending_ | `(:UserProfile)-[:SUPPORTS]->(:DefinedTerm {category:'cause'})` |
|
|
118
118
|
| 6 | `Events.csv` | _pending_ | `(:AdminUser)-[:ATTENDED_EVENT]->(:Event)` |
|
|
119
|
-
| 7 | `messages.csv` |
|
|
120
|
-
| 7 | `guide_messages.csv` |
|
|
121
|
-
| 7 | `learning_coach_messages.csv` |
|
|
122
|
-
| 7 | `learning_role_play_messages.csv` |
|
|
119
|
+
| 7 | `messages.csv` | conversation-archive (Task 397) | dispatched to `conversation-archive` skill with `--source linkedin-messages`; one `:ConversationArchive` per DM thread + `:Section` chunks + `(:Person)-[:PARTICIPANT_IN]->(archive)` |
|
|
120
|
+
| 7 | `guide_messages.csv` | same | same dispatch (likely empty) |
|
|
121
|
+
| 7 | `learning_coach_messages.csv` | same | same dispatch (likely empty) |
|
|
122
|
+
| 7 | `learning_role_play_messages.csv` | same | same dispatch (likely empty) |
|
|
123
123
|
| 8 | `Articles/` | _pending_ | `(:AdminUser)-[:AUTHORED]->(:CreativeWork {category:'linkedin-article'})` |
|
|
124
124
|
| 8 | `Learning.csv` | _pending_ | `(:UserProfile)-[:COMPLETED]->(:Credential)` (LinkedIn Learning courses) |
|
|
125
125
|
| 9 | `Jobs/Job Applications.csv` | _pending_ | `(:AdminUser)-[:APPLIED_TO {date}]->(:Organization)` via job-posting text on the edge |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory",
|
|
3
|
-
"description": "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-rank, memory-write, memory-update, and the :Report surface (memory-report-write / memory-report-read-latest / memory-report-list) 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 (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :
|
|
3
|
+
"description": "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-rank, memory-write, memory-update, and the :Report surface (memory-report-write / memory-report-read-latest / memory-report-list) 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 (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :ConversationArchive (Task 397, keyed on `conversationIdentity`) + :Section chunks; two parent labels, two writer paths chosen by which identity property is set. 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
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: memory
|
|
3
|
-
description: "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-rank, memory-write, memory-update, and the :Report surface (memory-report-write / memory-report-read-latest / memory-report-list) 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 (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :
|
|
3
|
+
description: "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-rank, memory-write, memory-update, and the :Report surface (memory-report-write / memory-report-read-latest / memory-report-list) 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 (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :ConversationArchive (Task 397, keyed on `conversationIdentity`) + :Section chunks; two parent labels, two writer paths chosen by which identity property is set. 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
|
- name: memory-search
|
|
6
6
|
publicAllowlist: true
|
|
@@ -160,7 +160,7 @@ Use `expandHops: 0` for listing and inventory queries (returns node properties o
|
|
|
160
160
|
|
|
161
161
|
## Hierarchy
|
|
162
162
|
|
|
163
|
-
Every node in the graph sits under a parent in the canonical chain `LocalBusiness → Project → Task | Person | Organisation | KnowledgeDocument
|
|
163
|
+
Every node in the graph sits under a parent in the canonical chain `LocalBusiness → Project → Task | Person | Organisation | KnowledgeDocument | ConversationArchive` (Task 397 promoted `:ConversationArchive` to a peer parent). `memory-write` and `memory-ingest` require the caller to name the parent (an `elementId` for the resolved parent node) and refuse the write if the parent does not exist or is at the wrong level of the chain. Specialists author the parent resolution — typically via `memory-search` against the brief — before invoking the write. Cross-hierarchy edges (a `:Person` linked to several Projects, a `:KnowledgeDocument` or `:ConversationArchive` about several Organisations) are normal and are added as separate `memory-write` calls; the hierarchy rule governs the containment edge that anchors the node, not the additional cross-edges that describe what the node relates to.
|
|
164
164
|
|
|
165
165
|
## Anchoring writes to provenance — `producedByTaskId` and the conversation env-stamp
|
|
166
166
|
|
|
@@ -182,7 +182,7 @@ Graph hygiene is **agent-directed, case by case** — no autonomous rule engine,
|
|
|
182
182
|
**Conversation-level expunge.**
|
|
183
183
|
- `conversation-memory-expunge` — agent-invoked, scoped to one conversation. Scrubs a specified substring from every linked `:Memory` node's string properties with audit. Transcript Message nodes are immutable history and are never modified — only the derived `:Memory` payload that subsequent `memory-search` would re-surface.
|
|
184
184
|
|
|
185
|
-
**Single-node removal.** Use `memory-delete` by `elementId`. The node is marked `:Trashed` (relationships preserved, invisible to `memory-search` and any read filtered via `notTrashed`). For `KnowledgeDocument
|
|
185
|
+
**Single-node removal.** Use `memory-delete` by `elementId`. The node is marked `:Trashed` (relationships preserved, invisible to `memory-search` and any read filtered via `notTrashed`). For `KnowledgeDocument` and `ConversationArchive` (Task 397), the trash cascades to linked `Section` and `Chunk` children. Restore via `memory-restore` (fails loudly when an active node already holds a unique slot the trashed one needs back). Hard removal happens later via `memory-empty-trash` after the grace period (default 30 days). GDPR cascades go through `contact-erase` (per Art. 17) — that bypasses the trash primitive deliberately. There is no bulk delete-by-shape.
|
|
186
186
|
|
|
187
187
|
**Why soft-delete.** On 2026-04-20 an autonomous orphan-delete rule wiped 19 nodes in one `graph-prune-run` call; properties were unrecoverable (Neo4j Community has no PITR, no APOC). The cron, the `graph-prune-run` tool, and the rule engine have been removed. An earlier fix added the `:Trashed` label primitive so every soft-delete caller (`memory-delete`, `contact-delete`, the admin `/graph` page's trash button) runs the same shared `trashNode` helper — relationships preserved, unique keys snapshotted into `_trashedKeys`, audit lines `[trash:marked] by=<surface>` for cross-cut provenance.
|
|
188
188
|
|
|
@@ -192,7 +192,7 @@ Graph hygiene is **agent-directed, case by case** — no autonomous rule engine,
|
|
|
192
192
|
|
|
193
193
|
## Brain-capture diagnostic — `memory-signals-recent`
|
|
194
194
|
|
|
195
|
-
Read-only window into the extractive graph writes produced under this account. Returns recent `:MENTIONS` edges (`:Message-[:MENTIONS]->Entity`) and `:Idea` nodes interleaved by `observedAt`. Default window is 24 hours; pass `windowHours` (max 720) and `limit` (max 200) to scan a longer arc. Use this when the operator asks "what has the brain captured today?", "show me recent mentions of X", or "what ideas did I leave on the table this week?". Writes are produced by `database-operator`, either inline at admin discretion or via the once-per-
|
|
195
|
+
Read-only window into the extractive graph writes produced under this account. Returns recent `:MENTIONS` edges (`:Message-[:MENTIONS]->Entity`) and `:Idea` nodes interleaved by `observedAt`. Default window is 24 hours; pass `windowHours` (max 720) and `limit` (max 200) to scan a longer arc. Use this when the operator asks "what has the brain captured today?", "show me recent mentions of X", or "what ideas did I leave on the table this week?". Writes are produced by `database-operator`, either inline at admin discretion or via the once-per-operator-prompt background pass (`platform/plugins/admin/hooks/pre-turn-graph-pass.sh`, Task 425 — supersedes the Task 412 Stop-hook variant of the same logic, which itself superseded the original `signal-detector` specialist + throttled hook from Task 303).
|
|
196
196
|
|
|
197
197
|
## Conversational Memory
|
|
198
198
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// `--source <enum>` selects the normaliser at the top of the pipeline:
|
|
10
10
|
//
|
|
11
11
|
// normalise → bind canonical senders → derive conversationIdentity
|
|
12
|
-
// → look up prior
|
|
12
|
+
// → look up prior :ConversationArchive (delta cursor)
|
|
13
13
|
// → sessionize delta at gap-hours boundaries
|
|
14
14
|
// → for each session: classify (mode='chat') → memoryIngest immediately
|
|
15
15
|
// → advance lastIngestedMessageHash + lastIngestedMessageAt
|
|
@@ -319,7 +319,7 @@ function findChatTxt(dir) {
|
|
|
319
319
|
// The title is computed once per ingest from inputs that are immutable for
|
|
320
320
|
// the conversation (source, accountId, sortedParticipantElementIds, archive
|
|
321
321
|
// dateRange) so re-ingests produce the same string. Memory-ingest writes it
|
|
322
|
-
// to the
|
|
322
|
+
// to the `:ConversationArchive.title` ON CREATE and COALESCE-on-MATCH so the UI
|
|
323
323
|
// (display-helpers.ts) reads a stable label without ever falling through to
|
|
324
324
|
// `summary`. Per-session counter strings stay in the progress file only.
|
|
325
325
|
//
|
|
@@ -555,7 +555,7 @@ async function main() {
|
|
|
555
555
|
|
|
556
556
|
// 6c. Verify participant elementIds resolve to graph nodes with the right
|
|
557
557
|
// accountId and labels. Per-line senderName binding is gone;
|
|
558
|
-
// operator-confirmed participants attach to the
|
|
558
|
+
// operator-confirmed participants attach to the :ConversationArchive parent.
|
|
559
559
|
let session = getSession();
|
|
560
560
|
try {
|
|
561
561
|
await verifyParticipantElementIds({
|
|
@@ -574,7 +574,7 @@ async function main() {
|
|
|
574
574
|
fail("argv", { reason: err instanceof Error ? err.message : String(err) });
|
|
575
575
|
}
|
|
576
576
|
|
|
577
|
-
// 6d. Derive conversationIdentity and look up the prior
|
|
577
|
+
// 6d. Derive conversationIdentity and look up the prior :ConversationArchive parent (Task 397).
|
|
578
578
|
// sub-scope B — under `--rebuild` we skip the prior-cursor lookup
|
|
579
579
|
// entirely; the run is treated as first-ingest, the first session cleans
|
|
580
580
|
// prior chunks (cleanupPriorChunks=true), and the cursor is overwritten
|
|
@@ -590,7 +590,7 @@ async function main() {
|
|
|
590
590
|
if (!rebuild) {
|
|
591
591
|
try {
|
|
592
592
|
const r = await session.run(
|
|
593
|
-
`MATCH (a:
|
|
593
|
+
`MATCH (a:ConversationArchive { conversationIdentity: $cid })
|
|
594
594
|
OPTIONAL MATCH (a)-[:HAS_SECTION]->(c:Section { archiveSha256: $sha })
|
|
595
595
|
RETURN elementId(a) AS elemId,
|
|
596
596
|
a.lastIngestedMessageHash AS lastHash,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# the same pipeline with `--source` selecting the normaliser.
|
|
10
10
|
#
|
|
11
11
|
# Pipeline: normalise (per source) → bind canonical sender set →
|
|
12
|
-
# derive conversationIdentity → look up prior
|
|
12
|
+
# derive conversationIdentity → look up prior :ConversationArchive (delta
|
|
13
13
|
# cursor) → sessionize delta at gap-hours boundary → for each session: classify
|
|
14
14
|
# via Haiku (mode='chat') + memory-ingest immediately (per-session checkpoint).
|
|
15
15
|
#
|
|
@@ -17,10 +17,10 @@ import { memoryArchiveWrite } from "./tools/memory-archive-write.js";
|
|
|
17
17
|
import { obsidianVaultImport, } from "./tools/obsidian-vault-import.js";
|
|
18
18
|
// whatsapp-export-{parse,preview,insight-write} deleted. Conversation
|
|
19
19
|
// transcripts now flow through the source-agnostic `conversation-archive` skill
|
|
20
|
-
// + `memory/bin/conversation-archive-ingest.sh`, writing :
|
|
21
|
-
// (keyed on `conversationIdentity`) + :Section chunks via
|
|
22
|
-
// writer dispatches on the presence of
|
|
23
|
-
// parent
|
|
20
|
+
// + `memory/bin/conversation-archive-ingest.sh`, writing :ConversationArchive
|
|
21
|
+
// (Task 397 — keyed on `conversationIdentity`) + :Section chunks via
|
|
22
|
+
// memory-ingest. The writer dispatches on the presence of
|
|
23
|
+
// `conversationIdentity`; the parent label tracks the dispatch.
|
|
24
24
|
// whatsapp-export-insight-pass deleted (Phase 2 deferred).
|
|
25
25
|
import { memoryIngestWeb } from "./tools/memory-ingest-web.js";
|
|
26
26
|
import { memoryClassify } from "./tools/memory-classify.js";
|
|
@@ -460,18 +460,18 @@ eagerTool(server, "memory-rank", "Retrieve entities from the knowledge graph and
|
|
|
460
460
|
});
|
|
461
461
|
// ---------------------------------------------------------------------------
|
|
462
462
|
// conversation-archive Phase 2: chunk-anchored insight derivation.
|
|
463
|
-
// Read-only tool. Walks the :Section chunks of one named conversation
|
|
464
|
-
// (`:
|
|
465
|
-
// Haiku (via OAuth) for the high-confidence claims.
|
|
466
|
-
// proposals with the cypher needed to wire them;
|
|
467
|
-
// enrich skill drives the per-row operator gate.
|
|
468
|
-
// writes; idempotency lives on the MERGE-key shape
|
|
469
|
-
// contentHash) encoded in each proposal's mergeCypher.
|
|
463
|
+
// Read-only tool. Walks the :Section chunks of one named conversation
|
|
464
|
+
// archive (`:ConversationArchive` keyed on `conversationIdentity` — Task
|
|
465
|
+
// 397) in pages and asks Haiku (via OAuth) for the high-confidence claims.
|
|
466
|
+
// Returns operator-facing proposals with the cypher needed to wire them;
|
|
467
|
+
// the conversation-archive-enrich skill drives the per-row operator gate.
|
|
468
|
+
// The tool itself NEVER writes; idempotency lives on the MERGE-key shape
|
|
469
|
+
// (chunkElementId, kind, contentHash) encoded in each proposal's mergeCypher.
|
|
470
470
|
// ---------------------------------------------------------------------------
|
|
471
|
-
server.tool("conversation-archive-derive-insights", "Walk the :Section chunks of one named conversation-
|
|
471
|
+
server.tool("conversation-archive-derive-insights", "Walk the :Section chunks of one named :ConversationArchive (conversation-archive walker) 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.", {
|
|
472
472
|
archiveElementId: z
|
|
473
473
|
.string()
|
|
474
|
-
.describe("elementId of the
|
|
474
|
+
.describe("elementId of the :ConversationArchive parent (Task 397 — keyed on conversationIdentity) the operator named (required)."),
|
|
475
475
|
chunkOffset: z
|
|
476
476
|
.number()
|
|
477
477
|
.int()
|
|
@@ -866,11 +866,11 @@ if (!readOnly) {
|
|
|
866
866
|
};
|
|
867
867
|
}
|
|
868
868
|
});
|
|
869
|
-
server.tool("memory-update", "Update properties on an existing node in the knowledge graph. Finds the node by element ID, verifies account ownership, sets the provided properties, and recomputes the embedding. For :Person / :Organization / :Concept / :Project / :LocalBusiness, pass rewriteCompiledTruth=true to recompute the admin compiledTruth summary; when the label is in the public-twin set (:Organization / :Concept / :Project / :LocalBusiness) and the admin pass changes the truth, the public-twin compiledTruthPublic auto-rewrites in the same call.
|
|
869
|
+
server.tool("memory-update", "Update properties on an existing node in the knowledge graph. Finds the node by element ID, verifies account ownership, sets the provided properties, and recomputes the embedding. For :Person / :Organization / :Concept / :Project / :LocalBusiness, pass rewriteCompiledTruth=true to recompute the admin compiledTruth summary; when the label is in the public-twin set (:Organization / :Concept / :Project / :LocalBusiness) and the admin pass changes the truth, the public-twin compiledTruthPublic auto-rewrites in the same call. Pair rewriteCompiledTruth=true with operatorHint to feed the rewriter an operator correction from chat for the admin twin — the resulting write is stamped compiledTruthSource='operator', the 7-day cooldown does not block it, and the public-twin auto-fire is skipped. Pair rewriteCompiledTruth=true with publicOperatorHint to do the same for the public twin — the public rewriter is invoked with the hint folded into the public-mode prompt regardless of whether the admin pass changed the truth, the public-cooldown is bypassed, and the write is stamped compiledTruthPublicSource='operator'. Direct writes to compiledTruth / compiledTruthUpdatedAt / compiledTruthSource / compiledTruthPublic / compiledTruthPublicUpdatedAt / compiledTruthPublicSource via the properties bag are refused — the rewriter is the only writer.", {
|
|
870
870
|
nodeId: z.string().describe("Element ID of the node to update (from memory-search results)"),
|
|
871
871
|
properties: z
|
|
872
872
|
.record(z.string(), z.unknown())
|
|
873
|
-
.describe("Properties to set or overwrite. Restricted keys (embedding, accountId, createdAt) are ignored. Compiled-truth fields are system-managed and rejected loudly here — use rewriteCompiledTruth
|
|
873
|
+
.describe("Properties to set or overwrite. Restricted keys (embedding, accountId, createdAt) are ignored. Compiled-truth fields are system-managed and rejected loudly here — use rewriteCompiledTruth (optionally with operatorHint or publicOperatorHint)."),
|
|
874
874
|
rewriteCompiledTruth: z
|
|
875
875
|
.boolean()
|
|
876
876
|
.optional()
|
|
@@ -879,11 +879,15 @@ if (!readOnly) {
|
|
|
879
879
|
.boolean()
|
|
880
880
|
.optional()
|
|
881
881
|
.describe("Bypass the 7-day cooldown that protects operator-edited compiledTruth and compiledTruthPublic fields from automatic rewriter passes. Set only when the operator explicitly asks for a refresh."),
|
|
882
|
-
|
|
882
|
+
operatorHint: z
|
|
883
883
|
.string()
|
|
884
884
|
.optional()
|
|
885
|
-
.describe("Operator
|
|
886
|
-
|
|
885
|
+
.describe("Operator correction for the admin twin, supplied via chat. Only valid with rewriteCompiledTruth=true. Folded into the rewriter prompt with precedence over the timeline; the resulting write is stamped compiledTruthSource='operator', the 7-day cooldown does not block this pass, and the public-twin auto-fire is skipped so admin-side corrections do not leak unreviewed to the public surface. Cap: 1000 characters."),
|
|
886
|
+
publicOperatorHint: z
|
|
887
|
+
.string()
|
|
888
|
+
.optional()
|
|
889
|
+
.describe("Operator correction for the public twin, supplied via chat. Only valid with rewriteCompiledTruth=true AND a public-twin label (:Organization / :Concept / :Project / :LocalBusiness — :Person has no public twin). Folded into the public-mode rewriter prompt with precedence over the timeline; the public rewriter fires regardless of whether the admin pass changed the truth, the public-cooldown is bypassed, and the write is stamped compiledTruthPublicSource='operator'. Cap: 1000 characters."),
|
|
890
|
+
}, async ({ nodeId, properties, rewriteCompiledTruth, forceRewrite, operatorHint, publicOperatorHint }) => {
|
|
887
891
|
if (!accountId)
|
|
888
892
|
return refuseNoAccount("memory-update");
|
|
889
893
|
try {
|
|
@@ -898,13 +902,15 @@ if (!readOnly) {
|
|
|
898
902
|
},
|
|
899
903
|
rewriteCompiledTruth,
|
|
900
904
|
forceRewrite,
|
|
901
|
-
|
|
905
|
+
operatorHint,
|
|
906
|
+
publicOperatorHint,
|
|
902
907
|
});
|
|
903
908
|
const rewriterLine = (() => {
|
|
904
909
|
if (!result.rewriter)
|
|
905
910
|
return "";
|
|
906
911
|
if (result.rewriter.kind === "ok") {
|
|
907
|
-
|
|
912
|
+
const sourceFragment = result.rewriter.source === "operator" ? " (operator-attributed)" : "";
|
|
913
|
+
return `\nAdmin compiled-truth rewritten${sourceFragment}: ${result.rewriter.charsBefore} → ${result.rewriter.charsAfter} chars.`;
|
|
908
914
|
}
|
|
909
915
|
if (result.rewriter.kind === "refused") {
|
|
910
916
|
return result.rewriter.reason === "operator-edit-cooldown"
|
|
@@ -920,7 +926,8 @@ if (!readOnly) {
|
|
|
920
926
|
if (!result.publicRewriter)
|
|
921
927
|
return "";
|
|
922
928
|
if (result.publicRewriter.kind === "ok") {
|
|
923
|
-
|
|
929
|
+
const sourceFragment = result.publicRewriter.source === "operator" ? " (operator-attributed)" : "";
|
|
930
|
+
return `\nPublic compiled-truth rewritten${sourceFragment}: ${result.publicRewriter.charsBefore} → ${result.publicRewriter.charsAfter} chars.`;
|
|
924
931
|
}
|
|
925
932
|
if (result.publicRewriter.kind === "refused") {
|
|
926
933
|
return result.publicRewriter.reason === "operator-edit-cooldown"
|
|
@@ -932,21 +939,11 @@ if (!readOnly) {
|
|
|
932
939
|
}
|
|
933
940
|
return `\nPublic rewriter LLM call failed (cause=${result.publicRewriter.cause}); compiledTruthPublic unchanged.`;
|
|
934
941
|
})();
|
|
935
|
-
const overrideLine = (() => {
|
|
936
|
-
if (!result.publicOverride)
|
|
937
|
-
return "";
|
|
938
|
-
if (result.publicOverride.kind === "applied") {
|
|
939
|
-
return `\ncompiledTruthPublic overridden by operator: ${result.publicOverride.chars} chars.`;
|
|
940
|
-
}
|
|
941
|
-
return result.publicOverride.reason === "wrong-label"
|
|
942
|
-
? `\ncompiledTruthPublic override refused: label is not in the public-twin set (:Organization / :Concept / :Project / :LocalBusiness).`
|
|
943
|
-
: `\ncompiledTruthPublic override refused: exceeds 2000 character cap.`;
|
|
944
|
-
})();
|
|
945
942
|
return {
|
|
946
943
|
content: [
|
|
947
944
|
{
|
|
948
945
|
type: "text",
|
|
949
|
-
text: `Node updated: [${result.labels.join(", ")}] (ID: ${result.nodeId})${rewriterLine}${publicRewriterLine}
|
|
946
|
+
text: `Node updated: [${result.labels.join(", ")}] (ID: ${result.nodeId})${rewriterLine}${publicRewriterLine}`,
|
|
950
947
|
},
|
|
951
948
|
],
|
|
952
949
|
};
|
|
@@ -1256,7 +1253,7 @@ if (!readOnly) {
|
|
|
1256
1253
|
"raw Cypher. Use ONLY for flat first-class entity exports (LinkedIn Connections.csv and " +
|
|
1257
1254
|
"future CRM-type seeds). Conversation transcripts (WhatsApp, Telegram, Signal, …) flow " +
|
|
1258
1255
|
"through the `conversation-archive` skill (memory/bin/conversation-archive-ingest.sh) " +
|
|
1259
|
-
"instead, which writes :
|
|
1256
|
+
"instead, which writes :ConversationArchive (Task 397 — keyed on conversationIdentity) + :Section chunks via memory-ingest. " +
|
|
1260
1257
|
"Currently supported archiveType values: `linkedin-connections`, `obsidian-vault` (invoked indirectly by the obsidian-import skill via the obsidian-vault-import tool).", {
|
|
1261
1258
|
archiveType: z
|
|
1262
1259
|
.enum(["linkedin-connections", "obsidian-vault"])
|
|
@@ -2348,6 +2345,11 @@ server.tool("memory-typed-edge-pass", "Run the typed-edge auto-extraction pass o
|
|
|
2348
2345
|
return refuseNoAccount("memory-typed-edge-pass");
|
|
2349
2346
|
const dbSession = getSession();
|
|
2350
2347
|
try {
|
|
2348
|
+
// Cast: the McpServer instance is `any`-typed at the module level (see
|
|
2349
|
+
// file-top comment on TS2589), so `args` infers as the loose
|
|
2350
|
+
// ShapeOutput<ZodRawShapeCompat> instead of the Zod schema above.
|
|
2351
|
+
// Runtime shape is enforced by the schema; this re-asserts the typed
|
|
2352
|
+
// contract at the consumer boundary. Task 404.
|
|
2351
2353
|
const result = await memoryTypedEdgePass(args, dbSession, accountId);
|
|
2352
2354
|
return {
|
|
2353
2355
|
content: [{
|