@rubytech/create-maxy-code 0.1.127 → 0.1.131
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 +10 -7
- package/package.json +1 -1
- package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts +5 -1
- package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts.map +1 -1
- package/payload/platform/lib/graph-write/dist/conversation-provenance.js +18 -11
- package/payload/platform/lib/graph-write/dist/conversation-provenance.js.map +1 -1
- package/payload/platform/lib/graph-write/src/conversation-provenance.ts +24 -16
- package/payload/platform/plugins/contacts/PLUGIN.md +1 -1
- package/payload/platform/plugins/docs/references/internals.md +1 -1
- package/payload/platform/plugins/docs/references/memory-guide.md +4 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +2 -6
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts.map +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js +46 -6
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js.map +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +28 -2
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -1
- package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +6 -3
- package/payload/platform/plugins/memory/PLUGIN.md +1 -1
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +3 -3
- 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 +16 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +11 -0
- 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 +11 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +12 -0
- package/payload/platform/templates/agents/public/IDENTITY.md +8 -2
- package/payload/platform/templates/specialists/agents/content-producer.md +4 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +2 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +2 -0
- package/payload/server/server.js +70 -11
package/dist/index.js
CHANGED
|
@@ -3524,14 +3524,17 @@ else {
|
|
|
3524
3524
|
}
|
|
3525
3525
|
catch { /* non-critical */ }
|
|
3526
3526
|
// Darwin has no VNC/X11 stack — `virtual` mode is Pi-only (tigervnc +
|
|
3527
|
-
// websockify + novnc, none of which exist on macOS).
|
|
3528
|
-
//
|
|
3529
|
-
//
|
|
3530
|
-
//
|
|
3531
|
-
//
|
|
3532
|
-
|
|
3527
|
+
// websockify + novnc, none of which exist on macOS). A preserved
|
|
3528
|
+
// DISPLAY_MODE=virtual is never operator intent on darwin — it is the
|
|
3529
|
+
// legacy default from installers before 0.1.127, when `virtual` was the
|
|
3530
|
+
// only initial value written. The guard fires whenever the resolved
|
|
3531
|
+
// value is not already `native`, covering both fresh installs and
|
|
3532
|
+
// upgrades that inherited the legacy default.
|
|
3533
|
+
if (process.platform === "darwin" && DISPLAY_MODE !== "native") {
|
|
3534
|
+
DISPLAY_MODE_SOURCE = preservedFromEnv
|
|
3535
|
+
? "darwin-auto (override-legacy-virtual)"
|
|
3536
|
+
: "darwin-auto (no VNC stack)";
|
|
3533
3537
|
DISPLAY_MODE = "native";
|
|
3534
|
-
DISPLAY_MODE_SOURCE = "darwin-auto (no VNC stack)";
|
|
3535
3538
|
}
|
|
3536
3539
|
}
|
|
3537
3540
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -10,7 +10,11 @@ export interface InjectConversationProvenanceParams {
|
|
|
10
10
|
writeLabels: readonly string[];
|
|
11
11
|
/**
|
|
12
12
|
* Value of `process.env.CONVERSATION_NODE_ID` at call time. Passed as a
|
|
13
|
-
* parameter so tests can supply it without mutating process.env.
|
|
13
|
+
* parameter so tests can supply it without mutating process.env. The
|
|
14
|
+
* value is the `conversationId` UUID property of the active
|
|
15
|
+
* `:AdminConversation`, not its Neo4j elementId — the env-var name is
|
|
16
|
+
* historical. The lookup MATCHes `(c:Conversation {conversationId, accountId})`
|
|
17
|
+
* and reads `elementId(c)` itself to compose the synthetic edge.
|
|
14
18
|
*/
|
|
15
19
|
conversationNodeId: string | undefined;
|
|
16
20
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-provenance.d.ts","sourceRoot":"","sources":["../src/conversation-provenance.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"conversation-provenance.d.ts","sourceRoot":"","sources":["../src/conversation-provenance.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAK5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC5C,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,sGAAsG;IACtG,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;;;;OAOG;IACH,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,kCAAkC,GACzC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAmE9B"}
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
//
|
|
4
4
|
// MCP tool wrappers that write to ACTION_PROVENANCE_LABELS call this helper
|
|
5
5
|
// before `writeNodeWithEdges`. When the admin server has stamped
|
|
6
|
-
// `CONVERSATION_NODE_ID` in the MCP spawn env (the
|
|
7
|
-
// `:AdminConversation`
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// `CONVERSATION_NODE_ID` in the MCP spawn env (the `conversationId` UUID of
|
|
7
|
+
// the active `:AdminConversation` — name is historical, value is the UUID
|
|
8
|
+
// property, not the Neo4j elementId, so it can be threaded through spawn
|
|
9
|
+
// without an extra Neo4j round-trip at session-start), the helper:
|
|
10
|
+
// - verifies a `:Conversation` exists with that conversationId AND the
|
|
11
|
+
// write's accountId (account isolation is enforced inside the MATCH,
|
|
12
|
+
// not as a separate gate)
|
|
13
|
+
// - verifies the node carries one of PROVENANCE_SOURCE_LABELS
|
|
10
14
|
// - prepends a synthetic inbound `:PRODUCED` edge to the relationships array
|
|
11
15
|
// so the gate inside `writeNodeWithEdges` accepts the write
|
|
12
16
|
//
|
|
@@ -38,7 +42,14 @@ async function injectConversationProvenance(params) {
|
|
|
38
42
|
return original;
|
|
39
43
|
let lookup;
|
|
40
44
|
try {
|
|
41
|
-
|
|
45
|
+
// MATCH on (conversationId, accountId) — the account filter is part of
|
|
46
|
+
// the natural key, so a cross-account env-stamp produces zero rows
|
|
47
|
+
// (a clean `node-not-found`, not a silent leak). `elementId(n)` is
|
|
48
|
+
// still returned because `writeNodeWithEdges` composes the synthetic
|
|
49
|
+
// edge by elementId, not by conversationId — Neo4j's edge composition
|
|
50
|
+
// is internal-id based.
|
|
51
|
+
lookup = await session.run(`MATCH (c:Conversation {conversationId: $cid, accountId: $accountId})
|
|
52
|
+
RETURN elementId(c) AS elementId, labels(c) AS labels LIMIT 1`, { cid: conversationNodeId, accountId });
|
|
42
53
|
}
|
|
43
54
|
catch (err) {
|
|
44
55
|
process.stderr.write(`[${logNamespace}] [provenance-missing] tool=${tool} reason=driver-error message=${err instanceof Error ? err.message : String(err)} conversationNodeId=${conversationNodeId}\n`);
|
|
@@ -48,23 +59,19 @@ async function injectConversationProvenance(params) {
|
|
|
48
59
|
process.stderr.write(`[${logNamespace}] [provenance-missing] tool=${tool} reason=node-not-found conversationNodeId=${conversationNodeId}\n`);
|
|
49
60
|
return original;
|
|
50
61
|
}
|
|
62
|
+
const elementId = lookup.records[0].get("elementId");
|
|
51
63
|
const labels = lookup.records[0].get("labels");
|
|
52
|
-
const sourceAccountId = lookup.records[0].get("accountId");
|
|
53
64
|
const sourceLabel = labels.find((l) => index_js_1.PROVENANCE_SOURCE_LABELS.has(l));
|
|
54
65
|
if (!sourceLabel) {
|
|
55
66
|
process.stderr.write(`[${logNamespace}] [provenance-missing] tool=${tool} reason=wrong-source-label labels=${labels.join(",")} conversationNodeId=${conversationNodeId}\n`);
|
|
56
67
|
return original;
|
|
57
68
|
}
|
|
58
|
-
if (sourceAccountId !== accountId) {
|
|
59
|
-
process.stderr.write(`[${logNamespace}] [provenance-missing] tool=${tool} reason=account-mismatch sourceAccountId=${sourceAccountId.slice(0, 8)} writeAccountId=${accountId.slice(0, 8)} conversationNodeId=${conversationNodeId}\n`);
|
|
60
|
-
return original;
|
|
61
|
-
}
|
|
62
69
|
process.stderr.write(`[${logNamespace}] [provenance-inject] tool=${tool} from=${sourceLabel}:${conversationNodeId}\n`);
|
|
63
70
|
return [
|
|
64
71
|
{
|
|
65
72
|
type: "PRODUCED",
|
|
66
73
|
direction: "incoming",
|
|
67
|
-
targetNodeId:
|
|
74
|
+
targetNodeId: elementId,
|
|
68
75
|
},
|
|
69
76
|
...original,
|
|
70
77
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-provenance.js","sourceRoot":"","sources":["../src/conversation-provenance.ts"],"names":[],"mappings":";AAAA,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,iEAAiE;AACjE,
|
|
1
|
+
{"version":3,"file":"conversation-provenance.js","sourceRoot":"","sources":["../src/conversation-provenance.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
//
|
|
3
3
|
// MCP tool wrappers that write to ACTION_PROVENANCE_LABELS call this helper
|
|
4
4
|
// before `writeNodeWithEdges`. When the admin server has stamped
|
|
5
|
-
// `CONVERSATION_NODE_ID` in the MCP spawn env (the
|
|
6
|
-
// `:AdminConversation`
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// `CONVERSATION_NODE_ID` in the MCP spawn env (the `conversationId` UUID of
|
|
6
|
+
// the active `:AdminConversation` — name is historical, value is the UUID
|
|
7
|
+
// property, not the Neo4j elementId, so it can be threaded through spawn
|
|
8
|
+
// without an extra Neo4j round-trip at session-start), the helper:
|
|
9
|
+
// - verifies a `:Conversation` exists with that conversationId AND the
|
|
10
|
+
// write's accountId (account isolation is enforced inside the MATCH,
|
|
11
|
+
// not as a separate gate)
|
|
12
|
+
// - verifies the node carries one of PROVENANCE_SOURCE_LABELS
|
|
9
13
|
// - prepends a synthetic inbound `:PRODUCED` edge to the relationships array
|
|
10
14
|
// so the gate inside `writeNodeWithEdges` accepts the write
|
|
11
15
|
//
|
|
@@ -41,7 +45,11 @@ export interface InjectConversationProvenanceParams {
|
|
|
41
45
|
writeLabels: readonly string[];
|
|
42
46
|
/**
|
|
43
47
|
* Value of `process.env.CONVERSATION_NODE_ID` at call time. Passed as a
|
|
44
|
-
* parameter so tests can supply it without mutating process.env.
|
|
48
|
+
* parameter so tests can supply it without mutating process.env. The
|
|
49
|
+
* value is the `conversationId` UUID property of the active
|
|
50
|
+
* `:AdminConversation`, not its Neo4j elementId — the env-var name is
|
|
51
|
+
* historical. The lookup MATCHes `(c:Conversation {conversationId, accountId})`
|
|
52
|
+
* and reads `elementId(c)` itself to compose the synthetic edge.
|
|
45
53
|
*/
|
|
46
54
|
conversationNodeId: string | undefined;
|
|
47
55
|
/**
|
|
@@ -75,9 +83,16 @@ export async function injectConversationProvenance(
|
|
|
75
83
|
|
|
76
84
|
let lookup;
|
|
77
85
|
try {
|
|
86
|
+
// MATCH on (conversationId, accountId) — the account filter is part of
|
|
87
|
+
// the natural key, so a cross-account env-stamp produces zero rows
|
|
88
|
+
// (a clean `node-not-found`, not a silent leak). `elementId(n)` is
|
|
89
|
+
// still returned because `writeNodeWithEdges` composes the synthetic
|
|
90
|
+
// edge by elementId, not by conversationId — Neo4j's edge composition
|
|
91
|
+
// is internal-id based.
|
|
78
92
|
lookup = await session.run(
|
|
79
|
-
`MATCH (
|
|
80
|
-
|
|
93
|
+
`MATCH (c:Conversation {conversationId: $cid, accountId: $accountId})
|
|
94
|
+
RETURN elementId(c) AS elementId, labels(c) AS labels LIMIT 1`,
|
|
95
|
+
{ cid: conversationNodeId, accountId },
|
|
81
96
|
);
|
|
82
97
|
} catch (err) {
|
|
83
98
|
process.stderr.write(
|
|
@@ -93,8 +108,8 @@ export async function injectConversationProvenance(
|
|
|
93
108
|
return original;
|
|
94
109
|
}
|
|
95
110
|
|
|
111
|
+
const elementId = lookup.records[0].get("elementId") as string;
|
|
96
112
|
const labels = lookup.records[0].get("labels") as string[];
|
|
97
|
-
const sourceAccountId = lookup.records[0].get("accountId") as string;
|
|
98
113
|
const sourceLabel = labels.find((l) => PROVENANCE_SOURCE_LABELS.has(l));
|
|
99
114
|
|
|
100
115
|
if (!sourceLabel) {
|
|
@@ -104,13 +119,6 @@ export async function injectConversationProvenance(
|
|
|
104
119
|
return original;
|
|
105
120
|
}
|
|
106
121
|
|
|
107
|
-
if (sourceAccountId !== accountId) {
|
|
108
|
-
process.stderr.write(
|
|
109
|
-
`[${logNamespace}] [provenance-missing] tool=${tool} reason=account-mismatch sourceAccountId=${sourceAccountId.slice(0, 8)} writeAccountId=${accountId.slice(0, 8)} conversationNodeId=${conversationNodeId}\n`,
|
|
110
|
-
);
|
|
111
|
-
return original;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
122
|
process.stderr.write(
|
|
115
123
|
`[${logNamespace}] [provenance-inject] tool=${tool} from=${sourceLabel}:${conversationNodeId}\n`,
|
|
116
124
|
);
|
|
@@ -119,7 +127,7 @@ export async function injectConversationProvenance(
|
|
|
119
127
|
{
|
|
120
128
|
type: "PRODUCED",
|
|
121
129
|
direction: "incoming",
|
|
122
|
-
targetNodeId:
|
|
130
|
+
targetNodeId: elementId,
|
|
123
131
|
},
|
|
124
132
|
...original,
|
|
125
133
|
];
|
|
@@ -54,7 +54,7 @@ Tools are available via the `contacts` MCP server.
|
|
|
54
54
|
|
|
55
55
|
## Provenance anchoring
|
|
56
56
|
|
|
57
|
-
`contact-create` writes a `:Person` node, which is one of the action-provenance-gated labels in `writeNodeWithEdges`. The write must carry an inbound `:PRODUCED` edge from `:Task`, `:Conversation`, or `:Message`. There is no agent-visible field for this — the admin server stamps `CONVERSATION_NODE_ID` (the
|
|
57
|
+
`contact-create` writes a `:Person` node, which is one of the action-provenance-gated labels in `writeNodeWithEdges`. The write must carry an inbound `:PRODUCED` edge from `:Task`, `:Conversation`, or `:Message`. There is no agent-visible field for this — the admin server stamps `CONVERSATION_NODE_ID` (the `conversationId` UUID of the active `:AdminConversation`) at MCP spawn time, and the wrapper auto-injects the `:PRODUCED` edge via `injectConversationProvenance`. The helper MATCHes `(c:Conversation {conversationId, accountId})` — account isolation is enforced inside the natural key, not as a separate gate, so a cross-account env-stamp returns zero rows and never injects.
|
|
58
58
|
|
|
59
59
|
The agent passes whichever organic relationships are appropriate (`PARTICIPATES_IN` to a Conversation, `WORKS_FOR` to an Organization, `KNOWN_VIA` to a referral source) — the provenance edge is composed automatically and is not an agent concern. When the env-stamp resolution fails (loud `[mcp:contacts] [provenance-missing] reason=<...>`), the downstream gate emits `missing-provenance` and the write fails closed.
|
|
60
60
|
|
|
@@ -527,7 +527,7 @@ The doctrine is enforced at the storage primitive: writes to `:Person`, `:UserPr
|
|
|
527
527
|
|
|
528
528
|
Two surfaces emit the lifecycle: agent-driven actions call `work-create`/`work-update`/`work-complete` over MCP (`work-create` accepts `kind`, the canonical `inputsProvided` call-shape record, `inputs` + `inputSchema` for the operator-meaningful form payload, and `raisedDuringConversationKey` to resolve the `RAISED_DURING` edge). Shell-driven actions wrap their script invocation in [platform/ui/app/lib/cloudflare-task-tracker.ts](../../../ui/app/lib/cloudflare-task-tracker.ts) (cloudflare is the first; installer / brand-publish / OAuth-login deferred). Both surfaces emit the same `[task] action-start|step|done` log lines so operators can grep one channel uniformly. Both also call the central `redactSecrets` primitive ([platform/lib/task-secrets/](../../../lib/task-secrets/)) to strip schema-tagged secret keys before persisting `inputs.<field>` props on the Task — see `.docs/neo4j.md § Audit Task input contract` for the contract that replaces per-kind allow-lists.
|
|
529
529
|
|
|
530
|
-
Two surfaces feed the gate. (1) **Workflow path:** `memory-write` accepts an optional `producedByTaskId` parameter. When set, an inbound `:PRODUCED` edge from that Task is composed into the write's relationships before the gate runs — the typical agent-side pattern is to call `work-create` at the start of an autonomous flow, capture `taskId`, and pass it as `producedByTaskId` on every subsequent `memory-write` for a gated label. The gate verifies Task and write share the same `accountId`; mismatch is rejected loud. (2) **Direct-ask path:** the admin server resolves the active `:AdminConversation`
|
|
530
|
+
Two surfaces feed the gate. (1) **Workflow path:** `memory-write` accepts an optional `producedByTaskId` parameter. When set, an inbound `:PRODUCED` edge from that Task is composed into the write's relationships before the gate runs — the typical agent-side pattern is to call `work-create` at the start of an autonomous flow, capture `taskId`, and pass it as `producedByTaskId` on every subsequent `memory-write` for a gated label. The gate verifies Task and write share the same `accountId`; mismatch is rejected loud. (2) **Direct-ask path:** the admin server resolves the active `:AdminConversation`'s `conversationId` UUID and stamps it as `CONVERSATION_NODE_ID` in the spawn env at PTY-spawn time. The same stamp propagates onto specialist subagent spawns the admin dispatches (Task 382) so listing-curator, content-producer, database-operator etc. inherit the same conversation anchor. The `contact-create` and `memory-write` wrappers call `injectConversationProvenance` (exported from [`@maxy/graph-write`](../../../lib/graph-write/src/conversation-provenance.ts)) which MATCHes `(c:Conversation {conversationId, accountId})` — account isolation is part of the natural key, not a separate gate — and prepends the synthetic `:PRODUCED` edge (composed by Neo4j elementId, which the helper reads off the MATCH). No agent-visible schema field changes. `memory-write` uses the env-stamp only as a fallback when `producedByTaskId` is unset; `contact-create` has no `producedByTaskId` parameter today and relies on the env-stamp alone. Autonomous (cron-driven) specialists with no parent conversation legitimately have no env-stamp; those must thread `producedByTaskId`.
|
|
531
531
|
|
|
532
532
|
Operator audit cyphers:
|
|
533
533
|
- "What entities did this conversation's actions produce?" — `MATCH (c:AdminConversation {conversationId:$id})<-[:RAISED_DURING]-(t:Task)-[:PRODUCED]->(e) RETURN labels(e), e.name, t.kind, t.status`
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Memory Guide
|
|
2
2
|
|
|
3
|
+
## Brain-first lookup
|
|
4
|
+
|
|
5
|
+
The graph is the brain, and every turn that needs to know something runs the same five-step loop in order: (1) classify the question (entity, temporal, event, general, or none — the inbound gateway emits this as `retrievalClass`), (2) read the graph with `memory-search` (and `profile-read` when the question is about the operator) as the first tool call of the turn, (3) walk one hop to hydrate a partial hit before calling it a miss, (4) call an external tool only when steps 2–3 confirmed the graph has nothing useful, and (5) write the external evidence back via `database-operator`. The loop is what makes the next turn smarter; an external call whose result is never persisted is a leak in the brain. `retrievalClass = none` (greetings, meta-instructions) is the only exception. Operator-facing doctrine lives in [`.docs/brain-first.md`](../../../.docs/brain-first.md).
|
|
6
|
+
|
|
3
7
|
## How Memory Works
|
|
4
8
|
|
|
5
9
|
{{productName}} maintains a graph of everything you've told it. Contacts, conversations, preferences, relationships, business context — all stored as connected nodes in a local Neo4j database on your Raspberry Pi.
|
|
@@ -15,7 +15,7 @@ const server = new McpServer({
|
|
|
15
15
|
name: "maxy-graph-viewer",
|
|
16
16
|
version: "0.1.0",
|
|
17
17
|
});
|
|
18
|
-
eagerTool(server, "graph-render", "Render
|
|
18
|
+
eagerTool(server, "graph-render", "Render a PNG of an account-scoped Neo4j subgraph rooted at a given nodeId, walking up to hopDepth hops (0-3). Writes the PNG to data/accounts/<accountId>/output/graph-render/ and returns {ok:true, path, nodeCount, edgeCount, width, height, truncated?}. Callers deliver the image to the operator by calling SendUserFile with the returned path — MCP image content blocks are not surfaced in the maxy-code chat UI. The image uses the same colour palette and short-label strings as the live /graph page; node positions differ (d3-force, not vis-network) and are auto-fit to the canvas. Subgraphs above 200 nodes are capped and the envelope carries truncated={rendered,total}. Trashed nodes are excluded. Use when the operator asks to see what a subgraph looks like and you want to avoid making them switch to the admin UI on a different device.", {
|
|
19
19
|
accountId: z.string().min(1).describe("Operator account id. The root nodeId must belong to this account or the tool refuses with reason=scope."),
|
|
20
20
|
nodeId: z.string().min(1).describe("elementId of the root node, as returned by memory-search or other graph tools."),
|
|
21
21
|
hopDepth: z.number().int().min(0).max(3).describe("Number of hops to walk from the root (0 = just the root). Max 3."),
|
|
@@ -34,6 +34,7 @@ eagerTool(server, "graph-render", "Render an inline PNG of an account-scoped Neo
|
|
|
34
34
|
}
|
|
35
35
|
const envelope = {
|
|
36
36
|
ok: true,
|
|
37
|
+
path: result.path,
|
|
37
38
|
width: result.width,
|
|
38
39
|
height: result.height,
|
|
39
40
|
nodeCount: result.nodeCount,
|
|
@@ -43,11 +44,6 @@ eagerTool(server, "graph-render", "Render an inline PNG of an account-scoped Neo
|
|
|
43
44
|
envelope.truncated = result.truncated;
|
|
44
45
|
return {
|
|
45
46
|
content: [
|
|
46
|
-
{
|
|
47
|
-
type: "image",
|
|
48
|
-
data: result.pngBase64,
|
|
49
|
-
mimeType: "image/png",
|
|
50
|
-
},
|
|
51
47
|
{
|
|
52
48
|
type: "text",
|
|
53
49
|
text: JSON.stringify(envelope, null, 2),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAC7E,aAAa,CAAC,cAAc,CAAC,CAAC;AAE9B,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,mBAAmB;IACzB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,SAAS,CACP,MAAM,EACN,cAAc,EACd,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAC7E,aAAa,CAAC,cAAc,CAAC,CAAC;AAE9B,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,mBAAmB;IACzB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,SAAS,CACP,MAAM,EACN,cAAc,EACd,00BAA00B,EAC10B;IACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,yGAAyG,CAAC;IAChJ,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,gFAAgF,CAAC;IACpH,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,kEAAkE,CAAC;CACtH,EACD,KAAK,EAAE,IAA6D,EAAE,EAAE;IACtE,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,IAAI;YACb,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,EAC7D,IAAI,EACJ,CAAC,CACF;iBACF;aACF;SACF,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAA4B;QACxC,EAAE,EAAE,IAAI;QACR,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC;IACF,IAAI,MAAM,CAAC,SAAS;QAAE,QAAQ,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IAC5D,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;aACxC;SACF;KACF,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;IAC9B,MAAM,WAAW,EAAE,CAAC;IACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;IAC/B,MAAM,WAAW,EAAE,CAAC;IACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/render/layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAcH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/render/layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAcH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AA6BD,wBAAgB,WAAW,CACzB,KAAK,EAAE,YAAY,EAAE,EACrB,KAAK,EAAE,YAAY,EAAE,EACrB,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD,YAAY,CAsFd"}
|
|
@@ -32,6 +32,7 @@ const ITERATIONS = 300;
|
|
|
32
32
|
const LINK_DISTANCE = 90;
|
|
33
33
|
const CHARGE_STRENGTH = -240;
|
|
34
34
|
const CENTER_STRENGTH = 0.05;
|
|
35
|
+
const FIT_PADDING = 60;
|
|
35
36
|
export function layoutGraph(nodes, edges, opts) {
|
|
36
37
|
const seed = opts.seed ?? 1;
|
|
37
38
|
const rand = seededRandom(seed);
|
|
@@ -66,12 +67,51 @@ export function layoutGraph(nodes, edges, opts) {
|
|
|
66
67
|
.force("y", forceY(cy).strength(CENTER_STRENGTH))
|
|
67
68
|
.stop();
|
|
68
69
|
simulation.tick(ITERATIONS);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
// Fit-to-content pass. The force simulation settles into a cluster that
|
|
71
|
+
// can be much smaller than the canvas (typical for sparse subgraphs of a
|
|
72
|
+
// few nodes), leaving the operator with two tiny dots in the middle of a
|
|
73
|
+
// 1200×900 image. Compute the bounding box of the settled positions and
|
|
74
|
+
// apply a uniform scale + translate so the cluster fills the canvas with
|
|
75
|
+
// a `FIT_PADDING` border. Aspect ratio is preserved. Degenerate cases
|
|
76
|
+
// (single node, or all nodes collapsed onto one point) skip scaling and
|
|
77
|
+
// are centred at (cx, cy).
|
|
78
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
79
|
+
for (const n of simNodes) {
|
|
80
|
+
const x = n.x ?? cx;
|
|
81
|
+
const y = n.y ?? cy;
|
|
82
|
+
if (x < minX)
|
|
83
|
+
minX = x;
|
|
84
|
+
if (x > maxX)
|
|
85
|
+
maxX = x;
|
|
86
|
+
if (y < minY)
|
|
87
|
+
minY = y;
|
|
88
|
+
if (y > maxY)
|
|
89
|
+
maxY = y;
|
|
90
|
+
}
|
|
91
|
+
const bboxW = maxX - minX;
|
|
92
|
+
const bboxH = maxY - minY;
|
|
93
|
+
const targetW = opts.width - 2 * FIT_PADDING;
|
|
94
|
+
const targetH = opts.height - 2 * FIT_PADDING;
|
|
95
|
+
// Degenerate when a single node, or all nodes collapsed within one pixel.
|
|
96
|
+
const degenerate = simNodes.length <= 1 || (bboxW < 1 && bboxH < 1);
|
|
97
|
+
const scale = degenerate
|
|
98
|
+
? 1
|
|
99
|
+
: Math.min(targetW / Math.max(bboxW, 1), targetH / Math.max(bboxH, 1));
|
|
100
|
+
const outNodes = simNodes.map((n) => {
|
|
101
|
+
const x = n.x ?? cx;
|
|
102
|
+
const y = n.y ?? cy;
|
|
103
|
+
if (degenerate)
|
|
104
|
+
return { id: n.id, labels: n.labels, x: cx, y: cy };
|
|
105
|
+
// Centre the scaled bbox on the canvas centre.
|
|
106
|
+
const scaledCx = (minX + bboxW / 2) * scale;
|
|
107
|
+
const scaledCy = (minY + bboxH / 2) * scale;
|
|
108
|
+
return {
|
|
109
|
+
id: n.id,
|
|
110
|
+
labels: n.labels,
|
|
111
|
+
x: x * scale - scaledCx + cx,
|
|
112
|
+
y: y * scale - scaledCy + cy,
|
|
113
|
+
};
|
|
114
|
+
});
|
|
75
115
|
return { nodes: outNodes, edges: validEdges, iterations: ITERATIONS };
|
|
76
116
|
}
|
|
77
117
|
//# sourceMappingURL=layout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/render/layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EACL,eAAe,EACf,SAAS,EACT,aAAa,EACb,WAAW,EACX,MAAM,EACN,MAAM,GAIP,MAAM,UAAU,CAAC;AAwBlB;;;;;GAKG;AACH,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC;IACvB,OAAO,GAAG,EAAE;QACV,KAAK,GAAG,CAAC,KAAK,GAAG,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAC7C,OAAO,KAAK,GAAG,WAAW,CAAC;IAC7B,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,GAAG,GAAG,CAAC;AACvB,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,MAAM,eAAe,GAAG,CAAC,GAAG,CAAC;AAC7B,MAAM,eAAe,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/render/layout.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-render.d.ts","sourceRoot":"","sources":["../../src/tools/graph-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;
|
|
1
|
+
{"version":3,"file":"graph-render.d.ts","sourceRoot":"","sources":["../../src/tools/graph-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA6BH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,cAAc,GAAG,YAAY,GAAG,eAAe,CAAC;IAC5E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACjD;AAED,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAMxE,wBAAsB,WAAW,CAC/B,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,iBAAiB,CAAC,CA+K5B"}
|
|
@@ -18,9 +18,23 @@
|
|
|
18
18
|
* parameterise variable-length path bounds. Anything outside that range
|
|
19
19
|
* fails fast at the param-validation layer, well before string assembly.
|
|
20
20
|
*/
|
|
21
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { resolve } from "node:path";
|
|
21
23
|
import { getSession } from "../lib/neo4j.js";
|
|
22
24
|
import { layoutGraph } from "../render/layout.js";
|
|
23
25
|
import { drawGraph } from "../render/draw.js";
|
|
26
|
+
function getAccountsDir() {
|
|
27
|
+
const root = process.env.MAXY_PLATFORM_ROOT;
|
|
28
|
+
if (!root) {
|
|
29
|
+
throw new Error("[graph-render] MAXY_PLATFORM_ROOT not set — cannot resolve account output dir.");
|
|
30
|
+
}
|
|
31
|
+
return resolve(root, "..", "data/accounts");
|
|
32
|
+
}
|
|
33
|
+
function slugifyElementId(elementId) {
|
|
34
|
+
// Neo4j elementIds use `:` as a separator; filesystem-safe replacement
|
|
35
|
+
// keeps the digest readable while avoiding path-segment confusion.
|
|
36
|
+
return elementId.replace(/[:/\\]/g, "_");
|
|
37
|
+
}
|
|
24
38
|
const NODE_CAP = 200;
|
|
25
39
|
const MAX_HOP_DEPTH = 3;
|
|
26
40
|
const IMAGE_WIDTH = 1200;
|
|
@@ -146,16 +160,28 @@ export async function graphRender(params) {
|
|
|
146
160
|
message: `Canvas draw failed: ${message}`,
|
|
147
161
|
};
|
|
148
162
|
}
|
|
163
|
+
// Write the PNG to a deterministic per-account output path. Operator
|
|
164
|
+
// delivery is via SendUserFile in the calling skill — MCP `image`
|
|
165
|
+
// content blocks do not surface in the maxy-code chat transport
|
|
166
|
+
// (see Task 384). The slug+hopDepth pair makes repeated renders of
|
|
167
|
+
// the same view overwrite the same file rather than accumulating.
|
|
168
|
+
const accountsDir = getAccountsDir();
|
|
169
|
+
const outputDir = resolve(accountsDir, accountId, "output", "graph-render");
|
|
170
|
+
mkdirSync(outputDir, { recursive: true });
|
|
171
|
+
const fileName = `${slugifyElementId(nodeId)}-h${hopDepth}.png`;
|
|
172
|
+
const outputPath = resolve(outputDir, fileName);
|
|
173
|
+
writeFileSync(outputPath, pngBuffer);
|
|
149
174
|
const truncated = totalCount > nodeRows.length
|
|
150
175
|
? { rendered: nodeRows.length, total: totalCount }
|
|
151
176
|
: undefined;
|
|
152
177
|
const ms = Date.now() - t0;
|
|
153
178
|
logLine(`[graph-render] ok account=${accountId} ms=${ms} bytes=${pngBuffer.length} ` +
|
|
154
179
|
`nodeCount=${nodeRows.length} edgeCount=${edges.length} ` +
|
|
155
|
-
`truncated=${truncated ? "true" : "false"} iterations=${layout.iterations}`
|
|
180
|
+
`truncated=${truncated ? "true" : "false"} iterations=${layout.iterations} ` +
|
|
181
|
+
`path=${outputPath}`);
|
|
156
182
|
return {
|
|
157
183
|
ok: true,
|
|
158
|
-
|
|
184
|
+
path: outputPath,
|
|
159
185
|
width: IMAGE_WIDTH,
|
|
160
186
|
height: IMAGE_HEIGHT,
|
|
161
187
|
nodeCount: nodeRows.length,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-render.js","sourceRoot":"","sources":["../../src/tools/graph-render.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"graph-render.js","sourceRoot":"","sources":["../../src/tools/graph-render.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,9 +14,12 @@ When the operator asks for a picture of a subgraph:
|
|
|
14
14
|
asks for the wider neighbourhood. Depth 3 on a busy node will hit the
|
|
15
15
|
200-node cap and return a `truncated` envelope.
|
|
16
16
|
3. Call `graph-render` with `{ accountId, nodeId, hopDepth }`.
|
|
17
|
-
4. If the result is `ok: true`,
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
4. If the result is `ok: true`, call `SendUserFile` with the returned
|
|
18
|
+
`path` and a one-line caption describing the subgraph. Tell the
|
|
19
|
+
operator how many nodes are in view and, if `truncated` is present,
|
|
20
|
+
that there were more nodes than the cap. The image will not appear
|
|
21
|
+
in chat until you deliver it via `SendUserFile` — `graph-render` only
|
|
22
|
+
writes the file.
|
|
20
23
|
5. If the result is `ok: false`, surface the structured `reason` and
|
|
21
24
|
`message` to the operator verbatim. Do not retry with a different
|
|
22
25
|
nodeId without their input. Do not fall back to Mermaid, ASCII, or any
|
|
@@ -147,7 +147,7 @@ Writes targeting `:Person`, `:UserProfile`, `:AdminUser`, `:Organization`, `:Loc
|
|
|
147
147
|
|
|
148
148
|
- **Workflow path — `producedByTaskId`.** `memory-write` accepts an optional `producedByTaskId` parameter. When set, the platform composes an inbound `:PRODUCED` edge from that `:Task` node into the write's `relationships` array before the write runs. The Task and the new node must share the same `accountId`; mismatch is rejected loud. The typical pattern: call `work-create` at the start of an autonomous flow (onboarding skill, cloudflare tunnel-login) with `kind` and `raisedDuringConversationKey`; capture the returned `taskId`; pass it as `producedByTaskId` on every subsequent `memory-write` for one of the gated labels.
|
|
149
149
|
|
|
150
|
-
- **Direct-ask path — `CONVERSATION_NODE_ID` env-stamp.** When `producedByTaskId` is not provided (typical of direct admin asks like "add Anneke as person"), `memory-write` falls back to the `CONVERSATION_NODE_ID` env var
|
|
150
|
+
- **Direct-ask path — `CONVERSATION_NODE_ID` env-stamp.** When `producedByTaskId` is not provided (typical of direct admin asks like "add Anneke as person"), `memory-write` falls back to the `CONVERSATION_NODE_ID` env var. The admin server stamps it at PTY spawn time on both admin sessions and on the specialist subagent spawns the admin dispatches (Task 382 — listing-curator, content-producer, database-operator and friends inherit the same anchor). The value is the `conversationId` UUID of the active `:AdminConversation`; the wrapper's `injectConversationProvenance` MATCHes `(c:Conversation {conversationId, accountId})` so account isolation is enforced inside the natural key — a cross-account stamp returns zero rows and never injects. Autonomous spawns with no parent conversation (cron, scheduled-task) legitimately have no env-stamp; those must thread `producedByTaskId` via `work-create` instead. The env-stamp is invisible to the agent (no schema field changes); the LLM never decides whether to use it.
|
|
151
151
|
|
|
152
152
|
## Graph Hygiene
|
|
153
153
|
|
|
@@ -96,7 +96,7 @@ If the source emits multiple distinct listing events tied to a single underlying
|
|
|
96
96
|
## Relationship Patterns
|
|
97
97
|
|
|
98
98
|
```
|
|
99
|
-
(:Listing)-[:LISTED_BY]->(:
|
|
99
|
+
(:Listing)-[:LISTED_BY]->(:Organization {brandSlug})
|
|
100
100
|
(:Listing)-[:FOR_PROPERTY]->(:Property)
|
|
101
101
|
(:Listing)-[:DEPICTS]->(:ImageObject)
|
|
102
102
|
(:Viewing)-[:FOR_LISTING]->(:Listing)
|
|
@@ -105,6 +105,6 @@ If the source emits multiple distinct listing events tied to a single underlying
|
|
|
105
105
|
(:Offer)-[:FROM_APPLICANT]->(:Person)
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
`(:Listing)-[:LISTED_BY]->(:
|
|
108
|
+
`(:Listing)-[:LISTED_BY]->(:Organization {brandSlug})` is the mandatory parent edge that every Listing carries — it satisfies the ≥1-adjacency write-gate doctrine ([[feedback_graph_hierarchy_doctrine]]) and names the estate agency that listed the property. The target is `:Organization` keyed by `brandSlug`, not `:LocalBusiness`. `:LocalBusiness` is the per-account installer-seeded singleton (the account's own business — Real Agent itself), held immutable by the write gate; the agencies whose listings ingest through the curator are third-party businesses and live as `:Organization`. `[:FOR_PROPERTY]` is composed additionally when the underlying physical `:Property` node is known (full structural data captured); it is optional because Listings frequently land before Property structural data has been collected.
|
|
109
109
|
|
|
110
|
-
`(:Listing)-[:DEPICTS]->(:ImageObject)` is written by the curator once per image present on the Listing. ImageObjects satisfy the ≥1-adjacency rule via this edge (Listing is the parent); they do not carry their own `LISTED_BY` edge
|
|
110
|
+
`(:Listing)-[:DEPICTS]->(:ImageObject)` is written by the curator once per image present on the Listing. ImageObjects satisfy the ≥1-adjacency rule via this edge (Listing is the parent); they do not carry their own `LISTED_BY` edge because the Listing already does. Hybrid search expands one hop by default, so a memory-search returning a Listing also returns its DEPICTS-linked ImageObjects in the result's `related` array — no separate search call needed to assemble a property card with the right image.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAI3B,OAAO,EAYL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAYzB,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,iBAAiB,CAAA;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAI3B,OAAO,EAYL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAYzB,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,iBAAiB,CAAA;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAwB9E;4CAC4C;AAC5C,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AAwED,eAAO,MAAM,kBAAkB,QAA2B,CAAA;AAI1D,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC7E,OAAO,EAAE,SAAS,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,WAAW,CAAA;IAC/B,eAAe,EAAE,aAAa,CAAA;IAC9B;kFAC8E;IAC9E,cAAc,EAAE,cAAc,CAAA;CAC/B;AA+ID;;;kEAGkE;AAClE,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG;IAC3B,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACpD;;;+CAG2C;IAC3C,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAChD,CAAA;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAu0CpD"}
|
|
@@ -32,6 +32,10 @@ import { basename, join } from 'node:path';
|
|
|
32
32
|
import { validateUserTitle } from './user-title-store.js';
|
|
33
33
|
const ROLES = ['admin', 'public'];
|
|
34
34
|
const CHANNELS = ['browser', 'whatsapp', 'telegram', 'webchat', 'email'];
|
|
35
|
+
// Task 382 — body.conversationNodeId shape gate. The value is the
|
|
36
|
+
// conversationId UUID (v4) minted by createNewAdminConversation in
|
|
37
|
+
// platform/ui/app/lib/neo4j-store.ts.
|
|
38
|
+
const UUID_PATTERN = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
35
39
|
// Task 179 — resume idempotency. Two POSTs 9ms apart with the same
|
|
36
40
|
// idempotencyKey produce one PTY and return the same ManagerSession.
|
|
37
41
|
// Keyed on `${senderId}:${idempotencyKey}` so two operators' keys cannot
|
|
@@ -487,6 +491,12 @@ export function buildHttpApp(deps) {
|
|
|
487
491
|
const agentSlug = typeof body.agentSlug === 'string' && /^[A-Za-z0-9_-]{1,64}$/.test(body.agentSlug)
|
|
488
492
|
? body.agentSlug
|
|
489
493
|
: undefined;
|
|
494
|
+
// Task 382 — conversationId UUID of the active :AdminConversation (or
|
|
495
|
+
// :PublicConversation). Shape-validated as a v4 UUID; anything else is
|
|
496
|
+
// discarded so a body-controlled value cannot reach the env-stamp.
|
|
497
|
+
const conversationNodeId = typeof body.conversationNodeId === 'string' && UUID_PATTERN.test(body.conversationNodeId)
|
|
498
|
+
? body.conversationNodeId
|
|
499
|
+
: undefined;
|
|
490
500
|
const aboutOwnerShape = body.aboutOwner == null ? 'absent' : (typeof body.aboutOwner === 'object' ? 'object' : typeof body.aboutOwner);
|
|
491
501
|
const dormantPluginsShape = Array.isArray(body.dormantPlugins)
|
|
492
502
|
? `array(${body.dormantPlugins.length})`
|
|
@@ -583,6 +593,7 @@ export function buildHttpApp(deps) {
|
|
|
583
593
|
initialMessage,
|
|
584
594
|
agentSlug,
|
|
585
595
|
liveMemory: resolvedLiveMemory,
|
|
596
|
+
conversationNodeId,
|
|
586
597
|
});
|
|
587
598
|
if (!result.ok) {
|
|
588
599
|
if ('rejected' in result) {
|
|
@@ -959,6 +970,10 @@ export function buildHttpApp(deps) {
|
|
|
959
970
|
: undefined;
|
|
960
971
|
const sourceRow = resolveRow(deps.watcher, sessionId);
|
|
961
972
|
const specialist = bodySpecialist ?? sourceRow?.agent ?? undefined;
|
|
973
|
+
// Task 382 — conversationId UUID gate. Same shape gate as /spawn.
|
|
974
|
+
const conversationNodeId = typeof body.conversationNodeId === 'string' && UUID_PATTERN.test(body.conversationNodeId)
|
|
975
|
+
? body.conversationNodeId
|
|
976
|
+
: undefined;
|
|
962
977
|
// Task 258 / Task 273 — restore the archived jsonl to its canonical
|
|
963
978
|
// top-level path before claude --resume runs. Claude CLI manages
|
|
964
979
|
// resume natively as one file per UUID: it appends to
|
|
@@ -1035,6 +1050,7 @@ export function buildHttpApp(deps) {
|
|
|
1035
1050
|
activePlugins,
|
|
1036
1051
|
specialistDomains,
|
|
1037
1052
|
specialist,
|
|
1053
|
+
conversationNodeId,
|
|
1038
1054
|
});
|
|
1039
1055
|
if (!result.ok) {
|
|
1040
1056
|
if ('rejected' in result) {
|