@rubytech/create-maxy-code 0.1.132 → 0.1.140
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/dist/__tests__/installer-specialist-registration.test.js +5 -40
- package/dist/index.js +24 -28
- package/dist/specialist-registration.js +0 -15
- package/package.json +1 -1
- package/payload/platform/config/brand.json +1 -1
- package/payload/platform/lib/graph-search/dist/boosts.d.ts +39 -0
- package/payload/platform/lib/graph-search/dist/boosts.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/boosts.js +57 -0
- package/payload/platform/lib/graph-search/dist/boosts.js.map +1 -0
- package/payload/platform/lib/graph-search/dist/dedup.d.ts +29 -0
- package/payload/platform/lib/graph-search/dist/dedup.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/dedup.js +97 -0
- package/payload/platform/lib/graph-search/dist/dedup.js.map +1 -0
- package/payload/platform/lib/graph-search/dist/index.d.ts +70 -8
- package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-search/dist/index.js +323 -62
- package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-search/dist/query-expansion.d.ts +37 -0
- package/payload/platform/lib/graph-search/dist/query-expansion.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/query-expansion.js +101 -0
- package/payload/platform/lib/graph-search/dist/query-expansion.js.map +1 -0
- package/payload/platform/lib/graph-search/dist/route.d.ts +29 -0
- package/payload/platform/lib/graph-search/dist/route.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/route.js +53 -0
- package/payload/platform/lib/graph-search/dist/route.js.map +1 -0
- package/payload/platform/lib/graph-search/dist/rrf-fusion.d.ts +31 -0
- package/payload/platform/lib/graph-search/dist/rrf-fusion.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/rrf-fusion.js +57 -0
- package/payload/platform/lib/graph-search/dist/rrf-fusion.js.map +1 -0
- package/payload/platform/lib/graph-search/src/__tests__/boosts.test.ts +59 -0
- package/payload/platform/lib/graph-search/src/__tests__/dedup.test.ts +83 -0
- package/payload/platform/lib/graph-search/src/__tests__/query-expansion.test.ts +96 -0
- package/payload/platform/lib/graph-search/src/__tests__/route.test.ts +62 -0
- package/payload/platform/lib/graph-search/src/__tests__/rrf-fusion.test.ts +39 -0
- package/payload/platform/lib/graph-search/src/boosts.ts +61 -0
- package/payload/platform/lib/graph-search/src/dedup.ts +108 -0
- package/payload/platform/lib/graph-search/src/index.ts +407 -65
- package/payload/platform/lib/graph-search/src/query-expansion.ts +129 -0
- package/payload/platform/lib/graph-search/src/route.ts +70 -0
- package/payload/platform/lib/graph-search/src/rrf-fusion.ts +62 -0
- package/payload/platform/lib/obsidian-parser/dist/index.d.ts +98 -0
- package/payload/platform/lib/obsidian-parser/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/obsidian-parser/dist/index.js +480 -0
- package/payload/platform/lib/obsidian-parser/dist/index.js.map +1 -0
- package/payload/platform/lib/obsidian-parser/src/index.ts +572 -0
- package/payload/platform/lib/obsidian-parser/tsconfig.json +8 -0
- package/payload/platform/neo4j/schema.cypher +86 -1
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/.claude-plugin/marketplace.json +10 -0
- package/payload/platform/plugins/admin/hooks/session-end-retrospective.sh +4 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js +6 -4
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/docs/references/internals.md +36 -10
- package/payload/platform/plugins/docs/references/neo4j.md +63 -0
- package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
- package/payload/platform/plugins/docs/references/session-retrospective.md +11 -4
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +2 -2
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +3 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +203 -7
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.js +25 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js +70 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.js +50 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts +33 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js +99 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +21 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +24 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts +13 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js +151 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts +55 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js +290 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts +19 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js +102 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js +18 -1
- 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-emit.test.js +5 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +64 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +258 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.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 +14 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts +29 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js +22 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +31 -1
- 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 +182 -50
- 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 +76 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/obsidian-vault-import.d.ts +127 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/obsidian-vault-import.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/obsidian-vault-import.js +477 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/obsidian-vault-import.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts +30 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js +129 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/scripts/backfill-typed-edges.ts +72 -0
- package/payload/platform/plugins/memory/mcp/scripts/generate-edge-docs.ts +75 -0
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +5 -0
- package/payload/platform/plugins/memory/references/schema-base.md +44 -0
- package/payload/platform/plugins/notion-import/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/notion-import/PLUGIN.md +27 -0
- package/payload/platform/plugins/notion-import/skills/notion-import/SKILL.md +110 -0
- package/payload/platform/plugins/notion-import/skills/notion-import/references/attachments.md +55 -0
- package/payload/platform/plugins/notion-import/skills/notion-import/references/databases.md +81 -0
- package/payload/platform/plugins/notion-import/skills/notion-import/references/page-tree.md +61 -0
- package/payload/platform/plugins/notion-import/skills/notion-import/references/workspace-export.md +41 -0
- package/payload/platform/plugins/obsidian-import/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/obsidian-import/PLUGIN.md +39 -0
- package/payload/platform/plugins/obsidian-import/skills/obsidian-import/SKILL.md +92 -0
- package/payload/platform/plugins/obsidian-import/skills/obsidian-import/references/attachments.md +80 -0
- package/payload/platform/plugins/obsidian-import/skills/obsidian-import/references/daily-notes.md +31 -0
- package/payload/platform/plugins/obsidian-import/skills/obsidian-import/references/vault-structure.md +46 -0
- package/payload/platform/plugins/obsidian-import/skills/obsidian-import/references/wikilinks.md +70 -0
- package/payload/platform/scripts/seed-neo4j.sh +49 -0
- package/payload/platform/templates/agents/admin/IDENTITY.md +4 -3
- package/payload/platform/templates/specialists/agents/database-operator.md +21 -1
- package/payload/server/{adminuser-self-heal-QAWOZ3JV.js → adminuser-self-heal-YC47O34W.js} +2 -1
- package/payload/server/{chunk-JSBRDJBE.js → chunk-HYQNUVGO.js} +9 -1
- package/payload/server/{chunk-HCYM5FLU.js → chunk-T3DJD5QR.js} +3 -1
- package/payload/server/maxy-edge.js +3 -2
- package/payload/server/public/assets/{admin-FcRHAL-3.js → admin-BM9aXUUh.js} +1 -1
- package/payload/server/public/assets/{data-Ds37mflX.js → data-Ds2ECtfP.js} +1 -1
- package/payload/server/public/assets/{graph-CmWRhaiS.js → graph-BzxObKMM.js} +1 -1
- package/payload/server/public/assets/{graph-labels-Ch2r00Gt.js → graph-labels-Dwgep_nr.js} +1 -1
- package/payload/server/public/assets/{page-BcHhJXUt.js → page-BlkBsjP2.js} +1 -1
- package/payload/server/public/assets/page-h_SA5SVQ.js +51 -0
- 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 +803 -76
- package/payload/server/public/assets/page-BOtNny_4.js +0 -51
|
@@ -11,20 +11,29 @@ Use this reference when assessing capabilities, diagnosing retrieval behaviour,
|
|
|
11
11
|
Every knowledge query flows through a hybrid search pipeline that combines semantic similarity with keyword matching, applies layered access controls, expands results via graph traversal, and optionally re-ranks via LLM reasoning.
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
QUERY
|
|
14
|
+
QUERY ── (retrievalClass from Task 304 gateway-classifier)
|
|
15
15
|
│
|
|
16
|
-
├──
|
|
17
|
-
│
|
|
18
|
-
|
|
19
|
-
│
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
├── EXPAND (Haiku — 3-5 paraphrases, 1h cache) [flag: MAXY_GS_EXPANSION]
|
|
17
|
+
│
|
|
18
|
+
├── ROUTE (per-class label filter + fusion weights) [flag: MAXY_GS_ROUTE]
|
|
19
|
+
│
|
|
20
|
+
├── For each query ────► EMBED ──► VECTOR SEARCH ──┐
|
|
21
|
+
│ ├─► FUSE (weighted-sum or RRF) [flag: MAXY_GS_RRF]
|
|
22
|
+
│ └────► BM25 FULL-TEXT ──────────┘
|
|
23
|
+
│ (entity_search — universal coverage)
|
|
24
|
+
│
|
|
25
|
+
├── BOOST (compiledTruth +15%, backlinks log 5-25%) [flag: MAXY_GS_BOOSTS]
|
|
26
|
+
├── DEDUP (4 layers: nodeId, slug, canonicalName, hash) [flag: MAXY_GS_DEDUP]
|
|
27
|
+
├── THRESHOLD + SORT + SLICE
|
|
28
|
+
└── GRAPH EXPAND ──► RESULTS
|
|
29
|
+
|
|
30
|
+
Fusion (default / weighted-sum): combined = 0.7 × vector + 0.3 × bm25_norm
|
|
31
|
+
Fusion (RRF): score = Σ 1 / (60 + rank_i) across ranked lists
|
|
25
32
|
Fallback: if the full-text index doesn't exist, vector-only results are returned (graceful degradation, no error).
|
|
26
33
|
```
|
|
27
34
|
|
|
35
|
+
Each Task 308 enhancement is independently flagged. All flags default OFF — the unflagged pipeline is identical to the baseline weighted-sum + nodeId-only-dedup behaviour. Tasks 305 (typed-edge backlinks) and 306 (compiledTruth property) have landed, so the boost data is populated; flag activation, soak windows, and per-flag measurement live under Task 337.
|
|
36
|
+
|
|
28
37
|
### Hybrid Search Detail
|
|
29
38
|
|
|
30
39
|
**Vector path:** The query is embedded via Ollama (model per `EMBED_MODEL` env var, default `nomic-embed-text`). The resulting vector is compared against Neo4j's HNSW cosine indexes — one per indexed label. Dimensions are configured at install time (default 768). The search runs against all discovered indexes (or a subset if the caller specifies label filters). Scores are in [0, 1] (cosine similarity).
|
|
@@ -33,6 +42,23 @@ Fallback: if the full-text index doesn't exist, vector-only results are returned
|
|
|
33
42
|
|
|
34
43
|
**Merge:** Results from both paths are collected in a single map keyed by `nodeId`. A node appearing in both paths accumulates the max vector score and max BM25 score independently. The combined score is `0.7 * vectorScore + 0.3 * bm25Score`. Results are sorted descending by combined score, then sliced to the requested limit (default 10).
|
|
35
44
|
|
|
45
|
+
### Task 308 enhancements (flagged, default off)
|
|
46
|
+
|
|
47
|
+
| Stage | Module | Flag | What it does |
|
|
48
|
+
|---|---|---|---|
|
|
49
|
+
| Routing | `route.ts` | `MAXY_GS_ROUTE` | Picks per-class label filter + fusion weights from the `retrievalClass` hint produced by Task 304's gateway-classifier. `entity` → vector-heavy + Person/Company/Concept; `temporal` → BM25-heavy over Event; `event` → BM25-only over Event; `general` → balanced; `none` → skip the lookup. |
|
|
50
|
+
| Multi-query expansion | `query-expansion.ts` | `MAXY_GS_EXPANSION` | Haiku generates 3-5 paraphrases per query; each runs through vector + BM25 in parallel, with results unioned before fusion. Per-call 1-hour cache keyed by (accountId, query, retrievalClass). Graceful degrade on Haiku failure — original query only. |
|
|
51
|
+
| RRF fusion | `rrf-fusion.ts` | `MAXY_GS_RRF` | Replaces weighted-sum with Reciprocal Rank Fusion (k=60 by default). Sums `1 / (k + rank)` per node across the ranked lists each pass produces. More robust to score-distribution drift between indexes than weighted-sum. Weighted-sum stays as the fallback. |
|
|
52
|
+
| compiledTruth boost | `boosts.ts` | `MAXY_GS_BOOSTS` | +15% to the combined score of any hit whose node carries a non-null `compiledTruth` property (populated by Task 306 on Person/Company/Concept). The property is in the `entity_search` index so BM25 hits against summary text are also matched. |
|
|
53
|
+
| Backlink boost | `boosts.ts` | `MAXY_GS_BOOSTS` | `bump = clamp(0.05 + 0.05 × log10(backlinkCount), 0.05, 0.25)`. 1 backlink → +5%; 10 → +10%; 100 → +15%; 1000+ → +20%; capped at +25%. Reads `backlinkCount` populated by Task 305's typed-edge hook. |
|
|
54
|
+
| 4-layer dedup | `dedup.ts` | `MAXY_GS_DEDUP` | Strict superset of nodeId-only dedup. Layers: `nodeId`, `slug`, `canonicalName` (case-insensitive, falls back to `name`), `contentHash` (sha256 of `compiledTruth || content`). Highest-score representative wins per collision class. Missing keys skip the layer, no false collision. |
|
|
55
|
+
|
|
56
|
+
A per-call log line lets the operator see which stages ran with which counts:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
[graph-search:hybrid] accountId=<8c> retrievalClass=<c> expansions=<n> vector=<n> bm25=<n> fused=<n> boosted=<n> deduped=<n> final=<n> mode=<hybrid|rrf|bm25> ms=<ms> expand-ms=<ms>
|
|
60
|
+
```
|
|
61
|
+
|
|
36
62
|
### What the hybrid approach catches
|
|
37
63
|
|
|
38
64
|
Vector search excels at semantic meaning — "how do I contact someone" finds nodes about communication even if the word "contact" doesn't appear. BM25 excels at exact terms — invoice numbers, product codes, proper nouns, technical identifiers. The hybrid combination ensures both modes contribute, with semantic similarity weighted higher (0.7) because most user queries are natural language.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Neo4j edge types — operator reference
|
|
2
|
+
|
|
3
|
+
How Maxy's graph wires itself.
|
|
4
|
+
|
|
5
|
+
## Typed-edge auto-extraction
|
|
6
|
+
|
|
7
|
+
At the end of every admin session (`/end`, `/archive`, `end session`, `archive this session`), the admin agent delegates one final pass to the `database-operator` specialist. That pass reads every prose-bearing node your account wrote since the last completed retrospective — messages, meetings, notes, pages, posts, reports, emails, ideas — and asks Claude Haiku to propose typed edges from the text. Only edges that match a closed allowlist of `(sourceLabel, EDGE_TYPE, targetLabel)` shapes are MERGEd into the graph. The graph wires itself; you do not have to ask for it.
|
|
8
|
+
|
|
9
|
+
If a session closes without one of the four typed end-intent tokens (Sidebar Archive, tab-close, power loss), the pass for that session is deferred — its prose nodes land at the next `/end`. Nothing is lost, just delayed by one session.
|
|
10
|
+
|
|
11
|
+
## Typed-edge allowlist
|
|
12
|
+
|
|
13
|
+
<!-- TYPED-EDGE-TABLE:START -->
|
|
14
|
+
|
|
15
|
+
<!-- Generated by platform/plugins/memory/mcp/scripts/generate-edge-docs.ts from TYPED_EDGE_ALLOWLIST. Do not edit by hand. -->
|
|
16
|
+
|
|
17
|
+
| Source label | Edge type | Target label |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| Person | ATTENDED | Event |
|
|
20
|
+
| Person | ATTENDED | Meeting |
|
|
21
|
+
| Person | WORKS_AT | Organization |
|
|
22
|
+
| Person | WORKS_AT | LocalBusiness |
|
|
23
|
+
| Person | INVESTED_IN | Organization |
|
|
24
|
+
| Person | INVESTED_IN | LocalBusiness |
|
|
25
|
+
| Organization | INVESTED_IN | Organization |
|
|
26
|
+
| Organization | INVESTED_IN | LocalBusiness |
|
|
27
|
+
| Person | FOUNDED | Organization |
|
|
28
|
+
| Person | FOUNDED | LocalBusiness |
|
|
29
|
+
| Person | ADVISES | Organization |
|
|
30
|
+
| Person | ADVISES | LocalBusiness |
|
|
31
|
+
| Person | ADVISES | Person |
|
|
32
|
+
| Message | MENTIONS | Person |
|
|
33
|
+
| Message | MENTIONS | Organization |
|
|
34
|
+
| Message | MENTIONS | LocalBusiness |
|
|
35
|
+
| Message | MENTIONS | Event |
|
|
36
|
+
| Page | MENTIONS | Person |
|
|
37
|
+
| Page | MENTIONS | Organization |
|
|
38
|
+
| Page | MENTIONS | LocalBusiness |
|
|
39
|
+
| Page | MENTIONS | Event |
|
|
40
|
+
| Meeting | MENTIONS | Person |
|
|
41
|
+
| Meeting | MENTIONS | Organization |
|
|
42
|
+
| Email | MENTIONS | Person |
|
|
43
|
+
| Email | MENTIONS | Organization |
|
|
44
|
+
| Note | MENTIONS | Person |
|
|
45
|
+
| Note | MENTIONS | Organization |
|
|
46
|
+
| Idea | MENTIONS | Person |
|
|
47
|
+
| Idea | MENTIONS | Organization |
|
|
48
|
+
| Post | MENTIONS | Person |
|
|
49
|
+
| Post | MENTIONS | Organization |
|
|
50
|
+
| Report | MENTIONS | Person |
|
|
51
|
+
| Report | MENTIONS | Organization |
|
|
52
|
+
| Person | AUTHORED | Post |
|
|
53
|
+
| Person | AUTHORED | Report |
|
|
54
|
+
| Person | AUTHORED | Page |
|
|
55
|
+
| Person | AUTHORED | Note |
|
|
56
|
+
| Email | ATTACHED_TO | Meeting |
|
|
57
|
+
| Page | ATTACHED_TO | Project |
|
|
58
|
+
| Note | ATTACHED_TO | Project |
|
|
59
|
+
| Page | REFERENCES | Page |
|
|
60
|
+
| Report | REFERENCES | Report |
|
|
61
|
+
| Report | REFERENCES | Page |
|
|
62
|
+
|
|
63
|
+
<!-- TYPED-EDGE-TABLE:END -->
|
|
@@ -38,6 +38,8 @@ These are enabled during onboarding and can be added or removed at any time. Som
|
|
|
38
38
|
| `whatsapp` | WhatsApp messaging, pairing, and conversation browsing | Personal assistant |
|
|
39
39
|
| `replicate` | Image generation — three models for photorealistic, design, and fast draft images | Content producer, Research assistant |
|
|
40
40
|
| `linkedin-import` | Import a LinkedIn Basic Data Export — Profile and Connections today, more CSVs as references land | Database operator |
|
|
41
|
+
| `notion-import` | Import a Notion workspace export (markdown + CSV) — pages, databases, hierarchy, attachments, schema-bounded relations, `@person` mentions account-filtered | Database operator |
|
|
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 |
|
|
41
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 |
|
|
42
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 |
|
|
43
45
|
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
# Session-end retrospective
|
|
2
2
|
|
|
3
|
-
When you end an admin session by typing `/end`, `/archive`, `end session`, or `archive this session`, the admin agent runs one extra turn before the session closes. It walks the session and writes down
|
|
3
|
+
When you end an admin session by typing `/end`, `/archive`, `end session`, or `archive this session`, the admin agent runs one extra turn before the session closes. It walks the session and writes down four kinds of thing that would otherwise be lost:
|
|
4
4
|
|
|
5
5
|
- corrections and learnings you gave the agent during the session,
|
|
6
6
|
- tonal and working-style preferences worth carrying forward,
|
|
7
|
-
- people, decisions, commitments, or business facts that came up but were not yet saved to the graph
|
|
7
|
+
- people, decisions, commitments, or business facts that came up but were not yet saved to the graph,
|
|
8
|
+
- typed edges between any new prose-bearing nodes (messages, meetings, notes, pages) and the entities they mention — the auto-extraction pass that "wires the graph" so future questions can hop from a person to the companies they founded to the events they attended.
|
|
8
9
|
|
|
9
10
|
The next session starts against an up-to-date picture of you and your business — not just the parts that got recorded mid-flow.
|
|
10
11
|
|
|
11
|
-
The extra turn is one or two messages from the agent, then a short summary of what was written. It runs inside the same session you were already in; nothing happens in the background, no second session is spawned.
|
|
12
|
+
The extra turn is one or two messages from the agent, then a short summary of what was written. It runs inside the same session you were already in; nothing happens in the background, no second session is spawned. The typed-edge pass itself is delegated to the `database-operator` specialist so the writes land where graph writes are supposed to live.
|
|
12
13
|
|
|
13
14
|
## When the retrospective is skipped
|
|
14
15
|
|
|
15
16
|
- Clicking the Archive button in the Sidebar closes the session directly and skips the retrospective. The four typed phrases above are the only signals that trigger it.
|
|
16
17
|
- Closing the browser tab or losing power closes the session without the retrospective. The mid-session recording route (the agent writes facts as they come up) is the primary safety net; the retrospective is the catch-net for what slips through.
|
|
17
18
|
|
|
19
|
+
Skipped sessions do not lose typed-edge work. The next session's retrospective picks up any prose nodes the previous session wrote, because the pass is scoped by "what changed since the last completed retrospective" — not by which session wrote it. The cost of skipping is one session of delay, not lost extraction.
|
|
20
|
+
|
|
21
|
+
## Skip-rate visibility
|
|
22
|
+
|
|
23
|
+
At the start of every admin session, the agent checks how many of your recent sessions closed without firing the retrospective. If any did, it surfaces one line — for example, "the last 10 sessions: 4 ended without a retrospective; their typed edges land at the next `/end`." This is informational, not nagging — it lets you choose to end the next session properly so the deferred extraction lands sooner.
|
|
24
|
+
|
|
18
25
|
## What you see if the agent tries to skip it
|
|
19
26
|
|
|
20
|
-
If the agent replies with a prose summary of "what it learned" without actually performing the retrospective, the session does not close — the Stop gate blocks until the agent calls the deterministic completion tool. You can tell it happened correctly by the final reply naming the counts (
|
|
27
|
+
If the agent replies with a prose summary of "what it learned" without actually performing the retrospective, the session does not close — the Stop gate blocks until the agent calls the deterministic completion tool. You can tell it happened correctly by the final reply naming the five counts (learnings, tonal observations, graph updates, typed edges accepted, prose nodes scanned); the absence of those counts is the signal that something went wrong.
|
|
@@ -24,9 +24,9 @@ import { getSession } from "../lib/neo4j.js";
|
|
|
24
24
|
import { layoutGraph } from "../render/layout.js";
|
|
25
25
|
import { drawGraph } from "../render/draw.js";
|
|
26
26
|
function getAccountsDir() {
|
|
27
|
-
const root = process.env.MAXY_PLATFORM_ROOT;
|
|
27
|
+
const root = process.env.PLATFORM_ROOT ?? process.env.MAXY_PLATFORM_ROOT;
|
|
28
28
|
if (!root) {
|
|
29
|
-
throw new Error("[graph-render]
|
|
29
|
+
throw new Error("[graph-render] PLATFORM_ROOT not set (also checked MAXY_PLATFORM_ROOT) — cannot resolve account output dir.");
|
|
30
30
|
}
|
|
31
31
|
return resolve(root, "..", "data/accounts");
|
|
32
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-render.js","sourceRoot":"","sources":["../../src/tools/graph-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAwC,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,SAAS,cAAc;IACrB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"graph-render.js","sourceRoot":"","sources":["../../src/tools/graph-render.ts"],"names":[],"mappings":"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"}
|
|
@@ -56,6 +56,9 @@ tools:
|
|
|
56
56
|
- name: memory-archive-write
|
|
57
57
|
publicAllowlist: false
|
|
58
58
|
adminAllowlist: false
|
|
59
|
+
- name: obsidian-vault-import
|
|
60
|
+
publicAllowlist: false
|
|
61
|
+
adminAllowlist: false
|
|
59
62
|
- name: conversation-archive-derive-insights
|
|
60
63
|
publicAllowlist: false
|
|
61
64
|
adminAllowlist: false
|
|
@@ -14,6 +14,7 @@ import { memoryReindex } from "./tools/memory-reindex.js";
|
|
|
14
14
|
import { memoryIngestExtract } from "./tools/memory-ingest-extract.js";
|
|
15
15
|
import { memoryIngest } from "./tools/memory-ingest.js";
|
|
16
16
|
import { memoryArchiveWrite } from "./tools/memory-archive-write.js";
|
|
17
|
+
import { obsidianVaultImport, } from "./tools/obsidian-vault-import.js";
|
|
17
18
|
// whatsapp-export-{parse,preview,insight-write} deleted. Conversation
|
|
18
19
|
// transcripts now flow through the source-agnostic `conversation-archive` skill
|
|
19
20
|
// + `memory/bin/conversation-archive-ingest.sh`, writing :ConversationArchive
|
|
@@ -41,6 +42,8 @@ import { conversationMemoryExpunge } from "./tools/conversation-memory-expunge.j
|
|
|
41
42
|
import { conversationArchiveDeriveInsights, } from "./tools/conversation-archive-derive-insights.js";
|
|
42
43
|
import { conversationArchiveEnrichRejection } from "./tools/conversation-archive-enrich-rejection.js";
|
|
43
44
|
import { fetchImageAsContent } from "./tools/image-fetch.js";
|
|
45
|
+
import { memoryTypedEdgePass } from "./tools/memory-typed-edge-pass.js";
|
|
46
|
+
import { sessionRetrospectiveSkipRate } from "./tools/session-retrospective-skip-rate.js";
|
|
44
47
|
import { getSession, closeDriver } from "./lib/neo4j.js";
|
|
45
48
|
import { embed } from "./lib/embeddings.js";
|
|
46
49
|
import { notTrashed } from "../../../../lib/graph-trash/dist/index.js";
|
|
@@ -205,7 +208,11 @@ eagerTool(server, "memory-search", "Search the knowledge graph using semantic ve
|
|
|
205
208
|
.array(z.string())
|
|
206
209
|
.optional()
|
|
207
210
|
.describe("Narrow the response `properties` to these keys per result. Omit to return all properties (default). Empty array returns no properties (explicit 'none'). Unknown keys are silently skipped; nodes lacking a requested key omit it on that row. Use for known-shape lookups (slug → name) to avoid shipping bios, notes, source URLs on every turn. Ranking is unaffected — hybrid() sees the full text either way. See `references/graph-primitives.md` § Projecting fields."),
|
|
208
|
-
|
|
211
|
+
retrievalClass: z
|
|
212
|
+
.enum(["entity", "temporal", "event", "general", "none"])
|
|
213
|
+
.optional()
|
|
214
|
+
.describe("Hint that selects the per-class index mix (route-hint contract). Pass 'entity' for who/what-is-this lookups (vector-heavy, Person|Company|Concept filter), 'temporal' for when-did-this-happen (BM25-heavy over :Event with Timeline), 'event' for pure event listings, 'none' to skip graph lookup entirely (greetings/instructions). Omit or pass 'general' for the balanced default. Routing only takes effect when MAXY_GS_ROUTE is enabled; otherwise the value is recorded in the log line but does not change index selection."),
|
|
215
|
+
}, async ({ query, labels, limit, expandHops, keywords, keywordMatch, agentSlug: toolAgentSlug, fields, retrievalClass }) => {
|
|
209
216
|
if (!accountId)
|
|
210
217
|
return refuseNoAccount("memory-search");
|
|
211
218
|
// Env var takes precedence over tool param (security: public agent server-level enforcement)
|
|
@@ -223,6 +230,7 @@ eagerTool(server, "memory-search", "Search the knowledge graph using semantic ve
|
|
|
223
230
|
agentSlug: effectiveAgentSlug,
|
|
224
231
|
keywordSubscriptions: envKeywordSubscriptions,
|
|
225
232
|
fields,
|
|
233
|
+
retrievalClass,
|
|
226
234
|
});
|
|
227
235
|
if (results.length === 0) {
|
|
228
236
|
return {
|
|
@@ -633,12 +641,20 @@ if (!readOnly) {
|
|
|
633
641
|
};
|
|
634
642
|
}
|
|
635
643
|
});
|
|
636
|
-
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.", {
|
|
644
|
+
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, pass rewriteCompiledTruth=true to recompute the compiled-truth summary from the most recent :TimelineEvent rows; direct writes to compiledTruth / compiledTruthUpdatedAt / compiledTruthSource are refused.", {
|
|
637
645
|
nodeId: z.string().describe("Element ID of the node to update (from memory-search results)"),
|
|
638
646
|
properties: z
|
|
639
647
|
.record(z.string(), z.unknown())
|
|
640
|
-
.describe("Properties to set or overwrite. Restricted keys (embedding, accountId, createdAt) are ignored."),
|
|
641
|
-
|
|
648
|
+
.describe("Properties to set or overwrite. Restricted keys (embedding, accountId, createdAt) are ignored. compiledTruth / compiledTruthUpdatedAt / compiledTruthSource are system-managed and rejected loudly — use rewriteCompiledTruth instead."),
|
|
649
|
+
rewriteCompiledTruth: z
|
|
650
|
+
.boolean()
|
|
651
|
+
.optional()
|
|
652
|
+
.describe("When true, run the compiled-truth rewriter against the node's most recent :TimelineEvent rows and set the result on compiledTruth. Only valid on :Person / :Organization / :Concept."),
|
|
653
|
+
forceRewrite: z
|
|
654
|
+
.boolean()
|
|
655
|
+
.optional()
|
|
656
|
+
.describe("Bypass the 7-day cooldown that protects an operator-edited compiledTruth from automatic rewriter passes. Set only when the operator explicitly asks for a refresh."),
|
|
657
|
+
}, async ({ nodeId, properties, rewriteCompiledTruth, forceRewrite }) => {
|
|
642
658
|
if (!accountId)
|
|
643
659
|
return refuseNoAccount("memory-update");
|
|
644
660
|
try {
|
|
@@ -651,12 +667,30 @@ if (!readOnly) {
|
|
|
651
667
|
session: resolveSessionId(),
|
|
652
668
|
tool: "memory-update",
|
|
653
669
|
},
|
|
670
|
+
rewriteCompiledTruth,
|
|
671
|
+
forceRewrite,
|
|
654
672
|
});
|
|
673
|
+
const rewriterLine = (() => {
|
|
674
|
+
if (!result.rewriter)
|
|
675
|
+
return "";
|
|
676
|
+
if (result.rewriter.kind === "ok") {
|
|
677
|
+
return `\nCompiled-truth rewritten: ${result.rewriter.charsBefore} → ${result.rewriter.charsAfter} chars.`;
|
|
678
|
+
}
|
|
679
|
+
if (result.rewriter.kind === "refused") {
|
|
680
|
+
return result.rewriter.reason === "operator-edit-cooldown"
|
|
681
|
+
? `\nRewriter refused: operator-edit cooldown (${result.rewriter.daysRemaining} day(s) remaining; pass forceRewrite=true to override).`
|
|
682
|
+
: `\nRewriter refused: no :TimelineEvent rows linked to this node.`;
|
|
683
|
+
}
|
|
684
|
+
if (result.rewriter.kind === "too-long") {
|
|
685
|
+
return `\nRewriter output exceeded ${result.rewriter.attemptedLen} chars after one retry; compiledTruth unchanged.`;
|
|
686
|
+
}
|
|
687
|
+
return `\nRewriter LLM call failed (cause=${result.rewriter.cause}); compiledTruth unchanged.`;
|
|
688
|
+
})();
|
|
655
689
|
return {
|
|
656
690
|
content: [
|
|
657
691
|
{
|
|
658
692
|
type: "text",
|
|
659
|
-
text: `Node updated: [${result.labels.join(", ")}] (ID: ${result.nodeId})`,
|
|
693
|
+
text: `Node updated: [${result.labels.join(", ")}] (ID: ${result.nodeId})${rewriterLine}`,
|
|
660
694
|
},
|
|
661
695
|
],
|
|
662
696
|
};
|
|
@@ -967,9 +1001,9 @@ if (!readOnly) {
|
|
|
967
1001
|
"future CRM-type seeds). Conversation transcripts (WhatsApp, Telegram, Signal, …) flow " +
|
|
968
1002
|
"through the `conversation-archive` skill (memory/bin/conversation-archive-ingest.sh) " +
|
|
969
1003
|
"instead, which writes :ConversationArchive + :Section:Conversation chunks via memory-ingest. " +
|
|
970
|
-
"Currently supported archiveType values: `linkedin-connections
|
|
1004
|
+
"Currently supported archiveType values: `linkedin-connections`, `obsidian-vault` (invoked indirectly by the obsidian-import skill via the obsidian-vault-import tool).", {
|
|
971
1005
|
archiveType: z
|
|
972
|
-
.enum(["linkedin-connections"])
|
|
1006
|
+
.enum(["linkedin-connections", "obsidian-vault"])
|
|
973
1007
|
.describe("Discriminant naming the per-source schema and Cypher body the server runs. Add a new value here only when the corresponding handler is added in memory-archive-write.ts."),
|
|
974
1008
|
ownerNodeId: z
|
|
975
1009
|
.string()
|
|
@@ -1011,6 +1045,92 @@ if (!readOnly) {
|
|
|
1011
1045
|
};
|
|
1012
1046
|
}
|
|
1013
1047
|
});
|
|
1048
|
+
eagerTool(server, "obsidian-vault-import", "Two-phase server-side ingestion for an extracted Obsidian vault directory. " +
|
|
1049
|
+
"Phase 1 (`mode='dry-run'`) walks the vault, parses pages/wikilinks/tags/attachments, " +
|
|
1050
|
+
"classifies wikilinks against intra-vault pages + existing graph entities (fuzzy match), " +
|
|
1051
|
+
"and returns a plan with any ambiguities the operator must disambiguate. " +
|
|
1052
|
+
"Phase 2 (`mode='commit'`) takes the operator's resolutions, applies content-hash " +
|
|
1053
|
+
"idempotency against existing :KnowledgeDocument nodes, copies attachments to " +
|
|
1054
|
+
"{accountDir}/archive/obsidian/<importId>/, and invokes memory-archive-write with " +
|
|
1055
|
+
"archiveType='obsidian-vault'. The agent never reads or parses markdown — parsing is " +
|
|
1056
|
+
"fully server-side. Routed from the obsidian-import skill under the database-operator " +
|
|
1057
|
+
"specialist; do not call directly from admin chat.", {
|
|
1058
|
+
ownerNodeId: z
|
|
1059
|
+
.string()
|
|
1060
|
+
.min(1)
|
|
1061
|
+
.describe("elementId of the vault owner — :AdminUser for the operator's own vault, :Person for a vault ingested for reference. Confirmed via the skill's owner-confirmation flow before this tool is invoked."),
|
|
1062
|
+
extractedDir: z
|
|
1063
|
+
.string()
|
|
1064
|
+
.min(1)
|
|
1065
|
+
.describe("Absolute path to the extracted vault directory (typically the output of unzip-attachment under <accountDir>/extracted/<id>/). Must be a directory tree of markdown + asset files."),
|
|
1066
|
+
mode: z
|
|
1067
|
+
.enum(["dry-run", "commit"])
|
|
1068
|
+
.describe("`dry-run` returns the resolution plan + ambiguities without writing. `commit` requires the importId returned by the prior dry-run and a `resolutions` map covering every ambiguity."),
|
|
1069
|
+
importId: z
|
|
1070
|
+
.string()
|
|
1071
|
+
.optional()
|
|
1072
|
+
.describe("Stable identifier for the import run. Generated by dry-run; passed back in the commit call so observability lines tie together."),
|
|
1073
|
+
filters: z
|
|
1074
|
+
.object({
|
|
1075
|
+
folderPrefixes: z.array(z.string()).optional()
|
|
1076
|
+
.describe("Vault-relative folder prefixes (with trailing slash optional) to include. Pages outside these prefixes are skipped."),
|
|
1077
|
+
tags: z.array(z.string()).optional()
|
|
1078
|
+
.describe("Tag tokens (without `#`) to filter by. A page must carry at least one to qualify."),
|
|
1079
|
+
modifiedSince: z.string().optional()
|
|
1080
|
+
.describe("ISO 8601 date or epoch-ms. Pages with mtime older than this are skipped."),
|
|
1081
|
+
})
|
|
1082
|
+
.optional()
|
|
1083
|
+
.describe("Selective-ingest filter. The skill's filter conversation drives this — the operator picks folder / tag / modified-since combinations."),
|
|
1084
|
+
resolutions: z
|
|
1085
|
+
.record(z.string(), z.discriminatedUnion("kind", [
|
|
1086
|
+
z.object({ kind: z.literal("page"), obsidianPath: z.string() }),
|
|
1087
|
+
z.object({ kind: z.literal("entity"), nodeId: z.string() }),
|
|
1088
|
+
z.object({ kind: z.literal("stub") }),
|
|
1089
|
+
]))
|
|
1090
|
+
.optional()
|
|
1091
|
+
.describe("Operator answers to dry-run ambiguities, keyed by the ambiguity id the dry-run returned. Required on commit when ambiguities were reported."),
|
|
1092
|
+
selectiveIngestThreshold: z
|
|
1093
|
+
.number()
|
|
1094
|
+
.int()
|
|
1095
|
+
.positive()
|
|
1096
|
+
.optional()
|
|
1097
|
+
.describe("Page-count threshold above which dry-run sets `selectiveIngestSuggested=true`. Default 100."),
|
|
1098
|
+
sessionId: z
|
|
1099
|
+
.string()
|
|
1100
|
+
.optional()
|
|
1101
|
+
.describe("Skill-run UUID for provenance stamping. Falls back to SESSION_ID env var when absent."),
|
|
1102
|
+
}, async ({ ownerNodeId, extractedDir, mode, importId, filters, resolutions, selectiveIngestThreshold, sessionId: sessionIdOverride, }) => {
|
|
1103
|
+
if (!accountId)
|
|
1104
|
+
return refuseNoAccount("obsidian-vault-import");
|
|
1105
|
+
try {
|
|
1106
|
+
const result = await obsidianVaultImport({
|
|
1107
|
+
accountId,
|
|
1108
|
+
ownerNodeId,
|
|
1109
|
+
extractedDir,
|
|
1110
|
+
mode,
|
|
1111
|
+
importId,
|
|
1112
|
+
filters: filters,
|
|
1113
|
+
resolutions: resolutions,
|
|
1114
|
+
selectiveIngestThreshold,
|
|
1115
|
+
sessionId: resolveSessionId(sessionIdOverride),
|
|
1116
|
+
});
|
|
1117
|
+
return {
|
|
1118
|
+
content: [{
|
|
1119
|
+
type: "text",
|
|
1120
|
+
text: JSON.stringify(result),
|
|
1121
|
+
}],
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
catch (err) {
|
|
1125
|
+
return {
|
|
1126
|
+
content: [{
|
|
1127
|
+
type: "text",
|
|
1128
|
+
text: `obsidian-vault-import failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1129
|
+
}],
|
|
1130
|
+
isError: true,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1014
1134
|
// retired the `whatsapp-export-insight-pass` Phase-2 tool
|
|
1015
1135
|
// generalised the entire conversation-archive ingest path (no longer
|
|
1016
1136
|
// WhatsApp-specific) — see `memory/skills/conversation-archive/` and
|
|
@@ -1954,6 +2074,82 @@ server.tool("image-fetch", [
|
|
|
1954
2074
|
],
|
|
1955
2075
|
};
|
|
1956
2076
|
});
|
|
2077
|
+
// ===================================================================
|
|
2078
|
+
// Typed-edge auto-extraction pass (Task 305)
|
|
2079
|
+
// ===================================================================
|
|
2080
|
+
//
|
|
2081
|
+
// Schema-bounded session-end pass that reads prose-bearing nodes the
|
|
2082
|
+
// operator's account wrote since `sinceIso` and asks Haiku for typed-edge
|
|
2083
|
+
// proposals against `TYPED_EDGE_ALLOWLIST`. Owned by `database-operator`
|
|
2084
|
+
// (admin Task-spawns it at the retrospective turn); NOT in
|
|
2085
|
+
// `ADMIN_CORE_TOOLS`. Backfill is via the CLI under `scripts/backfill-typed-edges.ts`,
|
|
2086
|
+
// bypassing this tool.
|
|
2087
|
+
server.tool("memory-typed-edge-pass", "Run the typed-edge auto-extraction pass over prose-bearing nodes this account wrote since sinceIso. Reads Message/Page/Meeting/Email/Idea/Note/Post/Report nodes, batches them, asks Haiku for typed-edge proposals against the closed allowlist (ATTENDED, WORKS_AT, INVESTED_IN, FOUNDED, ADVISES, MENTIONS, AUTHORED, ATTACHED_TO, REFERENCES), validates each proposal inline, and MERGEs accepted edges with createdBy provenance. Returns the counters (nodes, proposed, accepted, rejected, failed, ms) for the caller to hand back to admin's session-retrospective-mark-complete.", {
|
|
2088
|
+
sinceIso: z.string().describe("ISO timestamp lower bound. Prose nodes with updatedAt >= sinceIso are processed."),
|
|
2089
|
+
conversationId: z.string().optional().describe("Conversation id stamped onto the createdBy provenance of MERGEd edges. Defaults to CONVERSATION_ID env if absent."),
|
|
2090
|
+
}, async (args) => {
|
|
2091
|
+
if (!accountId)
|
|
2092
|
+
return refuseNoAccount("memory-typed-edge-pass");
|
|
2093
|
+
const dbSession = getSession();
|
|
2094
|
+
try {
|
|
2095
|
+
const result = await memoryTypedEdgePass(args, dbSession, accountId);
|
|
2096
|
+
return {
|
|
2097
|
+
content: [{
|
|
2098
|
+
type: "text",
|
|
2099
|
+
text: JSON.stringify(result),
|
|
2100
|
+
}],
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
catch (err) {
|
|
2104
|
+
return {
|
|
2105
|
+
content: [{
|
|
2106
|
+
type: "text",
|
|
2107
|
+
text: `Typed-edge pass failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2108
|
+
}],
|
|
2109
|
+
isError: true,
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
finally {
|
|
2113
|
+
await dbSession.close();
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
// ===================================================================
|
|
2117
|
+
// Session-retrospective skip-rate (Task 305)
|
|
2118
|
+
// ===================================================================
|
|
2119
|
+
//
|
|
2120
|
+
// Visibility tool the admin agent calls at session start. Returns the ratio
|
|
2121
|
+
// of recent sessions for this account that ended WITHOUT calling
|
|
2122
|
+
// `session-retrospective-mark-complete` — those sessions skipped the
|
|
2123
|
+
// typed-edge pass, learnings pass, and SOUL.md pass. Admin surfaces a
|
|
2124
|
+
// one-line note to the operator when the rate is non-zero.
|
|
2125
|
+
server.tool("session-retrospective-skip-rate", "Return the ratio of recent sessions for this account that closed without firing the session-retrospective. Skipped retrospectives mean the typed-edge pass, learnings pass, and SOUL.md updates are deferred to the next /end. Use at session start to surface the cost to the operator when non-zero.", {
|
|
2126
|
+
windowSize: z.number().int().positive().optional().describe("Number of most-recent sessions to inspect. Defaults to 10, capped at 100."),
|
|
2127
|
+
}, async (args) => {
|
|
2128
|
+
if (!accountId)
|
|
2129
|
+
return refuseNoAccount("session-retrospective-skip-rate");
|
|
2130
|
+
const dbSession = getSession();
|
|
2131
|
+
try {
|
|
2132
|
+
const result = await sessionRetrospectiveSkipRate(args, dbSession, accountId);
|
|
2133
|
+
return {
|
|
2134
|
+
content: [{
|
|
2135
|
+
type: "text",
|
|
2136
|
+
text: JSON.stringify(result),
|
|
2137
|
+
}],
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
catch (err) {
|
|
2141
|
+
return {
|
|
2142
|
+
content: [{
|
|
2143
|
+
type: "text",
|
|
2144
|
+
text: `Skip-rate query failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2145
|
+
}],
|
|
2146
|
+
isError: true,
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
finally {
|
|
2150
|
+
await dbSession.close();
|
|
2151
|
+
}
|
|
2152
|
+
});
|
|
1957
2153
|
// Cleanup on exit
|
|
1958
2154
|
process.on("SIGINT", async () => {
|
|
1959
2155
|
liveSchemaRuntime.cache.stop();
|