@rubytech/create-maxy 1.0.678 → 1.0.680

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.
Files changed (52) hide show
  1. package/dist/index.js +23 -0
  2. package/package.json +1 -1
  3. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts +2 -0
  4. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts.map +1 -0
  5. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js +112 -0
  6. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js.map +1 -0
  7. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts +2 -0
  8. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts.map +1 -0
  9. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js +163 -0
  10. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js.map +1 -0
  11. package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts +38 -0
  12. package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts.map +1 -0
  13. package/payload/platform/lib/graph-mcp/dist/cypher-validate.js +130 -0
  14. package/payload/platform/lib/graph-mcp/dist/cypher-validate.js.map +1 -0
  15. package/payload/platform/lib/graph-mcp/dist/index.js +201 -45
  16. package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
  17. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts +78 -0
  18. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts.map +1 -0
  19. package/payload/platform/lib/graph-mcp/dist/schema-cache.js +194 -0
  20. package/payload/platform/lib/graph-mcp/dist/schema-cache.js.map +1 -0
  21. package/payload/platform/lib/graph-mcp/src/__tests__/cypher-validate.test.ts +141 -0
  22. package/payload/platform/lib/graph-mcp/src/__tests__/schema-cache.test.ts +169 -0
  23. package/payload/platform/lib/graph-mcp/src/cypher-validate.ts +157 -0
  24. package/payload/platform/lib/graph-mcp/src/index.ts +247 -47
  25. package/payload/platform/lib/graph-mcp/src/schema-cache.ts +212 -0
  26. package/payload/platform/lib/graph-trash/dist/index.d.ts +8 -0
  27. package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -1
  28. package/payload/platform/lib/graph-trash/dist/index.js +109 -14
  29. package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
  30. package/payload/platform/lib/graph-trash/src/index.ts +136 -21
  31. package/payload/platform/plugins/docs/references/memory-guide.md +5 -1
  32. package/payload/platform/plugins/docs/references/platform.md +1 -1
  33. package/payload/platform/plugins/docs/references/troubleshooting.md +18 -0
  34. package/payload/platform/plugins/memory/PLUGIN.md +1 -0
  35. package/payload/platform/plugins/memory/mcp/dist/index.js +54 -6
  36. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  37. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts +36 -0
  38. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts.map +1 -0
  39. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js +86 -0
  40. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js.map +1 -0
  41. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +23 -0
  42. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
  43. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +47 -1
  44. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts +58 -0
  46. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts.map +1 -0
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js +125 -0
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js.map +1 -0
  49. package/payload/platform/templates/agents/admin/IDENTITY.md +16 -0
  50. package/payload/server/chunk-3RBKKDHC.js +783 -0
  51. package/payload/server/maxy-edge.js +11 -3
  52. package/payload/server/server.js +284 -112
@@ -14,6 +14,14 @@
14
14
  * are snapshotted into `_trashedKeys` (JSON) and nulled on the node, so
15
15
  * MERGE against the same key won't collide. `restoreNode` writes them back
16
16
  * and fails loudly when an active node already occupies the slot.
17
+ *
18
+ * Label-aware cascade (Task 655): when `trashNode` trashes a `:Conversation`,
19
+ * it also trashes every `(m:Message)-[:PART_OF]->(c)` Message in the same
20
+ * managed transaction. This makes `MATCH (m:Trashed)` a correct audit
21
+ * primitive again — pre-Task-655 a Conversation trash left attached Messages
22
+ * un-labelled, so audits that filtered `m:Trashed` silently under-reported
23
+ * the blast radius of the 2026-04-22 incident by a factor of 10+.
24
+ * `restoreNode` reverses both sides.
17
25
  */
18
26
  Object.defineProperty(exports, "__esModule", { value: true });
19
27
  exports.TRASH_METADATA_PROPS = void 0;
@@ -92,19 +100,72 @@ async function trashNode(params) {
92
100
  .join(", ");
93
101
  const setNullSuffix = setNullClauses ? `, ${setNullClauses}` : "";
94
102
  const trashedAt = new Date().toISOString();
95
- await session.run(`MATCH (n) WHERE elementId(n) = $eid
96
- SET n:Trashed,
97
- n.trashedAt = datetime($trashedAt),
98
- n.trashedBy = $by,
99
- n.trashReason = $reason,
100
- n._trashedKeys = $trashedKeysJson${setNullSuffix}`, {
101
- eid: elementId,
102
- trashedAt,
103
- by,
104
- reason: reason ?? null,
105
- trashedKeysJson: JSON.stringify(originalKeys),
103
+ const isConversation = baseLabels.includes("Conversation");
104
+ // Message unique-key list computed once — matches UNIQUE_KEYS_BY_LABEL["Message"].
105
+ const messageUniqueKeys = UNIQUE_KEYS_BY_LABEL["Message"] ?? [];
106
+ // Root trash + Conversation→Message cascade in one managed transaction so a
107
+ // DB error mid-sweep can't leave a Conversation :Trashed with some Messages
108
+ // un-trashed (the 2026-04-22 audit correctness gap). For non-Conversation
109
+ // labels, the cascade branch is a no-op — same one-write shape as before.
110
+ let cascadedMessageCount = 0;
111
+ await session.executeWrite(async (tx) => {
112
+ await tx.run(`MATCH (n) WHERE elementId(n) = $eid
113
+ SET n:Trashed,
114
+ n.trashedAt = datetime($trashedAt),
115
+ n.trashedBy = $by,
116
+ n.trashReason = $reason,
117
+ n._trashedKeys = $trashedKeysJson${setNullSuffix}`, {
118
+ eid: elementId,
119
+ trashedAt,
120
+ by,
121
+ reason: reason ?? null,
122
+ trashedKeysJson: JSON.stringify(originalKeys),
123
+ });
124
+ if (isConversation) {
125
+ // Two-step cascade: collect Message elementIds and their unique-key
126
+ // values first, then apply :Trashed + snapshot + null in one SET per
127
+ // message using parameter-passed snapshot JSON. This keeps JSON
128
+ // construction in the Node process (where JSON.stringify is trivially
129
+ // correct) rather than building escaped JSON inside Cypher.
130
+ const collectKeys = messageUniqueKeys.length > 0
131
+ ? messageUniqueKeys.map((k) => `\`${k}\`: m.\`${k}\``).join(", ")
132
+ : "";
133
+ const collectMsgProps = collectKeys ? `, {${collectKeys}}` : ", {}";
134
+ const collected = await tx.run(`MATCH (c) WHERE elementId(c) = $eid
135
+ MATCH (m:Message)-[:PART_OF]->(c)
136
+ WHERE NOT m:Trashed
137
+ RETURN elementId(m) AS meid${collectMsgProps} AS keys`, { eid: elementId });
138
+ for (const rec of collected.records) {
139
+ const meid = rec.get("meid");
140
+ const keys = rec.get("keys");
141
+ const liveKeys = {};
142
+ for (const k of messageUniqueKeys) {
143
+ if (keys[k] !== undefined && keys[k] !== null)
144
+ liveKeys[k] = keys[k];
145
+ }
146
+ const msgSetNulls = Object.keys(liveKeys).length > 0
147
+ ? ", " + Object.keys(liveKeys).map((k) => `m.\`${k}\` = null`).join(", ")
148
+ : "";
149
+ await tx.run(`MATCH (m) WHERE elementId(m) = $meid
150
+ SET m:Trashed,
151
+ m.trashedAt = datetime($trashedAt),
152
+ m.trashedBy = $by,
153
+ m.trashReason = $reason,
154
+ m._trashedKeys = $trashedKeysJson${msgSetNulls}`, {
155
+ meid,
156
+ trashedAt,
157
+ by: `${by}:cascade-from-conversation`,
158
+ reason: reason ?? `cascade from Conversation ${elementId}`,
159
+ trashedKeysJson: JSON.stringify(liveKeys),
160
+ });
161
+ }
162
+ cascadedMessageCount = collected.records.length;
163
+ }
106
164
  });
107
165
  process.stderr.write(`[trash:marked] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")} by=${by} reason=${reason ?? "null"}\n`);
166
+ if (isConversation) {
167
+ process.stderr.write(`[trash:cascaded] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount} by=${by}\n`);
168
+ }
108
169
  return {
109
170
  trashed: true,
110
171
  alreadyTrashed: false,
@@ -150,10 +211,44 @@ async function restoreNode(params) {
150
211
  const setParams = { eid: elementId };
151
212
  for (const [k, v] of Object.entries(originalKeys))
152
213
  setParams[`val_${k}`] = v;
153
- await session.run(`MATCH (n:Trashed) WHERE elementId(n) = $eid
154
- REMOVE n:Trashed, n.trashedAt, n.trashedBy, n.trashReason, n._trashedKeys
155
- SET n.restoredAt = datetime()${setSuffix}`, setParams);
214
+ const isConversation = baseLabels.includes("Conversation");
215
+ let cascadedMessageCount = 0;
216
+ await session.executeWrite(async (tx) => {
217
+ await tx.run(`MATCH (n:Trashed) WHERE elementId(n) = $eid
218
+ REMOVE n:Trashed, n.trashedAt, n.trashedBy, n.trashReason, n._trashedKeys
219
+ SET n.restoredAt = datetime()${setSuffix}`, setParams);
220
+ if (isConversation) {
221
+ // Reverse of the trashNode cascade: re-label and restore unique-key
222
+ // snapshots for every `(m:Message)-[:PART_OF]->(c)` that was trashed
223
+ // by the same cascade chain (i.e. m._trashedKeys exists + m was
224
+ // trashed by a `*:cascade-from-conversation` provenance). We scope by
225
+ // the second condition so a user-initiated single-Message trash that
226
+ // happens to sit inside this Conversation isn't accidentally undone.
227
+ const collected = await tx.run(`MATCH (c) WHERE elementId(c) = $eid
228
+ MATCH (m:Trashed:Message)-[:PART_OF]->(c)
229
+ WHERE m.trashedBy ENDS WITH ':cascade-from-conversation'
230
+ RETURN elementId(m) AS meid, m._trashedKeys AS keysJson`, { eid: elementId });
231
+ for (const rec of collected.records) {
232
+ const meid = rec.get("meid");
233
+ const keysJson = rec.get("keysJson");
234
+ const keys = keysJson ? JSON.parse(keysJson) : {};
235
+ const setClause = Object.keys(keys).length > 0
236
+ ? ", " + Object.keys(keys).map((k) => `m.\`${k}\` = $val_${k}`).join(", ")
237
+ : "";
238
+ const msgParams = { meid };
239
+ for (const [k, v] of Object.entries(keys))
240
+ msgParams[`val_${k}`] = v;
241
+ await tx.run(`MATCH (m) WHERE elementId(m) = $meid
242
+ REMOVE m:Trashed, m.trashedAt, m.trashedBy, m.trashReason, m._trashedKeys
243
+ SET m.restoredAt = datetime()${setClause}`, msgParams);
244
+ }
245
+ cascadedMessageCount = collected.records.length;
246
+ }
247
+ });
156
248
  process.stderr.write(`[trash:restored] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")}\n`);
249
+ if (isConversation) {
250
+ process.stderr.write(`[trash:cascade-restored] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount}\n`);
251
+ }
157
252
  return {
158
253
  restored: true,
159
254
  nodeId: elementId,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,14 @@
13
13
  * are snapshotted into `_trashedKeys` (JSON) and nulled on the node, so
14
14
  * MERGE against the same key won't collide. `restoreNode` writes them back
15
15
  * and fails loudly when an active node already occupies the slot.
16
+ *
17
+ * Label-aware cascade (Task 655): when `trashNode` trashes a `:Conversation`,
18
+ * it also trashes every `(m:Message)-[:PART_OF]->(c)` Message in the same
19
+ * managed transaction. This makes `MATCH (m:Trashed)` a correct audit
20
+ * primitive again — pre-Task-655 a Conversation trash left attached Messages
21
+ * un-labelled, so audits that filtered `m:Trashed` silently under-reported
22
+ * the blast radius of the 2026-04-22 incident by a factor of 10+.
23
+ * `restoreNode` reverses both sides.
16
24
  */
17
25
 
18
26
  import type { Session } from "neo4j-driver";
@@ -118,25 +126,89 @@ export async function trashNode(params: TrashParams): Promise<TrashResult> {
118
126
  const setNullSuffix = setNullClauses ? `, ${setNullClauses}` : "";
119
127
 
120
128
  const trashedAt = new Date().toISOString();
121
- await session.run(
122
- `MATCH (n) WHERE elementId(n) = $eid
123
- SET n:Trashed,
124
- n.trashedAt = datetime($trashedAt),
125
- n.trashedBy = $by,
126
- n.trashReason = $reason,
127
- n._trashedKeys = $trashedKeysJson${setNullSuffix}`,
128
- {
129
- eid: elementId,
130
- trashedAt,
131
- by,
132
- reason: reason ?? null,
133
- trashedKeysJson: JSON.stringify(originalKeys),
134
- },
135
- );
129
+ const isConversation = baseLabels.includes("Conversation");
130
+ // Message unique-key list computed once — matches UNIQUE_KEYS_BY_LABEL["Message"].
131
+ const messageUniqueKeys = UNIQUE_KEYS_BY_LABEL["Message"] ?? [];
132
+
133
+ // Root trash + Conversation→Message cascade in one managed transaction so a
134
+ // DB error mid-sweep can't leave a Conversation :Trashed with some Messages
135
+ // un-trashed (the 2026-04-22 audit correctness gap). For non-Conversation
136
+ // labels, the cascade branch is a no-op — same one-write shape as before.
137
+ let cascadedMessageCount = 0;
138
+ await session.executeWrite(async (tx) => {
139
+ await tx.run(
140
+ `MATCH (n) WHERE elementId(n) = $eid
141
+ SET n:Trashed,
142
+ n.trashedAt = datetime($trashedAt),
143
+ n.trashedBy = $by,
144
+ n.trashReason = $reason,
145
+ n._trashedKeys = $trashedKeysJson${setNullSuffix}`,
146
+ {
147
+ eid: elementId,
148
+ trashedAt,
149
+ by,
150
+ reason: reason ?? null,
151
+ trashedKeysJson: JSON.stringify(originalKeys),
152
+ },
153
+ );
154
+
155
+ if (isConversation) {
156
+ // Two-step cascade: collect Message elementIds and their unique-key
157
+ // values first, then apply :Trashed + snapshot + null in one SET per
158
+ // message using parameter-passed snapshot JSON. This keeps JSON
159
+ // construction in the Node process (where JSON.stringify is trivially
160
+ // correct) rather than building escaped JSON inside Cypher.
161
+ const collectKeys = messageUniqueKeys.length > 0
162
+ ? messageUniqueKeys.map((k) => `\`${k}\`: m.\`${k}\``).join(", ")
163
+ : "";
164
+ const collectMsgProps = collectKeys ? `, {${collectKeys}}` : ", {}";
165
+
166
+ const collected = await tx.run(
167
+ `MATCH (c) WHERE elementId(c) = $eid
168
+ MATCH (m:Message)-[:PART_OF]->(c)
169
+ WHERE NOT m:Trashed
170
+ RETURN elementId(m) AS meid${collectMsgProps} AS keys`,
171
+ { eid: elementId },
172
+ );
173
+
174
+ for (const rec of collected.records) {
175
+ const meid = rec.get("meid") as string;
176
+ const keys = rec.get("keys") as Record<string, unknown>;
177
+ const liveKeys: Record<string, unknown> = {};
178
+ for (const k of messageUniqueKeys) {
179
+ if (keys[k] !== undefined && keys[k] !== null) liveKeys[k] = keys[k];
180
+ }
181
+ const msgSetNulls = Object.keys(liveKeys).length > 0
182
+ ? ", " + Object.keys(liveKeys).map((k) => `m.\`${k}\` = null`).join(", ")
183
+ : "";
184
+ await tx.run(
185
+ `MATCH (m) WHERE elementId(m) = $meid
186
+ SET m:Trashed,
187
+ m.trashedAt = datetime($trashedAt),
188
+ m.trashedBy = $by,
189
+ m.trashReason = $reason,
190
+ m._trashedKeys = $trashedKeysJson${msgSetNulls}`,
191
+ {
192
+ meid,
193
+ trashedAt,
194
+ by: `${by}:cascade-from-conversation`,
195
+ reason: reason ?? `cascade from Conversation ${elementId}`,
196
+ trashedKeysJson: JSON.stringify(liveKeys),
197
+ },
198
+ );
199
+ }
200
+ cascadedMessageCount = collected.records.length;
201
+ }
202
+ });
136
203
 
137
204
  process.stderr.write(
138
205
  `[trash:marked] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")} by=${by} reason=${reason ?? "null"}\n`,
139
206
  );
207
+ if (isConversation) {
208
+ process.stderr.write(
209
+ `[trash:cascaded] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount} by=${by}\n`,
210
+ );
211
+ }
140
212
 
141
213
  return {
142
214
  trashed: true,
@@ -210,16 +282,59 @@ export async function restoreNode(params: RestoreParams): Promise<RestoreResult>
210
282
  const setParams: Record<string, unknown> = { eid: elementId };
211
283
  for (const [k, v] of Object.entries(originalKeys)) setParams[`val_${k}`] = v;
212
284
 
213
- await session.run(
214
- `MATCH (n:Trashed) WHERE elementId(n) = $eid
215
- REMOVE n:Trashed, n.trashedAt, n.trashedBy, n.trashReason, n._trashedKeys
216
- SET n.restoredAt = datetime()${setSuffix}`,
217
- setParams,
218
- );
285
+ const isConversation = baseLabels.includes("Conversation");
286
+ let cascadedMessageCount = 0;
287
+
288
+ await session.executeWrite(async (tx) => {
289
+ await tx.run(
290
+ `MATCH (n:Trashed) WHERE elementId(n) = $eid
291
+ REMOVE n:Trashed, n.trashedAt, n.trashedBy, n.trashReason, n._trashedKeys
292
+ SET n.restoredAt = datetime()${setSuffix}`,
293
+ setParams,
294
+ );
295
+
296
+ if (isConversation) {
297
+ // Reverse of the trashNode cascade: re-label and restore unique-key
298
+ // snapshots for every `(m:Message)-[:PART_OF]->(c)` that was trashed
299
+ // by the same cascade chain (i.e. m._trashedKeys exists + m was
300
+ // trashed by a `*:cascade-from-conversation` provenance). We scope by
301
+ // the second condition so a user-initiated single-Message trash that
302
+ // happens to sit inside this Conversation isn't accidentally undone.
303
+ const collected = await tx.run(
304
+ `MATCH (c) WHERE elementId(c) = $eid
305
+ MATCH (m:Trashed:Message)-[:PART_OF]->(c)
306
+ WHERE m.trashedBy ENDS WITH ':cascade-from-conversation'
307
+ RETURN elementId(m) AS meid, m._trashedKeys AS keysJson`,
308
+ { eid: elementId },
309
+ );
310
+ for (const rec of collected.records) {
311
+ const meid = rec.get("meid") as string;
312
+ const keysJson = rec.get("keysJson") as string | null;
313
+ const keys: Record<string, unknown> = keysJson ? JSON.parse(keysJson) : {};
314
+ const setClause = Object.keys(keys).length > 0
315
+ ? ", " + Object.keys(keys).map((k) => `m.\`${k}\` = $val_${k}`).join(", ")
316
+ : "";
317
+ const msgParams: Record<string, unknown> = { meid };
318
+ for (const [k, v] of Object.entries(keys)) msgParams[`val_${k}`] = v;
319
+ await tx.run(
320
+ `MATCH (m) WHERE elementId(m) = $meid
321
+ REMOVE m:Trashed, m.trashedAt, m.trashedBy, m.trashReason, m._trashedKeys
322
+ SET m.restoredAt = datetime()${setClause}`,
323
+ msgParams,
324
+ );
325
+ }
326
+ cascadedMessageCount = collected.records.length;
327
+ }
328
+ });
219
329
 
220
330
  process.stderr.write(
221
331
  `[trash:restored] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")}\n`,
222
332
  );
333
+ if (isConversation) {
334
+ process.stderr.write(
335
+ `[trash:cascade-restored] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount}\n`,
336
+ );
337
+ }
223
338
 
224
339
  return {
225
340
  restored: true,
@@ -90,10 +90,14 @@ Conversations and Messages carry role/channel sublabels so you can read the chat
90
90
 
91
91
  **Save a default view:** once you have the rows you want, click **Set default view** in the filter menu. Next time you open **Graph**, those rows are pre-selected and your data renders immediately. The default is per-admin, per-account — each admin on each account has their own.
92
92
 
93
- **Delete a node:** drag it to the trash icon top-right of the canvas. No confirmation — deletes are reversible for 30 days. To restore, toggle **Show trashed** inside the filter menu and click **Restore** on the node, or ask Maxy in chat ("restore the <label> I just deleted").
93
+ **Delete a node:** drag it to the trash icon top-right of the canvas. No confirmation — deletes are reversible for 30 days. To restore, toggle **Show trashed** inside the filter menu and click **Restore** on the node, or ask Maxy in chat ("restore the <label> I just deleted"). Deleting a conversation also trashes its messages in the same step, so they reappear together on restore.
94
+
95
+ **Bulk cleanup of conversations in chat:** when you ask Maxy to clean up conversations in bulk ("trash all empty conversations," "clean up the single-assistant tests"), the agent uses a deterministic selector with a fixed set of filter names — it cannot author custom delete queries. The server re-runs the same filter on every candidate before it trashes, so a stale list can't destroy something the filter wouldn't match now. If the filter matches nothing, Maxy reports "no candidates" and nothing happens.
94
96
 
95
97
  The page reads only your own brand's Neo4j — a Maxy device and a Real Agent device share no graph state even when on the same laptop. No credentials are required; the view inherits your admin session.
96
98
 
99
+ **Typo-proof cypher.** When Maxy runs direct Cypher to answer a relational question, the query is checked against your Neo4j's live label and relationship-type taxonomy before it executes. Cypher that references an unknown name (an edge or label that does not exist in your graph) is rejected for writes and flagged with a warnings header for reads, so Maxy never silently acts on a query that targeted the wrong set of nodes. You should not see this — it runs invisibly — but it is the safety net that stops a fabricated edge name from producing "empty" results that are really just unreachable. Before acting on a bulk operation Maxy surfaces the result count and a sample; if it ever describes a cypher rejection, that means its first attempt was malformed and it corrected itself.
100
+
97
101
  ## Privacy
98
102
 
99
103
  All memory is stored on your local Raspberry Pi. The Neo4j database never leaves your network. Maxy does not sync memory to any cloud service or third party.
@@ -52,7 +52,7 @@ The memory graph is stored on your Pi. It never leaves your network.
52
52
 
53
53
  ## The Web Interface
54
54
 
55
- The web app runs on your Pi on port 19200. A small always-on front door (`maxy-edge`) owns that port and the remote terminal transport — so when the Software Update command restarts the app server, the browser-side terminal keeps streaming bytes exactly like an SSH session would. It provides:
55
+ The web app runs on your Pi on port 19200. A small always-on front door (`maxy-edge`) owns that port and the remote terminal transport — so when the Software Update command restarts the app server, the browser-side terminal keeps streaming bytes exactly like an SSH session would. Login cookies are HMAC-signed with a shared key on disk, so both processes recognise the same session without any coordination and you do not have to log in again after an update. It provides:
56
56
 
57
57
  - **Admin chat** (at `/`) — your primary interface, PIN-protected
58
58
  - **Public chat** (at `/{agent-name}`) — visitor-facing agents, each with their own URL. On public hostnames, the root path serves the default agent.
@@ -133,6 +133,24 @@ DISPLAY=:99 xdpyinfo >/dev/null 2>&1 && echo "display :99 ok" || echo "display d
133
133
 
134
134
  ---
135
135
 
136
+ ## Upgrade terminal stays black after clicking Upgrade (VNC WebSocket rejected)
137
+
138
+ **Symptom:** The launch-upgrade POST returns 200 (the server-side spawn succeeded), but the browser's VNC iframe shows a black rectangle and the DevTools console spins on `WebSocket connection to '/websockify' failed` / `Connection closed (code: 1006)` every few seconds.
139
+
140
+ **What it means:** The browser has a session cookie the `/websockify` auth gate refuses to accept. Since Task 653, every rejection logs three fields that identify the failing layer:
141
+
142
+ ```bash
143
+ sudo tail -200 ~/.maxy/logs/vnc-boot.log | grep 'decision="rejected"'
144
+ ```
145
+
146
+ - `cookieHeaderPresent=false` → the browser never sent a cookie. Check the iframe context / SameSite policy / cookie domain. Most commonly: the user opened admin over HTTP instead of HTTPS, or the cookie is scoped to a different subdomain.
147
+ - `cookieHeaderPresent=true tokenPresent=false` → the cookie is present but the server rejects its signature. This is either tampering or a corrupted/missing shared secret. Check that `~/.maxy/credentials/remote-session-secret` exists on disk (mode `0600`, 64 hex chars) and has not been recently overwritten.
148
+ - `tokenExpired=true` → the 24h session TTL elapsed. Log in again.
149
+
150
+ If the secret file is missing entirely, re-run `npx -y @rubytech/create-maxy@latest` — the installer provisions it idempotently, preserving an existing file on upgrade and creating a fresh one on first install.
151
+
152
+ ---
153
+
136
154
  ## Upgrade terminal opens but npx never runs
137
155
 
138
156
  **Symptom:** You clicked **Upgrade**, the VNC overlay opened with a visible shell prompt, but `npx -y @rubytech/create-maxy@latest` does not execute and the shell is idle.
@@ -10,6 +10,7 @@ tools:
10
10
  - memory-ingest
11
11
  - memory-ingest-extract
12
12
  - memory-ingest-web
13
+ - memory-find-candidates
13
14
  - memory-delete
14
15
  - memory-restore
15
16
  - memory-empty-trash
@@ -14,6 +14,7 @@ import { memoryIngest } from "./tools/memory-ingest.js";
14
14
  import { memoryIngestWeb } from "./tools/memory-ingest-web.js";
15
15
  import { memoryUpdate } from "./tools/memory-update.js";
16
16
  import { memoryDelete } from "./tools/memory-delete.js";
17
+ import { memoryFindCandidates } from "./tools/memory-find-candidates.js";
17
18
  import { memoryRestore } from "./tools/memory-restore.js";
18
19
  import { memoryEmptyTrash } from "./tools/memory-empty-trash.js";
19
20
  import { memoryListAttachments } from "./tools/memory-list-attachments.js";
@@ -636,16 +637,63 @@ if (!readOnly) {
636
637
  };
637
638
  }
638
639
  });
640
+ server.tool("memory-find-candidates", "Deterministic bulk-delete selector for Conversation nodes. Returns a structured list of candidates " +
641
+ "matching a fixed-name filter ('empty' = zero :PART_OF messages; 'single-assistant' = exactly one " +
642
+ "assistant-role message) and an opaque filterToken. Pair with memory-delete(filterToken=…) — the server " +
643
+ "re-runs the same predicate per elementId before trashing, so a stale or forged token never deletes. " +
644
+ "Never author custom delete-selection Cypher for bulk flows; always use this tool.", {
645
+ filter: z.enum(["empty", "single-assistant"]).describe("Selection predicate. 'empty' = no :PART_OF Messages. 'single-assistant' = exactly one :PART_OF " +
646
+ "Message with role='assistant'. Content-matching filter ('no-real-user-input') is deferred to Task 656."),
647
+ agentType: z.enum(["admin", "public"]).optional().describe("Scope to conversations matching c.agentType. Omit to scope across both."),
648
+ limit: z.number().int().positive().optional().describe("Max candidates to return (default 200, hard cap 1000)."),
649
+ dryRun: z.boolean().optional().describe("Preview matches without minting an actionable filterToken. Candidates are listed in the response, but no delete can be performed against them — caller must re-run without dryRun to receive a token."),
650
+ }, async ({ filter, agentType, limit, dryRun }) => {
651
+ try {
652
+ const result = await memoryFindCandidates({ accountId, filter, agentType, limit, dryRun });
653
+ if (result.items.length === 0) {
654
+ return { content: [{ type: "text", text: `No candidates matched filter=${filter} agentType=${agentType ?? "any"}. No delete will happen.` }] };
655
+ }
656
+ const tokenLine = result.dryRun
657
+ ? "filterToken=(none — dryRun; re-run without dryRun to get an actionable token)"
658
+ : `filterToken=${result.filterToken}`;
659
+ const trailer = result.dryRun
660
+ ? "This is a dryRun preview. No delete can be performed until you re-run without dryRun."
661
+ : "Pass each elementId to memory-delete with the filterToken above. The server re-verifies on every call.";
662
+ const lines = [
663
+ `Matched ${result.matched} candidate(s) for filter=${filter} agentType=${agentType ?? "any"}.`,
664
+ tokenLine,
665
+ "",
666
+ trailer,
667
+ "",
668
+ ...result.items.map((i) => {
669
+ const preview = i.firstUserMsgPreview ? ` userPreview=${JSON.stringify(i.firstUserMsgPreview)}` : "";
670
+ return `- elementId=${i.elementId} conversationId=${i.conversationId ?? "null"} assistantMsgs=${i.assistantMsgCount} realUserMsgs=${i.realUserMsgCount}${preview}`;
671
+ }),
672
+ ];
673
+ return { content: [{ type: "text", text: lines.join("\n") }] };
674
+ }
675
+ catch (err) {
676
+ return {
677
+ content: [{ type: "text", text: `memory-find-candidates failed: ${err instanceof Error ? err.message : String(err)}` }],
678
+ isError: true,
679
+ };
680
+ }
681
+ });
639
682
  server.tool("memory-delete", "Soft-delete a single graph node by elementId. Adds the :Trashed label, preserves relationships, " +
640
683
  "and excludes the node from memory-search and any read filtered via notTrashed. For KnowledgeDocument, " +
641
- "cascades to linked Section and Chunk children. Hard removal happens later via memory-empty-trash " +
642
- "after the grace period (default 30 days). For GDPR Right-to-Erasure cascade, use contact-erase instead — " +
643
- "it bypasses this primitive and hard-deletes per Article 17.", {
644
- elementId: z.string().describe("Neo4j elementId of the node to trash (from memory-search results)."),
684
+ "cascades to linked Section and Chunk children. For Conversation, cascades to attached :PART_OF Messages " +
685
+ "(Task 655). Hard removal happens later via memory-empty-trash after the grace period (default 30 days). " +
686
+ "For bulk-delete flows, pass filterToken from memory-find-candidates the server re-verifies the predicate " +
687
+ "before trashing. For GDPR Right-to-Erasure, use contact-erase instead (bypasses this primitive and " +
688
+ "hard-deletes per Article 17).", {
689
+ elementId: z.string().describe("Neo4j elementId of the node to trash (from memory-search or memory-find-candidates)."),
645
690
  reason: z.string().optional().describe("Optional human-readable explanation; persisted as trashReason for audit."),
646
- }, async ({ elementId, reason }) => {
691
+ filterToken: z.string().optional().describe("Opaque token from memory-find-candidates. Required for bulk-selector flows; the server re-runs the " +
692
+ "canonical predicate on this elementId before trashing. Omit for single-node deletes where the caller " +
693
+ "chose the target directly (e.g. from a memory-search result)."),
694
+ }, async ({ elementId, reason, filterToken }) => {
647
695
  try {
648
- const result = await memoryDelete({ accountId, elementId, reason });
696
+ const result = await memoryDelete({ accountId, elementId, reason, filterToken });
649
697
  const head = result.alreadyTrashed
650
698
  ? `Already trashed: [${result.labels.join(", ")}] elementId=${elementId}`
651
699
  : `Trashed: [${result.labels.join(", ")}] elementId=${elementId}`;