@rubytech/create-realagent 1.0.678 → 1.0.681

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 (60) hide show
  1. package/dist/index.js +232 -39
  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/deployment.md +4 -2
  32. package/payload/platform/plugins/docs/references/memory-guide.md +5 -1
  33. package/payload/platform/plugins/docs/references/platform.md +1 -1
  34. package/payload/platform/plugins/docs/references/troubleshooting.md +20 -0
  35. package/payload/platform/plugins/memory/PLUGIN.md +1 -0
  36. package/payload/platform/plugins/memory/mcp/dist/index.js +54 -6
  37. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  38. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts +36 -0
  39. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts.map +1 -0
  40. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js +86 -0
  41. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js.map +1 -0
  42. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +23 -0
  43. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
  44. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +47 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
  46. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts +58 -0
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts.map +1 -0
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js +125 -0
  49. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js.map +1 -0
  50. package/payload/platform/scripts/vnc.sh +12 -409
  51. package/payload/platform/templates/agents/admin/IDENTITY.md +16 -0
  52. package/payload/platform/templates/dotfiles/.tmux.conf +1 -0
  53. package/payload/platform/templates/systemd/maxy-ttyd.service +25 -0
  54. package/payload/server/chunk-3RBKKDHC.js +783 -0
  55. package/payload/server/maxy-edge.js +377 -8
  56. package/payload/server/public/assets/admin-CIkyOur7.js +362 -0
  57. package/payload/server/public/assets/admin-kHJ-D0s7.css +1 -0
  58. package/payload/server/public/index.html +2 -1
  59. package/payload/server/server.js +391 -412
  60. package/payload/server/public/assets/admin-BBL1no_g.js +0 -352
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Per-process schema cache for the graph-mcp proxy (Task 654).
3
+ *
4
+ * Caches { labels, relationshipTypes } from the connected Neo4j instance so
5
+ * the cypher validator can look up tokens without a round-trip per call.
6
+ *
7
+ * Refresh triggers:
8
+ * - boot — async refresh kicked off by start(); validator fails
9
+ * OPEN until the first refresh resolves.
10
+ * - interval — every 60s (configurable) while the process is alive.
11
+ * - stale-miss — an unknown token with a near match (edit distance ≤ 2)
12
+ * triggers a debounced refresh, catching fresh migrations
13
+ * without requiring operators to wait out the interval.
14
+ *
15
+ * Failure posture:
16
+ * - A refresh that throws preserves the last good snapshot and leaves
17
+ * ready() as it was. `[schema-cache] refresh failure ...` is logged
18
+ * loudly so operators see persistent Neo4j unreachability.
19
+ * - A boot refresh that fails leaves ready()=false and the snapshot empty.
20
+ * The validator's own fail-open branch handles this (empty snapshot →
21
+ * ok=true, no rejections), so the graph-mcp proxy keeps working against
22
+ * a still-reachable Neo4j instance that's just slow to respond to the
23
+ * initial `db.labels()` call.
24
+ */
25
+
26
+ import type { SchemaSnapshot, UnknownToken } from "./cypher-validate.js";
27
+
28
+ export interface SchemaFetcher {
29
+ labels(): Promise<string[]>;
30
+ relationshipTypes(): Promise<string[]>;
31
+ }
32
+
33
+ export interface SchemaCacheOptions {
34
+ /** Interval between automatic refreshes. 0 disables the timer (tests). */
35
+ refreshIntervalMs?: number;
36
+ /** Minimum ms between stale-miss-triggered refreshes. Default 5000. */
37
+ staleMissDebounceMs?: number;
38
+ /** Emit a log line. Default: stderr. Tests inject to capture. */
39
+ emit?: (line: string) => void;
40
+ }
41
+
42
+ const DEFAULT_REFRESH_INTERVAL_MS = 60_000;
43
+ const DEFAULT_STALE_MISS_DEBOUNCE_MS = 5_000;
44
+ const STALE_MISS_DISTANCE_CEILING = 2;
45
+
46
+ export class SchemaCache {
47
+ private _labels = new Set<string>();
48
+ private _rels = new Set<string>();
49
+ private _ready = false;
50
+ private _timer: ReturnType<typeof setInterval> | null = null;
51
+ private _refreshInFlight: Promise<boolean> | null = null;
52
+ private _lastStaleMissAt = 0;
53
+
54
+ constructor(
55
+ private readonly fetcher: SchemaFetcher,
56
+ private readonly options: SchemaCacheOptions = {},
57
+ ) {}
58
+
59
+ snapshot(): SchemaSnapshot {
60
+ return { labels: this._labels, relationshipTypes: this._rels };
61
+ }
62
+
63
+ ready(): boolean {
64
+ return this._ready;
65
+ }
66
+
67
+ async start(): Promise<void> {
68
+ await this.refresh("boot");
69
+ const interval = this.options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
70
+ if (interval > 0) {
71
+ this._timer = setInterval(() => {
72
+ void this.refresh("interval");
73
+ }, interval);
74
+ // Allow the process to exit without waiting for the timer.
75
+ if (typeof this._timer === "object" && "unref" in this._timer) {
76
+ (this._timer as { unref?: () => void }).unref?.();
77
+ }
78
+ }
79
+ }
80
+
81
+ stop(): void {
82
+ if (this._timer) {
83
+ clearInterval(this._timer);
84
+ this._timer = null;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Refresh the cache. Single-flight: concurrent calls await the same in-flight
90
+ * promise rather than triggering overlapping Neo4j reads. Returns true on
91
+ * successful update, false on failure (last good snapshot preserved).
92
+ */
93
+ async refresh(reason: string): Promise<boolean> {
94
+ if (this._refreshInFlight) return this._refreshInFlight;
95
+ const run = async (): Promise<boolean> => {
96
+ const started = Date.now();
97
+ try {
98
+ const [labels, rels] = await Promise.all([
99
+ this.fetcher.labels(),
100
+ this.fetcher.relationshipTypes(),
101
+ ]);
102
+ this._labels = new Set(labels);
103
+ this._rels = new Set(rels);
104
+ this._ready = true;
105
+ this.emit(
106
+ `[schema-cache] refresh reason=${reason} labels=${this._labels.size} relationshipTypes=${this._rels.size} ms=${Date.now() - started}`,
107
+ );
108
+ return true;
109
+ } catch (err) {
110
+ const msg = err instanceof Error ? err.message : String(err);
111
+ this.emit(
112
+ `[schema-cache] refresh failure reason=${reason} ms=${Date.now() - started} error="${msg.replace(/"/g, "'")}"`,
113
+ );
114
+ return false;
115
+ }
116
+ };
117
+ this._refreshInFlight = run();
118
+ try {
119
+ return await this._refreshInFlight;
120
+ } finally {
121
+ this._refreshInFlight = null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * If the validator returned unknown tokens whose nearest known token is
127
+ * within STALE_MISS_DISTANCE_CEILING edits, trigger an out-of-band refresh
128
+ * (debounced). The heuristic: a near-miss is likely a fresh migration
129
+ * (renamed / newly added label/edge), where a far-miss is almost certainly
130
+ * a typo and a refresh would not change the outcome. Returns true iff a
131
+ * refresh was actually run this call.
132
+ */
133
+ async maybeRebuildOnStaleMiss(unknown: UnknownToken[]): Promise<boolean> {
134
+ if (unknown.length === 0) return false;
135
+ const now = Date.now();
136
+ const debounceMs =
137
+ this.options.staleMissDebounceMs ?? DEFAULT_STALE_MISS_DEBOUNCE_MS;
138
+ if (now - this._lastStaleMissAt < debounceMs) return false;
139
+ const anyNearMiss = unknown.some((u) => {
140
+ if (u.nearest.length === 0) return false;
141
+ return editDistance(u.token, u.nearest[0]) <= STALE_MISS_DISTANCE_CEILING;
142
+ });
143
+ if (!anyNearMiss) return false;
144
+ this._lastStaleMissAt = now;
145
+ await this.refresh("stale-miss");
146
+ return true;
147
+ }
148
+
149
+ private emit(line: string): void {
150
+ if (this.options.emit) {
151
+ this.options.emit(line);
152
+ } else {
153
+ process.stderr.write(`${line}\n`);
154
+ }
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Build a SchemaFetcher over a long-lived neo4j-driver Driver. Each call
160
+ * opens a fresh session (cheap, sub-ms) and closes it in finally so the
161
+ * driver's connection pool stays small. Consumer owns the driver lifecycle.
162
+ *
163
+ * Import is deferred to the factory call so that test files can exercise
164
+ * the SchemaCache class without pulling neo4j-driver into the module graph.
165
+ */
166
+ export async function neo4jSchemaFetcher(
167
+ uri: string,
168
+ user: string,
169
+ password: string,
170
+ ): Promise<SchemaFetcher> {
171
+ const neo4j = await import("neo4j-driver");
172
+ const driver = neo4j.default.driver(uri, neo4j.default.auth.basic(user, password));
173
+ const query = async (cypher: string): Promise<string[]> => {
174
+ const session = driver.session();
175
+ try {
176
+ const result = await session.run(cypher);
177
+ return result.records
178
+ .map((r) => r.get(0))
179
+ .filter((v): v is string => typeof v === "string");
180
+ } finally {
181
+ await session.close();
182
+ }
183
+ };
184
+ return {
185
+ async labels() {
186
+ return query("CALL db.labels() YIELD label RETURN label");
187
+ },
188
+ async relationshipTypes() {
189
+ return query(
190
+ "CALL db.relationshipTypes() YIELD relationshipType RETURN relationshipType",
191
+ );
192
+ },
193
+ };
194
+ }
195
+
196
+ function editDistance(a: string, b: string): number {
197
+ if (a === b) return 0;
198
+ if (a.length === 0) return b.length;
199
+ if (b.length === 0) return a.length;
200
+ let prev = new Array<number>(b.length + 1);
201
+ let curr = new Array<number>(b.length + 1);
202
+ for (let j = 0; j <= b.length; j++) prev[j] = j;
203
+ for (let i = 1; i <= a.length; i++) {
204
+ curr[0] = i;
205
+ for (let j = 1; j <= b.length; j++) {
206
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
207
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
208
+ }
209
+ [prev, curr] = [curr, prev];
210
+ }
211
+ return prev[b.length];
212
+ }
@@ -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
  import type { Session } from "neo4j-driver";
18
26
  export interface TrashParams {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAuC5C,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAyEzE;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAkE/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,qFAAqF;IACrF,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0EpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAI9D;AAED,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAuC5C,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAyIzE;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA6G/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,qFAAqF;IACrF,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0EpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAI9D;AAED,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAAqB,CAAC"}
@@ -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,
@@ -70,9 +70,11 @@ The logs will show which service failed to start and why. Common causes:
70
70
 
71
71
  Each Maxy device runs one `--user` systemd unit:
72
72
 
73
- - `maxy-ui.service` — the admin + public HTTP server (default port 19200). Restarted by the upgrade flow; short downtime is expected during steps 8→12 of an upgrade.
73
+ - `maxy-ui.service` — the admin + public HTTP server on `127.0.0.1:19199`. Restarted by the upgrade flow; short downtime is expected during steps 8→12 of an upgrade.
74
+ - `maxy-edge.service` — the always-on public listener on the configured port (default 19200). Reverse-proxies HTTP to `maxy-ui`, handles `/websockify` (VNC) and `/ttyd` (admin terminal) WebSocket upgrades locally. Does NOT restart during an upgrade — the browser WebSocket stays connected by construction.
75
+ - `maxy-ttyd.service` — `ttyd` bound to `127.0.0.1:7681`, running `tmux new-session -A -s maxy-pty`. Owns the byte-stream admin terminal rendered by xterm.js in the header overlay and the Software Update modal (Task 657). Independent of `maxy-ui` and `maxy-edge`; outlives service restarts so scrollback is preserved.
74
76
 
75
- The admin terminal (header Terminal button and Software Update window) runs inside the VNC framebuffer a `gnome-terminal`/`xterm` process spawned on display `:99` or the operator's native session and rendered in the admin UI via a noVNC iframe. The terminal is ephemeral and does not have its own systemd unit. If the Software Update window fails to open or the terminal doesn't appear, check `sudo tail -n 50 ~/.maxy/logs/terminal-launch.log` the VNC stack and spawn pipeline are the only components involved.
77
+ If the admin terminal fails to open, check `sudo tail -n 50 ~/.maxy/logs/edge-boot.log`the `ttyd-ws-upgrade` / `ttyd-proxy-open` / `ttyd-proxy-close` lines carry a `corrId` that ties the full session lifecycle together. For unit health, `systemctl --user status maxy-ttyd` + `journalctl --user -u maxy-ttyd`.
76
78
 
77
79
  ## Upgrading
78
80
 
@@ -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.