@rubytech/create-realagent 1.0.709 → 1.0.712
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 +38 -3
- package/package.json +2 -2
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts +53 -0
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js +132 -0
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js.map +1 -0
- package/payload/platform/lib/mcp-spawn-tee/src/index.ts +134 -0
- package/payload/platform/lib/mcp-spawn-tee/tsconfig.json +8 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/docs/references/plugins-guide.md +12 -4
- package/payload/platform/plugins/linkedin-import/PLUGIN.md +1 -0
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +26 -5
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/connections.md +53 -82
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/profile.md +42 -49
- package/payload/platform/plugins/memory/PLUGIN.md +1 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +48 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +34 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +10 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +22 -3
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +33 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +229 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/package.json +3 -1
- package/payload/platform/plugins/memory/mcp/scripts/boot-smoke.sh +69 -0
- package/payload/platform/plugins/memory/references/graph-primitives.md +22 -0
- package/payload/platform/plugins/memory/references/schema-base.md +1 -1
- package/payload/platform/scripts/redact-install-logs.sh +85 -0
- package/payload/platform/scripts/setup.sh +20 -3
- package/payload/platform/scripts/verify-skill-tool-surface.sh +255 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +6 -2
- package/payload/server/chunk-A5K3CFMI.js +12297 -0
- package/payload/server/chunk-U5JPRUYZ.js +12298 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/assets/{graph-BRD96pKD.js → graph-DJ7IfYHV.js} +12 -12
- package/payload/server/public/graph.html +1 -1
- package/payload/server/server.js +49 -28
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { getSession } from "../lib/neo4j.js";
|
|
2
|
+
/**
|
|
3
|
+
* memory-archive-write — deterministic bulk-archive write surface (Task 744).
|
|
4
|
+
*
|
|
5
|
+
* Writes a flat dataset (today: LinkedIn Connections.csv parsed into typed
|
|
6
|
+
* rows) into the graph as first-class entity nodes plus their natural edges.
|
|
7
|
+
* Distinct from memory-write (per-node, adjacency-required) and memory-ingest
|
|
8
|
+
* (narrative document → KnowledgeDocument + Section + HAS_SECTION + NEXT).
|
|
9
|
+
*
|
|
10
|
+
* Doctrine:
|
|
11
|
+
* - The Cypher body for each archiveType is fixed inside this tool. The
|
|
12
|
+
* agent never supplies Cypher; only parsed rows + the archiveType
|
|
13
|
+
* discriminant. Determinism here means "the Cypher is not in the tool
|
|
14
|
+
* input" — not "be careful with the Cypher".
|
|
15
|
+
* - 500-row UNWIND batches per executeWrite transaction. Larger batches
|
|
16
|
+
* risk Neo4j Community heap pressure on a Pi 5; smaller batches multiply
|
|
17
|
+
* round-trip cost.
|
|
18
|
+
* - Per-row provenance stamps (createdByAgent, createdBySession, source,
|
|
19
|
+
* createdAt) come from this server, not the agent.
|
|
20
|
+
* - Owner is matched by elementId so both AdminUser and external-Person
|
|
21
|
+
* archive owners flow through one path.
|
|
22
|
+
* - Counters come from result.summary.counters (nodesCreated /
|
|
23
|
+
* relationshipsCreated) so we never invent stamp-based detection.
|
|
24
|
+
*/
|
|
25
|
+
const BATCH_SIZE = 500;
|
|
26
|
+
const HANDLERS = {
|
|
27
|
+
"linkedin-connections": linkedinConnectionsHandler(),
|
|
28
|
+
};
|
|
29
|
+
export async function memoryArchiveWrite(params) {
|
|
30
|
+
const { archiveType, ownerNodeId, accountId, rows, sessionId } = params;
|
|
31
|
+
if (!accountId || !accountId.trim()) {
|
|
32
|
+
throw new Error("memory-archive-write: accountId is required.");
|
|
33
|
+
}
|
|
34
|
+
if (!ownerNodeId || !ownerNodeId.trim()) {
|
|
35
|
+
throw new Error("memory-archive-write: ownerNodeId is required.");
|
|
36
|
+
}
|
|
37
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
38
|
+
throw new Error("memory-archive-write: rows must be a non-empty array.");
|
|
39
|
+
}
|
|
40
|
+
const handler = HANDLERS[archiveType];
|
|
41
|
+
if (!handler) {
|
|
42
|
+
throw new Error(`memory-archive-write: unknown archiveType "${archiveType}". Known: ${Object.keys(HANDLERS).join(", ")}`);
|
|
43
|
+
}
|
|
44
|
+
const session = getSession();
|
|
45
|
+
const startedMs = Date.now();
|
|
46
|
+
const errors = [];
|
|
47
|
+
const totals = {
|
|
48
|
+
createdPersons: 0,
|
|
49
|
+
mergedPersons: 0,
|
|
50
|
+
createdOrganizations: 0,
|
|
51
|
+
mergedOrganizations: 0,
|
|
52
|
+
createdEdges: 0,
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
await handler.verifyOwner(session, ownerNodeId, accountId);
|
|
56
|
+
for (let offset = 0; offset < rows.length; offset += BATCH_SIZE) {
|
|
57
|
+
const batch = rows.slice(offset, offset + BATCH_SIZE);
|
|
58
|
+
try {
|
|
59
|
+
const counters = await handler.writeBatch(session, {
|
|
60
|
+
ownerNodeId,
|
|
61
|
+
accountId,
|
|
62
|
+
rows: batch,
|
|
63
|
+
sessionId: sessionId?.trim() || null,
|
|
64
|
+
});
|
|
65
|
+
totals.createdPersons += counters.createdPersons;
|
|
66
|
+
totals.mergedPersons += counters.mergedPersons;
|
|
67
|
+
totals.createdOrganizations += counters.createdOrganizations;
|
|
68
|
+
totals.mergedOrganizations += counters.mergedOrganizations;
|
|
69
|
+
totals.createdEdges += counters.createdEdges;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
73
|
+
errors.push({ rowIndex: offset, reason: `batch starting at row ${offset}: ${reason}` });
|
|
74
|
+
process.stderr.write(`[memory-archive-write] batch-failed archiveType=${archiveType} offset=${offset} reason=${reason}\n`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
await session.close();
|
|
80
|
+
}
|
|
81
|
+
const elapsedMs = Date.now() - startedMs;
|
|
82
|
+
process.stderr.write(`[memory-archive-write] archiveType=${archiveType} rows=${rows.length} ` +
|
|
83
|
+
`createdPersons=${totals.createdPersons} mergedPersons=${totals.mergedPersons} ` +
|
|
84
|
+
`createdOrganizations=${totals.createdOrganizations} mergedOrganizations=${totals.mergedOrganizations} ` +
|
|
85
|
+
`createdEdges=${totals.createdEdges} errors=${errors.length} ms=${elapsedMs}\n`);
|
|
86
|
+
return {
|
|
87
|
+
archiveType,
|
|
88
|
+
processedRows: rows.length,
|
|
89
|
+
...totals,
|
|
90
|
+
errors,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// linkedin-connections handler
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
//
|
|
97
|
+
// Owner anchor: AdminUser or Person (elementId match — operator-confirmed
|
|
98
|
+
// during the linkedin-import skill's owner-confirmation flow).
|
|
99
|
+
//
|
|
100
|
+
// Per row the CSV expresses two relationships:
|
|
101
|
+
// 1. (owner)-[:CONNECTED_ON_LINKEDIN {connectedOn}]->(:Person)
|
|
102
|
+
// 2. (:Person)-[:WORKS_FOR {title, current=true}]->(:Organization) — when company present
|
|
103
|
+
//
|
|
104
|
+
// Natural keys:
|
|
105
|
+
// :Person — linkedinUrl (indexed in platform/neo4j/schema.cypher)
|
|
106
|
+
// :Organization — (accountId, name)
|
|
107
|
+
//
|
|
108
|
+
// Provenance stamped on every node:
|
|
109
|
+
// createdByAgent='linkedin-import', createdBySource='linkedin-import',
|
|
110
|
+
// createdBySession=$sessionId (when provided), createdAt=datetime(),
|
|
111
|
+
// source='linkedin'
|
|
112
|
+
//
|
|
113
|
+
// We split each batch into two tx.run calls inside one executeWrite so each
|
|
114
|
+
// statement's summary counters give a clean per-label breakdown.
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
const PERSON_AND_CONNECTED_EDGE_CYPHER = `
|
|
117
|
+
MATCH (owner) WHERE elementId(owner) = $ownerNodeId
|
|
118
|
+
WITH owner, $rows AS rows
|
|
119
|
+
UNWIND rows AS row
|
|
120
|
+
MERGE (p:Person {linkedinUrl: row.linkedinUrl})
|
|
121
|
+
ON CREATE SET
|
|
122
|
+
p.accountId = $accountId,
|
|
123
|
+
p.source = 'linkedin',
|
|
124
|
+
p.createdByAgent = 'linkedin-import',
|
|
125
|
+
p.createdBySource = 'linkedin-import',
|
|
126
|
+
p.createdBySession = $sessionId,
|
|
127
|
+
p.createdAt = datetime(),
|
|
128
|
+
p.scope = 'admin'
|
|
129
|
+
SET
|
|
130
|
+
p.givenName = row.givenName,
|
|
131
|
+
p.familyName= row.familyName,
|
|
132
|
+
p.name = trim(coalesce(row.givenName,'') + ' ' + coalesce(row.familyName,''))
|
|
133
|
+
FOREACH (_ IN CASE WHEN row.email IS NOT NULL AND row.email <> '' THEN [1] ELSE [] END |
|
|
134
|
+
SET p.email = row.email
|
|
135
|
+
)
|
|
136
|
+
MERGE (owner)-[c:CONNECTED_ON_LINKEDIN]->(p)
|
|
137
|
+
ON CREATE SET
|
|
138
|
+
c.connectedOn = date(row.connectedOn),
|
|
139
|
+
c.source = 'linkedin',
|
|
140
|
+
c.createdAt = datetime()
|
|
141
|
+
RETURN count(*) AS processed
|
|
142
|
+
`;
|
|
143
|
+
const ORG_AND_WORKS_FOR_CYPHER = `
|
|
144
|
+
UNWIND $rows AS row
|
|
145
|
+
WITH row WHERE row.company IS NOT NULL AND row.company <> ''
|
|
146
|
+
MATCH (p:Person {linkedinUrl: row.linkedinUrl})
|
|
147
|
+
MERGE (o:Organization {accountId: $accountId, name: row.company})
|
|
148
|
+
ON CREATE SET
|
|
149
|
+
o.source = 'linkedin',
|
|
150
|
+
o.createdByAgent = 'linkedin-import',
|
|
151
|
+
o.createdBySource = 'linkedin-import',
|
|
152
|
+
o.createdBySession = $sessionId,
|
|
153
|
+
o.createdAt = datetime(),
|
|
154
|
+
o.scope = 'admin'
|
|
155
|
+
MERGE (p)-[w:WORKS_FOR]->(o)
|
|
156
|
+
ON CREATE SET
|
|
157
|
+
w.title = row.title,
|
|
158
|
+
w.source = 'linkedin',
|
|
159
|
+
w.current = true,
|
|
160
|
+
w.createdAt = datetime()
|
|
161
|
+
ON MATCH SET
|
|
162
|
+
w.title = coalesce(row.title, w.title)
|
|
163
|
+
RETURN count(*) AS processed
|
|
164
|
+
`;
|
|
165
|
+
function linkedinConnectionsHandler() {
|
|
166
|
+
return {
|
|
167
|
+
async verifyOwner(session, ownerNodeId, accountId) {
|
|
168
|
+
const result = await session.run(`MATCH (n) WHERE elementId(n) = $ownerNodeId
|
|
169
|
+
RETURN labels(n) AS labels, n.accountId AS accountId LIMIT 1`, { ownerNodeId });
|
|
170
|
+
if (result.records.length === 0) {
|
|
171
|
+
throw new Error(`ownerNodeId ${ownerNodeId} not found. Re-run the archive-owner confirmation flow before invoking memory-archive-write.`);
|
|
172
|
+
}
|
|
173
|
+
const labels = result.records[0].get("labels") ?? [];
|
|
174
|
+
const ownerAccountId = result.records[0].get("accountId");
|
|
175
|
+
const isAdminUser = labels.includes("AdminUser");
|
|
176
|
+
const isPerson = labels.includes("Person");
|
|
177
|
+
if (!isAdminUser && !isPerson) {
|
|
178
|
+
throw new Error(`ownerNodeId ${ownerNodeId} has labels [${labels.join(", ")}]; expected :AdminUser or :Person.`);
|
|
179
|
+
}
|
|
180
|
+
if (ownerAccountId && ownerAccountId !== accountId) {
|
|
181
|
+
throw new Error(`ownerNodeId ${ownerNodeId} belongs to account ${ownerAccountId}, not ${accountId}. Cross-account archive ingest refused.`);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
async writeBatch(session, { ownerNodeId, accountId, rows, sessionId }) {
|
|
185
|
+
const params = {
|
|
186
|
+
ownerNodeId,
|
|
187
|
+
accountId,
|
|
188
|
+
sessionId,
|
|
189
|
+
rows: rows.map((r) => normaliseLinkedinRow(r)),
|
|
190
|
+
};
|
|
191
|
+
return await session.executeWrite(async (tx) => {
|
|
192
|
+
const personRes = await tx.run(PERSON_AND_CONNECTED_EDGE_CYPHER, params);
|
|
193
|
+
const personCounters = personRes.summary.counters.updates();
|
|
194
|
+
const orgRes = await tx.run(ORG_AND_WORKS_FOR_CYPHER, params);
|
|
195
|
+
const orgCounters = orgRes.summary.counters.updates();
|
|
196
|
+
const createdPersonNodes = personCounters.nodesCreated;
|
|
197
|
+
const createdOrgNodes = orgCounters.nodesCreated;
|
|
198
|
+
const personsTouched = params.rows.length;
|
|
199
|
+
const personsCreated = createdPersonNodes;
|
|
200
|
+
const personsMerged = personsTouched - personsCreated;
|
|
201
|
+
return {
|
|
202
|
+
createdPersons: personsCreated,
|
|
203
|
+
mergedPersons: personsMerged,
|
|
204
|
+
createdOrganizations: createdOrgNodes,
|
|
205
|
+
mergedOrganizations: 0,
|
|
206
|
+
createdEdges: personCounters.relationshipsCreated + orgCounters.relationshipsCreated,
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function normaliseLinkedinRow(row) {
|
|
213
|
+
if (!row.linkedinUrl || !row.linkedinUrl.trim()) {
|
|
214
|
+
throw new Error("linkedin-connections row missing linkedinUrl (natural key).");
|
|
215
|
+
}
|
|
216
|
+
if (!row.connectedOn || !/^\d{4}-\d{2}-\d{2}$/.test(row.connectedOn)) {
|
|
217
|
+
throw new Error(`linkedin-connections row connectedOn="${row.connectedOn}" is not ISO 8601 (YYYY-MM-DD). Parser must convert "23 Apr 2026" before calling.`);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
givenName: (row.givenName ?? "").trim(),
|
|
221
|
+
familyName: (row.familyName ?? "").trim(),
|
|
222
|
+
linkedinUrl: row.linkedinUrl.trim(),
|
|
223
|
+
email: row.email && row.email.trim() ? row.email.trim() : null,
|
|
224
|
+
company: row.company && row.company.trim() ? row.company.trim() : null,
|
|
225
|
+
title: row.title && row.title.trim() ? row.title.trim() : null,
|
|
226
|
+
connectedOn: row.connectedOn,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=memory-archive-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-archive-write.js","sourceRoot":"","sources":["../../src/tools/memory-archive-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
|
-
"start": "node dist/index.js"
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"smoke": "bash ./scripts/boot-smoke.sh",
|
|
11
|
+
"prepublish": "bash ./scripts/boot-smoke.sh"
|
|
10
12
|
},
|
|
11
13
|
"dependencies": {
|
|
12
14
|
"@anthropic-ai/sdk": "^0.81.0",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Memory MCP boot-smoke gate (Task 743).
|
|
3
|
+
#
|
|
4
|
+
# Spawns dist/index.js with a stub env, holds stdin open via `tail -f /dev/null`
|
|
5
|
+
# (the MCP SDK's StdioServerTransport exits on EOF — closing stdin would mask a
|
|
6
|
+
# clean boot as a failure), sleeps 2s, then asserts the process is still alive.
|
|
7
|
+
# A schema-loader throw, an import failure, or any other module-load fault
|
|
8
|
+
# would have killed the child by now and `kill -0` would return non-zero.
|
|
9
|
+
#
|
|
10
|
+
# Wired to the memory MCP `prepublish` script in package.json so a regression
|
|
11
|
+
# can never reach npm publish without the gate firing first. Local matrix:
|
|
12
|
+
#
|
|
13
|
+
# $ ./scripts/boot-smoke.sh
|
|
14
|
+
# [boot-smoke] memory ok
|
|
15
|
+
#
|
|
16
|
+
# $ # break the schema content, rebuild, retry
|
|
17
|
+
# $ ./scripts/boot-smoke.sh
|
|
18
|
+
# [boot-smoke] memory FAILED tail=<schema-loader throw>
|
|
19
|
+
# exit 1
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
23
|
+
MCP_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
|
|
24
|
+
ENTRY="$MCP_DIR/dist/index.js"
|
|
25
|
+
PLATFORM_ROOT=$(cd "$MCP_DIR/../../.." && pwd)
|
|
26
|
+
|
|
27
|
+
if [ ! -f "$ENTRY" ]; then
|
|
28
|
+
echo "[boot-smoke] memory FAILED tail=\"dist/index.js missing — run npm run build first\"" >&2
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
LOG_DIR=$(mktemp -d -t mcp-boot-smoke.XXXXXX)
|
|
33
|
+
trap 'rm -rf "$LOG_DIR"' EXIT
|
|
34
|
+
|
|
35
|
+
# stdin from a `tail -f /dev/null` keeps the StdioServerTransport open. The
|
|
36
|
+
# pipeline's left side is a tiny long-running process; the right side is the
|
|
37
|
+
# MCP server we are smoke-testing. Both are killed below.
|
|
38
|
+
# Disable pipefail for this block — when bash forks the pipeline, $! is the
|
|
39
|
+
# PID of the LAST command (the node process), which is what we test below.
|
|
40
|
+
set +o pipefail
|
|
41
|
+
|
|
42
|
+
tail -f /dev/null | env \
|
|
43
|
+
ACCOUNT_ID=boot-smoke-test \
|
|
44
|
+
PLATFORM_ROOT="$PLATFORM_ROOT" \
|
|
45
|
+
LOG_DIR="$LOG_DIR" \
|
|
46
|
+
STREAM_LOG_PATH="$LOG_DIR/stream.log" \
|
|
47
|
+
NEO4J_URI=bolt://localhost:7687 \
|
|
48
|
+
SESSION_ID=boot-smoke \
|
|
49
|
+
node "$ENTRY" > /dev/null 2>"$LOG_DIR/stderr-direct.log" &
|
|
50
|
+
|
|
51
|
+
NODE_PID=$!
|
|
52
|
+
sleep 2
|
|
53
|
+
|
|
54
|
+
if ! kill -0 "$NODE_PID" 2>/dev/null; then
|
|
55
|
+
TAIL=$(tail -n 5 "$LOG_DIR/stderr-direct.log" 2>/dev/null | tr '\n' ' ' | sed 's/"/\\"/g')
|
|
56
|
+
echo "[boot-smoke] memory FAILED tail=\"$TAIL\"" >&2
|
|
57
|
+
# Reap the tail process by killing its parent group (the pipeline subshell).
|
|
58
|
+
kill 0 2>/dev/null || true
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
echo "[boot-smoke] memory ok"
|
|
63
|
+
kill "$NODE_PID" 2>/dev/null || true
|
|
64
|
+
# Reap the still-running `tail -f /dev/null` so the script exits cleanly.
|
|
65
|
+
# `disown` first so bash does not print a job-control "Terminated" line
|
|
66
|
+
# when the pipeline subshell is killed by exit-trap.
|
|
67
|
+
{ disown -a 2>/dev/null; pkill -P $$ -f "tail -f /dev/null"; } 2>/dev/null || true
|
|
68
|
+
wait 2>/dev/null || true
|
|
69
|
+
exit 0
|
|
@@ -38,6 +38,28 @@ deterministic path through the shim is the only supported way to read
|
|
|
38
38
|
the graph, and any substitute path loses the read-only + namespace +
|
|
39
39
|
token-limit discipline the upstream server enforces.
|
|
40
40
|
|
|
41
|
+
## When the memory tools are absent
|
|
42
|
+
|
|
43
|
+
If `mcp__memory__memory-write`, `mcp__memory__memory-search`,
|
|
44
|
+
`mcp__memory__memory-rank`, `mcp__memory__memory-reindex`,
|
|
45
|
+
`mcp__memory__session-compact`, `mcp__memory__session-compact-status`, or
|
|
46
|
+
`mcp__memory__conversation-search` is missing from your tool list, the
|
|
47
|
+
memory MCP server failed to start on this device. Reply once with
|
|
48
|
+
exactly:
|
|
49
|
+
|
|
50
|
+
> The memory MCP server failed to start on this device. Run the admin
|
|
51
|
+
> system-status check to diagnose — do not retry by other routes.
|
|
52
|
+
|
|
53
|
+
Then stop. Do not search for a similarly-named tool via `ToolSearch`, do
|
|
54
|
+
not improvise via `maxy-graph-read_neo4j_cypher` (it is read-only — every
|
|
55
|
+
write goes through the schema-aware memory tools), do not paraphrase.
|
|
56
|
+
A missing memory tool is a deterministic failure of the per-account
|
|
57
|
+
memory server: the platform's `[mcp-init-error] server=memory` line in
|
|
58
|
+
the per-conversation stream log already names it; the user-facing reply
|
|
59
|
+
exists so the operator knows the session cannot proceed without an
|
|
60
|
+
operator-side fix (Task 743 propagates Task 560's loud-fail contract
|
|
61
|
+
from graph to memory).
|
|
62
|
+
|
|
41
63
|
## Mutation intents
|
|
42
64
|
|
|
43
65
|
The graph tools above are read-only. Every write intent has a schema-aware
|
|
@@ -165,7 +165,7 @@ The classifier returns `kind` strings from the closed enumeration above. `kind`
|
|
|
165
165
|
| `CREATED` | `UserProfile` → `Project` | Standalone-node anchor. |
|
|
166
166
|
| `UNDER` | `Project` → `Organization` | Optional Project context. |
|
|
167
167
|
| `AT` | `Section:Position` → `Organization` | Position's employer. |
|
|
168
|
-
| `PARTY` | `KnowledgeDocument` → `Person
|
|
168
|
+
| `PARTY` | `KnowledgeDocument` → `Person` or `Organization` | Contract Parties — written from `documentEdges`. |
|
|
169
169
|
| `DEFINES` | `Section:Definitions` → `DefinedTerm` | Contract definitions — written from per-section `related`. |
|
|
170
170
|
|
|
171
171
|
**Ontology-growth review query.** When a document accumulates several `:Section:Other` nodes, the operator (or admin agent) can run the following Cypher to surface candidate ontology additions:
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Existing-pi install-log redaction (Task 744).
|
|
3
|
+
#
|
|
4
|
+
# Idempotent one-shot remediation for Pis that completed installation BEFORE
|
|
5
|
+
# the install-log redaction landed at index.ts:152 / setup.sh:94. Scans every
|
|
6
|
+
# `install-*.log` in the configured logs directory and replaces every literal
|
|
7
|
+
# `set-initial-password ...<secret>` payload with `set-initial-password
|
|
8
|
+
# [REDACTED]`. Re-running the script is safe — already-redacted lines do not
|
|
9
|
+
# match the source pattern, so no further edits occur.
|
|
10
|
+
#
|
|
11
|
+
# Source patterns covered:
|
|
12
|
+
# 1. TS installer (packages/create-maxy/src/index.ts:152) — "[ISO] > sudo
|
|
13
|
+
# neo4j-admin dbms set-initial-password -- <secret>" or any args after
|
|
14
|
+
# "set-initial-password" (positional or "--" delimited).
|
|
15
|
+
# 2. Shell installer (platform/scripts/setup.sh, pre-fix variant) — "+ sudo
|
|
16
|
+
# neo4j-admin dbms set-initial-password <secret>" if bash -x had been on.
|
|
17
|
+
#
|
|
18
|
+
# A trailing marker line `[redact-install-logs] redacted=<n> file=<path>` is
|
|
19
|
+
# appended to each modified log so subsequent reads can identify which logs
|
|
20
|
+
# went through the remediation. Files with zero matches are left untouched.
|
|
21
|
+
#
|
|
22
|
+
# Default scan location: $HOME/.maxy/logs (the installer's LOG_DIR). Override
|
|
23
|
+
# with --dir <path> for non-default deployments.
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
LOG_DIR="${HOME}/.maxy/logs"
|
|
28
|
+
|
|
29
|
+
while [ $# -gt 0 ]; do
|
|
30
|
+
case "$1" in
|
|
31
|
+
--dir) LOG_DIR="$2"; shift 2 ;;
|
|
32
|
+
--help|-h)
|
|
33
|
+
cat <<USAGE
|
|
34
|
+
Usage: redact-install-logs.sh [--dir <log-dir>]
|
|
35
|
+
|
|
36
|
+
Redacts neo4j set-initial-password secrets from install-*.log files.
|
|
37
|
+
Default --dir: \$HOME/.maxy/logs
|
|
38
|
+
|
|
39
|
+
Idempotent — safe to re-run.
|
|
40
|
+
USAGE
|
|
41
|
+
exit 0 ;;
|
|
42
|
+
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
43
|
+
esac
|
|
44
|
+
done
|
|
45
|
+
|
|
46
|
+
if [ ! -d "$LOG_DIR" ]; then
|
|
47
|
+
echo "[redact-install-logs] log dir not found: $LOG_DIR (nothing to do)"
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
shopt -s nullglob
|
|
52
|
+
TOTAL_FILES=0
|
|
53
|
+
TOTAL_REDACTIONS=0
|
|
54
|
+
|
|
55
|
+
for f in "$LOG_DIR"/install-*.log; do
|
|
56
|
+
[ -f "$f" ] || continue
|
|
57
|
+
TOTAL_FILES=$((TOTAL_FILES + 1))
|
|
58
|
+
|
|
59
|
+
# Pattern: any "set-initial-password" line followed by one or more args.
|
|
60
|
+
# The replacement keeps the leading prefix (timestamp + cmd up through
|
|
61
|
+
# set-initial-password and an optional "--") and substitutes everything
|
|
62
|
+
# after with [REDACTED]. We anchor the replacement only when the remaining
|
|
63
|
+
# tail is non-empty AND not already "[REDACTED]" — making the script idempotent.
|
|
64
|
+
REDACTED_THIS_FILE=$(
|
|
65
|
+
perl -ne '
|
|
66
|
+
if (/set-initial-password(\s+--)?\s+(\S.*)$/ && $2 ne "[REDACTED]") {
|
|
67
|
+
print STDOUT "1\n";
|
|
68
|
+
}
|
|
69
|
+
' "$f" | wc -l | tr -d ' '
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if [ "$REDACTED_THIS_FILE" -gt 0 ]; then
|
|
73
|
+
perl -i -pe '
|
|
74
|
+
if (/set-initial-password(\s+--)?\s+(\S.*)$/ && $2 ne "[REDACTED]") {
|
|
75
|
+
s/set-initial-password(\s+--)?\s+\S.*$/set-initial-password${1} [REDACTED]/;
|
|
76
|
+
}
|
|
77
|
+
' "$f"
|
|
78
|
+
printf "[redact-install-logs] redacted=%d file=%s\n" "$REDACTED_THIS_FILE" "$f" >> "$f"
|
|
79
|
+
echo "[redact-install-logs] redacted=$REDACTED_THIS_FILE file=$f"
|
|
80
|
+
TOTAL_REDACTIONS=$((TOTAL_REDACTIONS + REDACTED_THIS_FILE))
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
echo "[redact-install-logs] summary files_scanned=$TOTAL_FILES total_redactions=$TOTAL_REDACTIONS"
|
|
85
|
+
exit 0
|
|
@@ -86,12 +86,20 @@ else
|
|
|
86
86
|
# Configure Neo4j for local use
|
|
87
87
|
sudo sed -i 's/#server.default_listen_address=0.0.0.0/server.default_listen_address=127.0.0.1/' /etc/neo4j/neo4j.conf
|
|
88
88
|
|
|
89
|
-
# Generate a strong random password and store it
|
|
89
|
+
# Generate a strong random password and store it.
|
|
90
|
+
# Password handling block is set +x bracketed so even bash -x setup.sh
|
|
91
|
+
# cannot print the substituted secret. The password is written to
|
|
92
|
+
# platform/config/.neo4j-password (chmod 600) — the only readable source.
|
|
93
|
+
# set-initial-password reads the secret via $(cat ...) so the literal
|
|
94
|
+
# never appears on the parent shell's command line, and stdout is
|
|
95
|
+
# discarded so neo4j-admin's own echo cannot leak it either (Task 744).
|
|
96
|
+
{ set +x; } 2>/dev/null
|
|
90
97
|
NEO4J_GENERATED_PASSWORD=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32)
|
|
91
98
|
mkdir -p "$INSTALL_DIR/platform/config"
|
|
92
|
-
|
|
99
|
+
printf '%s' "$NEO4J_GENERATED_PASSWORD" > "$INSTALL_DIR/platform/config/.neo4j-password"
|
|
93
100
|
chmod 600 "$INSTALL_DIR/platform/config/.neo4j-password"
|
|
94
|
-
|
|
101
|
+
unset NEO4J_GENERATED_PASSWORD
|
|
102
|
+
sudo neo4j-admin dbms set-initial-password "$(cat "$INSTALL_DIR/platform/config/.neo4j-password")" >/dev/null 2>&1
|
|
95
103
|
|
|
96
104
|
# Start and enable
|
|
97
105
|
sudo systemctl enable neo4j
|
|
@@ -139,6 +147,15 @@ else
|
|
|
139
147
|
cd "$INSTALL_DIR"
|
|
140
148
|
fi
|
|
141
149
|
|
|
150
|
+
# ------------------------------------------------------------------
|
|
151
|
+
# 6.5. Redact install-log credential leaks (Task 744 — idempotent).
|
|
152
|
+
# Pre-fix logs may contain plaintext neo4j passwords; this script scrubs
|
|
153
|
+
# every install-*.log to "[REDACTED]". Safe on already-clean logs.
|
|
154
|
+
# ------------------------------------------------------------------
|
|
155
|
+
if [ -x "$INSTALL_DIR/platform/scripts/redact-install-logs.sh" ]; then
|
|
156
|
+
bash "$INSTALL_DIR/platform/scripts/redact-install-logs.sh" || true
|
|
157
|
+
fi
|
|
158
|
+
|
|
142
159
|
# ------------------------------------------------------------------
|
|
143
160
|
# 7. Install dependencies and build
|
|
144
161
|
# ------------------------------------------------------------------
|