@rubytech/create-maxy-code 0.1.322 → 0.1.324
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/index.js +55 -3
- package/package.json +1 -1
- package/payload/platform/plugins/admin/skills/insight/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +36 -5
- package/payload/platform/plugins/docs/references/admin-ui.md +12 -0
- package/payload/platform/plugins/docs/references/deployment.md +19 -0
- package/payload/platform/plugins/docs/references/plugins-guide.md +4 -4
- package/payload/platform/plugins/memory/PLUGIN.md +0 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +0 -45
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js +6 -6
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js.map +1 -1
- package/payload/platform/plugins/notion-import/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/notion-import/PLUGIN.md +6 -6
- package/payload/platform/plugins/notion-import/skills/notion-import/SKILL.md +1 -1
- package/payload/platform/plugins/notion-import/skills/notion-import/references/databases.md +1 -1
- package/payload/platform/plugins/obsidian-import/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/obsidian-import/PLUGIN.md +5 -5
- package/payload/platform/plugins/obsidian-import/skills/obsidian-import/SKILL.md +1 -1
- package/payload/platform/plugins/substack-import/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/substack-import/PLUGIN.md +8 -8
- package/payload/platform/plugins/substack-import/skills/substack-import/SKILL.md +2 -2
- package/payload/platform/plugins/substack-import/skills/substack-import/references/posts.md +1 -1
- package/payload/platform/plugins/x-import/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/x-import/PLUGIN.md +5 -5
- package/payload/platform/plugins/x-import/skills/x-import/SKILL.md +1 -1
- package/payload/platform/scripts/check-no-esm-require.mjs +3 -0
- package/payload/platform/scripts/identity-forbidden-token-check.mjs +0 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +0 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +24 -7
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +3 -3
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js +8 -4
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +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 +13 -12
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +5 -4
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/reaper.js +6 -6
- package/payload/platform/services/claude-session-manager/dist/reaper.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +0 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/templates/specialists/agents/coding-assistant.md +1 -1
- package/payload/platform/templates/specialists/agents/database-operator.md +2 -6
- package/payload/platform/templates/specialists/agents/librarian.md +20 -7
- package/payload/server/power-health.cjs +129 -0
- package/payload/server/public/assets/AdminLoginScreens-BaNHSvDG.js +1 -0
- package/payload/server/public/assets/AdminShell-6Iq4co_0.js +1 -0
- package/payload/server/public/assets/{Checkbox-DjANdKoK.js → Checkbox-BoljWa0z.js} +1 -1
- package/payload/server/public/assets/OperatorConversations-DL9372pQ.css +1 -0
- package/payload/server/public/assets/OperatorConversations-yKFxglvV.js +9 -0
- package/payload/server/public/assets/{admin--FlkQOMu.js → admin-DgGttnBt.js} +1 -1
- package/payload/server/public/assets/{browser-vowQc0TJ.js → browser-BLoa93fm.js} +1 -1
- package/payload/server/public/assets/chat-BJ4229vV.js +1 -0
- package/payload/server/public/assets/data-BK_ggj-z.js +1 -0
- package/payload/server/public/assets/{graph-B-64Cq-y.js → graph-iNIIHeRn.js} +3 -3
- package/payload/server/public/assets/{graph-labels-Dtd2ckXH.js → graph-labels-IeLlsqB1.js} +1 -1
- package/payload/server/public/assets/operator-plsrZHSk.js +1 -0
- package/payload/server/public/assets/page-BOQHw8Fa.js +30 -0
- package/payload/server/public/assets/{public-CrFepd06.js → public-CuMZDmDF.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/chat.html +5 -5
- package/payload/server/public/data.html +4 -4
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +6 -6
- package/payload/server/public/operator.html +7 -7
- package/payload/server/public/public.html +5 -5
- package/payload/server/server-init.cjs +40 -0
- package/payload/server/server.js +36 -1
- package/payload/platform/plugins/memory/mcp/scripts/backfill-typed-edges.ts +0 -72
- package/payload/platform/templates/specialists/agents/archive-ingest-operator.md +0 -45
- package/payload/server/public/assets/AdminLoginScreens-D1X4M9wX.js +0 -1
- package/payload/server/public/assets/AdminShell-BGZ3d3gY.js +0 -1
- package/payload/server/public/assets/OperatorConversations-BkitSIiT.js +0 -9
- package/payload/server/public/assets/OperatorConversations-C-eFXT7w.css +0 -1
- package/payload/server/public/assets/chat-Cw6rUWiC.js +0 -1
- package/payload/server/public/assets/data-9ArhwX1q.js +0 -1
- package/payload/server/public/assets/operator-Cqn1Ne60.js +0 -1
- package/payload/server/public/assets/page-DQ5D6Dg9.js +0 -30
package/dist/index.js
CHANGED
|
@@ -608,6 +608,55 @@ function writeChromiumBinaryPathFile() {
|
|
|
608
608
|
console.log(` Wrote ${target} → ${RESOLVED_CHROMIUM_BIN}`);
|
|
609
609
|
logFile(` [snap-chromium] wrote ${target} contents=${RESOLVED_CHROMIUM_BIN}`);
|
|
610
610
|
}
|
|
611
|
+
// Arm the systemd system-manager hardware watchdog so a *kernel hang* (the
|
|
612
|
+
// freeze mode: board powered but stuck, /dev/watchdog never petted) reboots the
|
|
613
|
+
// board without a manual power cycle. Distinct from the brand user-service's
|
|
614
|
+
// software watchdog (Type=notify WATCHDOG_USEC), which only restarts the UI
|
|
615
|
+
// process — this resets the whole SoC. Idempotent host-wide drop-in; gated on
|
|
616
|
+
// /dev/watchdog existing (driver loaded) and Linux. Best-effort: a failure
|
|
617
|
+
// warns, never aborts the install. Cannot recover a latched-off brownout (no
|
|
618
|
+
// power = no watchdog) — that needs the external power-cycler documented in
|
|
619
|
+
// deployment.md.
|
|
620
|
+
function configureHardwareWatchdog() {
|
|
621
|
+
try {
|
|
622
|
+
if (!isLinux()) {
|
|
623
|
+
logFile(" hardware watchdog: skipped (not Linux)");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (!existsSync("/dev/watchdog")) {
|
|
627
|
+
logFile(" hardware watchdog: skipped (/dev/watchdog absent)");
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const desired = "[Manager]\nRuntimeWatchdogSec=20s\nRebootWatchdogSec=2min\n";
|
|
631
|
+
const dropinDir = "/etc/systemd/system.conf.d";
|
|
632
|
+
const dropinPath = `${dropinDir}/10-maxy-watchdog.conf`;
|
|
633
|
+
let current = "";
|
|
634
|
+
if (existsSync(dropinPath)) {
|
|
635
|
+
try {
|
|
636
|
+
current = readFileSync(dropinPath, "utf-8");
|
|
637
|
+
}
|
|
638
|
+
catch {
|
|
639
|
+
// Unreadable existing drop-in — treat as changed so we rewrite.
|
|
640
|
+
current = "";
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (current === desired) {
|
|
644
|
+
logFile(` hardware watchdog: already active (${dropinPath})`);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const tmpPath = "/tmp/10-maxy-watchdog.conf";
|
|
648
|
+
writeFileSync(tmpPath, desired);
|
|
649
|
+
console.log(" [privileged] install systemd hardware watchdog drop-in (RuntimeWatchdogSec=20s RebootWatchdogSec=2min)");
|
|
650
|
+
shell("mkdir", ["-p", dropinDir], { sudo: true });
|
|
651
|
+
shell("cp", [tmpPath, dropinPath], { sudo: true });
|
|
652
|
+
spawnSync("rm", ["-f", tmpPath]);
|
|
653
|
+
spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "inherit" });
|
|
654
|
+
logFile(` hardware watchdog: enabled (wrote ${dropinPath}; arms on next boot or systemd re-exec)`);
|
|
655
|
+
}
|
|
656
|
+
catch (err) {
|
|
657
|
+
console.error(` WARNING: failed to configure hardware watchdog: ${err instanceof Error ? err.message : String(err)}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
611
660
|
function installSystemDeps() {
|
|
612
661
|
log("1", TOTAL, "System dependencies and network...");
|
|
613
662
|
const platform = requireSupportedPlatform(process.platform);
|
|
@@ -1566,8 +1615,10 @@ function ensureOllamaServing() {
|
|
|
1566
1615
|
// `ollama.service` bounds llama-server to a fraction of the cores so peak
|
|
1567
1616
|
// temperature plateaus below the limit regardless of batch shape or which
|
|
1568
1617
|
// platform path issues the embeds — the caller-agnostic fix, inherited by every
|
|
1569
|
-
// install. Quota is
|
|
1570
|
-
//
|
|
1618
|
+
// install. Quota is one core (100%): an embedding burst then draws roughly a
|
|
1619
|
+
// quarter of a 4-core Pi's compute current instead of half, which both keeps
|
|
1620
|
+
// peak SoC temperature down and shaves the current spike that browns out a
|
|
1621
|
+
// marginal PSU. Ingest is slower in exchange. Applied only when the ollama
|
|
1571
1622
|
// system service exists (the normal Linux path, where ollama's install.sh
|
|
1572
1623
|
// registers `ollama.service`); a detached `ollama serve` has no unit to cap.
|
|
1573
1624
|
// ollama is one shared host service across co-resident brands, so the drop-in is
|
|
@@ -1582,7 +1633,7 @@ function capOllamaServiceCpu() {
|
|
|
1582
1633
|
return;
|
|
1583
1634
|
}
|
|
1584
1635
|
const coreCount = Math.max(1, cpus().length);
|
|
1585
|
-
const quotaCores =
|
|
1636
|
+
const quotaCores = 1;
|
|
1586
1637
|
const quotaPct = quotaCores * 100;
|
|
1587
1638
|
const desired = `[Service]\nCPUQuota=${quotaPct}%\n`;
|
|
1588
1639
|
const dropinDir = "/etc/systemd/system/ollama.service.d";
|
|
@@ -4188,6 +4239,7 @@ logDiagnostics("pre-flight");
|
|
|
4188
4239
|
logFile(` Neo4j instance: ${NEO4J_DEDICATED ? "dedicated" : "shared"} on bolt://localhost:${NEO4J_PORT}`);
|
|
4189
4240
|
try {
|
|
4190
4241
|
installSystemDeps();
|
|
4242
|
+
configureHardwareWatchdog();
|
|
4191
4243
|
installNodejs();
|
|
4192
4244
|
installClaudeCode();
|
|
4193
4245
|
installNeo4j();
|
package/package.json
CHANGED
|
@@ -19,6 +19,6 @@ Pass 2 — Operator-relationship updates. Walk this session for tonal signals, r
|
|
|
19
19
|
|
|
20
20
|
Pass 3 — Graph completeness sweep. Walk this session for any node, edge, or commitment that was discussed but not written to the graph in-flight. For each missing write, delegate to `database-operator` via the Task tool. Count what you write.
|
|
21
21
|
|
|
22
|
-
Pass 4 — Typed-edge auto-extraction. Delegate to `
|
|
22
|
+
Pass 4 — Typed-edge auto-extraction. Delegate to `typed-edge-classifier` via the Task tool with a prompt supplying the `accountId` and the session-start timestamp as `sinceIso`. The specialist reads prose nodes written since that timestamp, writes typed edges from the closed allowlist, and returns one line `typed-edge-classifier: nodesProcessed=… proposed=… accepted=… rejected=… ms=…`. Capture `nodesProcessed` and `accepted` from that line.
|
|
23
23
|
|
|
24
24
|
Then reply with one short paragraph in your house voice summarising what you wrote — naming the five counts (learnings, preferences, graph-writes, typed-edges, nodesProcessed) — and carry on with the operator's work.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:479e04a1e25ace04e44415db3c536ed28faac7ee30b9171524d38b7a667716d1
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -365,10 +365,10 @@ These are enabled during onboarding and can be added or removed at any time. Som
|
|
|
365
365
|
| `whatsapp` | WhatsApp messaging, pairing, and conversation browsing | Personal assistant |
|
|
366
366
|
| `replicate` | Image generation — three models for photorealistic, design, and fast draft images | Content producer, Research assistant |
|
|
367
367
|
| `linkedin-import` | Import a LinkedIn Basic Data Export — Profile and Connections today, more CSVs as references land | Database operator |
|
|
368
|
-
| `notion-import` | Import a Notion workspace export (markdown + CSV) — pages, databases, hierarchy, attachments, schema-bounded relations, `@person` mentions account-filtered |
|
|
369
|
-
| `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). |
|
|
370
|
-
| `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 `sessionId` 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. |
|
|
371
|
-
| `substack-import` | Import a Substack "Export your data" archive — per-essay `:KnowledgeDocument {kind:'substack-post'}` via librarian/document-ingest with synthetic stable `attachmentId = "substack-post-${substackPostId}"` (survives Substack edits); one `:KnowledgeDocument {kind:'substack-subscriber-roster'}` per import run with `:MENTIONS {mentionContext:'substack-subscription', tier, totalOpens, totalClicks, lastOpenedAt, lastClickedAt, engagementWindowDays}` to each subscriber `:Person` MERGEd on `(accountId, email)`. Engagement aggregates parsed from `email_activity.csv` (or `subscriber_activity.csv` / `emails.csv`); overwrite-on-reimport. No new label, no new edge type, no new graph writer. Images attach via canonical `:HAS_ENCLOSURE` (or `:MENTIONS` fallback). Bulk-gate at >200 posts or >2000 subscribers. |
|
|
368
|
+
| `notion-import` | Import a Notion workspace export (markdown + CSV) — pages, databases, hierarchy, attachments, schema-bounded relations, `@person` mentions account-filtered | Librarian |
|
|
369
|
+
| `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). | Librarian |
|
|
370
|
+
| `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 `sessionId` 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. | Librarian |
|
|
371
|
+
| `substack-import` | Import a Substack "Export your data" archive — per-essay `:KnowledgeDocument {kind:'substack-post'}` via librarian/document-ingest with synthetic stable `attachmentId = "substack-post-${substackPostId}"` (survives Substack edits); one `:KnowledgeDocument {kind:'substack-subscriber-roster'}` per import run with `:MENTIONS {mentionContext:'substack-subscription', tier, totalOpens, totalClicks, lastOpenedAt, lastClickedAt, engagementWindowDays}` to each subscriber `:Person` MERGEd on `(accountId, email)`. Engagement aggregates parsed from `email_activity.csv` (or `subscriber_activity.csv` / `emails.csv`); overwrite-on-reimport. No new label, no new edge type, no new graph writer. Images attach via canonical `:HAS_ENCLOSURE` (or `:MENTIONS` fallback). Bulk-gate at >200 posts or >2000 subscribers. | Librarian |
|
|
372
372
|
| `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> --participant-person-ids <csv> --scope <admin\|public>` — runs normalise → operator-confirms owner + every distinct sender (owner derived from env via Cypher, no flag) → sessionize at the fixed 8h gap → emit one JSON line carrying prepared sessions (turn-attributed text + per-session cursor). The dispatched specialist iterates the sessions in-turn, produces a typed-section JSON chunking for each, and calls the `memory-ingest` MCP tool with `conversationIdentity` set (writes `:ConversationArchive`, source=<enum>) once per session — chunks + cursor advance commit atomically inside one Cypher transaction, so a kill mid-archive resumes from the next session on re-issue without re-classifying anything already written. 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 |
|
|
373
373
|
| `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__plugin_memory_memory__conversation-archive-list-chunks`; the dispatched specialist reads each chunk in-turn and emits claims under the four-kind contract (`mention`, `task`, `preference`, `observed-relationship`); the skill hands those claims to `mcp__plugin_memory_memory__conversation-archive-derive-insights` for per-kind cypher emission, then runs the per-row operator gate (`wire / skip / reject`). Idempotent on `(elementId(chunk), kind, contentHash)` — re-runs collapse identical claims. Confidence floor is a hedging-avoidance instruction the skill embeds in the specialist's per-chunk prompt, not a numeric post-filter; the LLM step runs in-turn from the dispatched specialist rather than as a server-side OAuth round-trip. | Database operator |
|
|
374
374
|
|
|
@@ -2290,6 +2290,18 @@ reserved for vertical / integration notes (LinkedIn extension,
|
|
|
2290
2290
|
PropertyData, Real Agent standalone, MCP server inventory) — not for
|
|
2291
2291
|
core-platform docs.
|
|
2292
2292
|
|
|
2293
|
+
## UI conventions
|
|
2294
|
+
|
|
2295
|
+
**Icons are Lucide components only — no emoji.** Every glyph rendered in
|
|
2296
|
+
`platform/ui` JSX (the `/chat` web chat, the WhatsApp/channel readers, the
|
|
2297
|
+
admin shell, the `/data` grid) is a `lucide-react` component (e.g.
|
|
2298
|
+
`<Paperclip>`, `<FileText>`), never an emoji/emoticon character in the rendered
|
|
2299
|
+
string. The rule is enforced at build time by
|
|
2300
|
+
[`scripts/check-no-emoji-in-jsx.mjs`](../../../ui/scripts/check-no-emoji-in-jsx.mjs),
|
|
2301
|
+
which fails `prebuild` if any emoji codepoint appears in a `.ts`/`.tsx` file
|
|
2302
|
+
under `app/` (tests excluded). Inline an icon at the surrounding text size and
|
|
2303
|
+
colour (`size={14}` matches body text; Lucide inherits `currentColor`).
|
|
2304
|
+
|
|
2293
2305
|
## Admin Hono routes
|
|
2294
2306
|
|
|
2295
2307
|
Every admin sub-app is mounted by
|
|
@@ -3458,6 +3470,25 @@ Source: https://docs.getmaxy.com/deployment.md
|
|
|
3458
3470
|
- Mac with macOS 14 (Sonoma) or newer — both Apple Silicon and Intel
|
|
3459
3471
|
- 256GB storage minimum
|
|
3460
3472
|
- Always-on power and network connection
|
|
3473
|
+
- **On a Pi 5: the official 27W USB-C power supply (5.1V / 5A) and a good cable.** This is not optional — see Power and recovery below.
|
|
3474
|
+
|
|
3475
|
+
## Power and recovery (Raspberry Pi)
|
|
3476
|
+
|
|
3477
|
+
A Pi 5 running Maxy draws hard during document ingest: the embedding model runs the CPU flat-out in bursts. An underpowered supply or a thin/long USB-C cable cannot hold 5V under that current spike, the board *browns out*, and you get one of two failures:
|
|
3478
|
+
|
|
3479
|
+
- **A reset or freeze** — the board drops out mid-task and either reboots or hangs.
|
|
3480
|
+
- **A latched-off board** — the board goes dark and stays dark until you physically unplug and replug the power. No software can recover this, because there is no power to run any recovery.
|
|
3481
|
+
|
|
3482
|
+
**The cure is hardware.** Use the official Raspberry Pi 5 27W PSU (5.1V / 5A) and a short, good-quality USB-C cable. A marginal phone charger or a cheap cable is the usual cause. This is the only thing that *prevents* a brownout.
|
|
3483
|
+
|
|
3484
|
+
**Recovery seatbelts the installer sets up for you:**
|
|
3485
|
+
|
|
3486
|
+
- **A hardware watchdog.** If the board *hangs* (powered but stuck), it reboots itself automatically within about two minutes — no manual power cycle. This only helps a hung board; it cannot help a board that has gone dark.
|
|
3487
|
+
- **Power-health logging.** The admin server writes a `[power-health]` line to `server.log` at boot and every 15 minutes, so a brownout shows up without waiting for a crash. A healthy line reads `throttled=0x0 ... undervolt_occurred=false`. Any `undervolt`-flag set, or `reboots_unclean_24h` above zero, means the board is browning out — fix the power before trusting it with heavy work.
|
|
3488
|
+
|
|
3489
|
+
**For a board that latches off (the dark-board case):** put the Pi on a smart plug that you can power-cycle remotely, ideally one driven by a heartbeat (cut power and restore it when the box stops responding). This is the robust recovery for the dark-board failure and is an operator hardware choice, not something the installer can do.
|
|
3490
|
+
|
|
3491
|
+
**Keep one address.** After a reboot the Pi can pick up a different DHCP address and look like it vanished. Add a **DHCP reservation** on your router that pins the Pi's MAC to a fixed LAN IP, so the box is always reachable at the same address. The boot `[power-health] op=boot lan_ip=<ip>` line records the address each boot — if it changes across reboots, the reservation is not in place.
|
|
3461
3492
|
|
|
3462
3493
|
## macOS install
|
|
3463
3494
|
|
|
@@ -25,6 +25,18 @@ reserved for vertical / integration notes (LinkedIn extension,
|
|
|
25
25
|
PropertyData, Real Agent standalone, MCP server inventory) — not for
|
|
26
26
|
core-platform docs.
|
|
27
27
|
|
|
28
|
+
## UI conventions
|
|
29
|
+
|
|
30
|
+
**Icons are Lucide components only — no emoji.** Every glyph rendered in
|
|
31
|
+
`platform/ui` JSX (the `/chat` web chat, the WhatsApp/channel readers, the
|
|
32
|
+
admin shell, the `/data` grid) is a `lucide-react` component (e.g.
|
|
33
|
+
`<Paperclip>`, `<FileText>`), never an emoji/emoticon character in the rendered
|
|
34
|
+
string. The rule is enforced at build time by
|
|
35
|
+
[`scripts/check-no-emoji-in-jsx.mjs`](../../../ui/scripts/check-no-emoji-in-jsx.mjs),
|
|
36
|
+
which fails `prebuild` if any emoji codepoint appears in a `.ts`/`.tsx` file
|
|
37
|
+
under `app/` (tests excluded). Inline an icon at the surrounding text size and
|
|
38
|
+
colour (`size={14}` matches body text; Lucide inherits `currentColor`).
|
|
39
|
+
|
|
28
40
|
## Admin Hono routes
|
|
29
41
|
|
|
30
42
|
Every admin sub-app is mounted by
|
|
@@ -6,6 +6,25 @@
|
|
|
6
6
|
- Mac with macOS 14 (Sonoma) or newer — both Apple Silicon and Intel
|
|
7
7
|
- 256GB storage minimum
|
|
8
8
|
- Always-on power and network connection
|
|
9
|
+
- **On a Pi 5: the official 27W USB-C power supply (5.1V / 5A) and a good cable.** This is not optional — see Power and recovery below.
|
|
10
|
+
|
|
11
|
+
## Power and recovery (Raspberry Pi)
|
|
12
|
+
|
|
13
|
+
A Pi 5 running {{productName}} draws hard during document ingest: the embedding model runs the CPU flat-out in bursts. An underpowered supply or a thin/long USB-C cable cannot hold 5V under that current spike, the board *browns out*, and you get one of two failures:
|
|
14
|
+
|
|
15
|
+
- **A reset or freeze** — the board drops out mid-task and either reboots or hangs.
|
|
16
|
+
- **A latched-off board** — the board goes dark and stays dark until you physically unplug and replug the power. No software can recover this, because there is no power to run any recovery.
|
|
17
|
+
|
|
18
|
+
**The cure is hardware.** Use the official Raspberry Pi 5 27W PSU (5.1V / 5A) and a short, good-quality USB-C cable. A marginal phone charger or a cheap cable is the usual cause. This is the only thing that *prevents* a brownout.
|
|
19
|
+
|
|
20
|
+
**Recovery seatbelts the installer sets up for you:**
|
|
21
|
+
|
|
22
|
+
- **A hardware watchdog.** If the board *hangs* (powered but stuck), it reboots itself automatically within about two minutes — no manual power cycle. This only helps a hung board; it cannot help a board that has gone dark.
|
|
23
|
+
- **Power-health logging.** The admin server writes a `[power-health]` line to `server.log` at boot and every 15 minutes, so a brownout shows up without waiting for a crash. A healthy line reads `throttled=0x0 ... undervolt_occurred=false`. Any `undervolt`-flag set, or `reboots_unclean_24h` above zero, means the board is browning out — fix the power before trusting it with heavy work.
|
|
24
|
+
|
|
25
|
+
**For a board that latches off (the dark-board case):** put the Pi on a smart plug that you can power-cycle remotely, ideally one driven by a heartbeat (cut power and restore it when the box stops responding). This is the robust recovery for the dark-board failure and is an operator hardware choice, not something the installer can do.
|
|
26
|
+
|
|
27
|
+
**Keep one address.** After a reboot the Pi can pick up a different DHCP address and look like it vanished. Add a **DHCP reservation** on your router that pins the Pi's MAC to a fixed LAN IP, so the box is always reachable at the same address. The boot `[power-health] op=boot lan_ip=<ip>` line records the address each boot — if it changes across reboots, the reservation is not in place.
|
|
9
28
|
|
|
10
29
|
## macOS install
|
|
11
30
|
|
|
@@ -43,10 +43,10 @@ These are enabled during onboarding and can be added or removed at any time. Som
|
|
|
43
43
|
| `whatsapp` | WhatsApp messaging, pairing, and conversation browsing | Personal assistant |
|
|
44
44
|
| `replicate` | Image generation — three models for photorealistic, design, and fast draft images | Content producer, Research assistant |
|
|
45
45
|
| `linkedin-import` | Import a LinkedIn Basic Data Export — Profile and Connections today, more CSVs as references land | Database operator |
|
|
46
|
-
| `notion-import` | Import a Notion workspace export (markdown + CSV) — pages, databases, hierarchy, attachments, schema-bounded relations, `@person` mentions account-filtered |
|
|
47
|
-
| `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). |
|
|
48
|
-
| `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 `sessionId` 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. |
|
|
49
|
-
| `substack-import` | Import a Substack "Export your data" archive — per-essay `:KnowledgeDocument {kind:'substack-post'}` via librarian/document-ingest with synthetic stable `attachmentId = "substack-post-${substackPostId}"` (survives Substack edits); one `:KnowledgeDocument {kind:'substack-subscriber-roster'}` per import run with `:MENTIONS {mentionContext:'substack-subscription', tier, totalOpens, totalClicks, lastOpenedAt, lastClickedAt, engagementWindowDays}` to each subscriber `:Person` MERGEd on `(accountId, email)`. Engagement aggregates parsed from `email_activity.csv` (or `subscriber_activity.csv` / `emails.csv`); overwrite-on-reimport. No new label, no new edge type, no new graph writer. Images attach via canonical `:HAS_ENCLOSURE` (or `:MENTIONS` fallback). Bulk-gate at >200 posts or >2000 subscribers. |
|
|
46
|
+
| `notion-import` | Import a Notion workspace export (markdown + CSV) — pages, databases, hierarchy, attachments, schema-bounded relations, `@person` mentions account-filtered | Librarian |
|
|
47
|
+
| `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). | Librarian |
|
|
48
|
+
| `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 `sessionId` 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. | Librarian |
|
|
49
|
+
| `substack-import` | Import a Substack "Export your data" archive — per-essay `:KnowledgeDocument {kind:'substack-post'}` via librarian/document-ingest with synthetic stable `attachmentId = "substack-post-${substackPostId}"` (survives Substack edits); one `:KnowledgeDocument {kind:'substack-subscriber-roster'}` per import run with `:MENTIONS {mentionContext:'substack-subscription', tier, totalOpens, totalClicks, lastOpenedAt, lastClickedAt, engagementWindowDays}` to each subscriber `:Person` MERGEd on `(accountId, email)`. Engagement aggregates parsed from `email_activity.csv` (or `subscriber_activity.csv` / `emails.csv`); overwrite-on-reimport. No new label, no new edge type, no new graph writer. Images attach via canonical `:HAS_ENCLOSURE` (or `:MENTIONS` fallback). Bulk-gate at >200 posts or >2000 subscribers. | Librarian |
|
|
50
50
|
| `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> --participant-person-ids <csv> --scope <admin\|public>` — runs normalise → operator-confirms owner + every distinct sender (owner derived from env via Cypher, no flag) → sessionize at the fixed 8h gap → emit one JSON line carrying prepared sessions (turn-attributed text + per-session cursor). The dispatched specialist iterates the sessions in-turn, produces a typed-section JSON chunking for each, and calls the `memory-ingest` MCP tool with `conversationIdentity` set (writes `:ConversationArchive`, source=<enum>) once per session — chunks + cursor advance commit atomically inside one Cypher transaction, so a kill mid-archive resumes from the next session on re-issue without re-classifying anything already written. 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 |
|
|
51
51
|
| `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__plugin_memory_memory__conversation-archive-list-chunks`; the dispatched specialist reads each chunk in-turn and emits claims under the four-kind contract (`mention`, `task`, `preference`, `observed-relationship`); the skill hands those claims to `mcp__plugin_memory_memory__conversation-archive-derive-insights` for per-kind cypher emission, then runs the per-row operator gate (`wire / skip / reject`). Idempotent on `(elementId(chunk), kind, contentHash)` — re-runs collapse identical claims. Confidence floor is a hedging-avoidance instruction the skill embeds in the specialist's per-chunk prompt, not a numeric post-filter; the LLM step runs in-turn from the dispatched specialist rather than as a server-side OAuth round-trip. | Database operator |
|
|
52
52
|
|
|
@@ -49,7 +49,6 @@ import { conversationArchiveDeriveInsights, PROPOSAL_KINDS, } from "./tools/conv
|
|
|
49
49
|
import { conversationArchiveListChunks } from "./tools/conversation-archive-list-chunks.js";
|
|
50
50
|
import { conversationArchiveEnrichRejection } from "./tools/conversation-archive-enrich-rejection.js";
|
|
51
51
|
import { fetchImageAsContent } from "./tools/image-fetch.js";
|
|
52
|
-
import { memoryTypedEdgePass } from "./tools/memory-typed-edge-pass.js";
|
|
53
52
|
import { memoryReportWrite } from "./tools/memory-report-write.js";
|
|
54
53
|
import { memoryReportReadLatest } from "./tools/memory-report-read-latest.js";
|
|
55
54
|
import { memoryReportList } from "./tools/memory-report-list.js";
|
|
@@ -2423,50 +2422,6 @@ lifelineTool(server, "image-fetch", [
|
|
|
2423
2422
|
};
|
|
2424
2423
|
});
|
|
2425
2424
|
// ===================================================================
|
|
2426
|
-
// Typed-edge auto-extraction pass (Task 305)
|
|
2427
|
-
// ===================================================================
|
|
2428
|
-
//
|
|
2429
|
-
// Schema-bounded pass that reads prose-bearing nodes the operator's account
|
|
2430
|
-
// wrote since `sinceIso` and asks Haiku for typed-edge proposals against
|
|
2431
|
-
// `TYPED_EDGE_ALLOWLIST`. Owned by `database-operator`
|
|
2432
|
-
// (admin Task-spawns it during an on-demand `/insight` pass); NOT in
|
|
2433
|
-
// `ADMIN_CORE_TOOLS`. Backfill is via the CLI under `scripts/backfill-typed-edges.ts`,
|
|
2434
|
-
// bypassing this tool.
|
|
2435
|
-
lifelineTool(server, "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 for its insight-pass summary.", {
|
|
2436
|
-
sinceIso: z.string().describe("ISO timestamp lower bound. Prose nodes with updatedAt >= sinceIso are processed."),
|
|
2437
|
-
sessionId: z.string().optional().describe("Conversation id stamped onto the createdBy provenance of MERGEd edges. Defaults to SESSION_ID env if absent."),
|
|
2438
|
-
}, async (args) => {
|
|
2439
|
-
if (!accountId)
|
|
2440
|
-
return refuseNoAccount("memory-typed-edge-pass");
|
|
2441
|
-
const dbSession = getSession();
|
|
2442
|
-
try {
|
|
2443
|
-
// Cast: the McpServer instance is `any`-typed at the module level (see
|
|
2444
|
-
// file-top comment on TS2589), so `args` infers as the loose
|
|
2445
|
-
// ShapeOutput<ZodRawShapeCompat> instead of the Zod schema above.
|
|
2446
|
-
// Runtime shape is enforced by the schema; this re-asserts the typed
|
|
2447
|
-
// contract at the consumer boundary. Task 404.
|
|
2448
|
-
const result = await memoryTypedEdgePass(args, dbSession, accountId);
|
|
2449
|
-
return {
|
|
2450
|
-
content: [{
|
|
2451
|
-
type: "text",
|
|
2452
|
-
text: JSON.stringify(result),
|
|
2453
|
-
}],
|
|
2454
|
-
};
|
|
2455
|
-
}
|
|
2456
|
-
catch (err) {
|
|
2457
|
-
return {
|
|
2458
|
-
content: [{
|
|
2459
|
-
type: "text",
|
|
2460
|
-
text: `Typed-edge pass failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2461
|
-
}],
|
|
2462
|
-
isError: true,
|
|
2463
|
-
};
|
|
2464
|
-
}
|
|
2465
|
-
finally {
|
|
2466
|
-
await dbSession.close();
|
|
2467
|
-
}
|
|
2468
|
-
});
|
|
2469
|
-
// ===================================================================
|
|
2470
2425
|
// Dream-cycle (Task 327, absorbs 330)
|
|
2471
2426
|
// ===================================================================
|
|
2472
2427
|
// Report MCP tools (Task 332) are registered earlier in this file.
|