@rubytech/create-realagent 1.0.838 → 1.0.840

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 (80) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/graph-write/dist/index.js +1 -1
  3. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  4. package/payload/platform/lib/graph-write/src/index.ts +1 -1
  5. package/payload/platform/plugins/admin/PLUGIN.md +2 -0
  6. package/payload/platform/plugins/admin/mcp/dist/index.js +1 -1
  7. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  8. package/payload/platform/plugins/admin/skills/business-profile/SKILL.md +5 -5
  9. package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +11 -11
  10. package/payload/platform/plugins/admin/skills/unzip-attachment/SKILL.md +2 -0
  11. package/payload/platform/plugins/contacts/mcp/dist/index.js +1 -1
  12. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  13. package/payload/platform/plugins/docs/references/internals.md +1 -1
  14. package/payload/platform/plugins/docs/references/platform.md +1 -1
  15. package/payload/platform/plugins/docs/references/troubleshooting.md +20 -0
  16. package/payload/platform/plugins/memory/PLUGIN.md +3 -3
  17. package/payload/platform/plugins/memory/mcp/dist/index.js +14 -14
  18. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  19. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +12 -11
  20. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -1
  21. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +22 -10
  22. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -1
  23. package/payload/platform/plugins/memory/mcp/dist/lib/uuid.js +7 -7
  24. package/payload/platform/plugins/memory/mcp/dist/lib/uuid.js.map +1 -1
  25. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +1 -1
  26. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  27. package/payload/platform/plugins/memory/references/schema-base.md +17 -17
  28. package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +14 -14
  29. package/payload/platform/plugins/tasks/PLUGIN.md +2 -2
  30. package/payload/platform/plugins/tasks/mcp/dist/index.js +11 -11
  31. package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -1
  32. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.js +1 -1
  33. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.js.map +1 -1
  34. package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
  35. package/payload/platform/scripts/seed-neo4j.sh +13 -3
  36. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -0
  37. package/payload/platform/templates/specialists/agents/database-operator.md +1 -1
  38. package/payload/server/adminuser-self-heal-QAWOZ3JV.js +45 -0
  39. package/payload/server/chunk-7PLAT6UR.js +2103 -0
  40. package/payload/server/chunk-CJWFM3WX.js +2098 -0
  41. package/payload/server/chunk-D5U4XQ66.js +656 -0
  42. package/payload/server/chunk-DJXPAH7T.js +1480 -0
  43. package/payload/server/chunk-M6J4JM3D.js +656 -0
  44. package/payload/server/chunk-PZZ3IKUU.js +1116 -0
  45. package/payload/server/chunk-T2MQIKBT.js +10001 -0
  46. package/payload/server/chunk-TSOYVJC4.js +10003 -0
  47. package/payload/server/client-pool-M25CGILI.js +32 -0
  48. package/payload/server/client-pool-OX75YUFD.js +33 -0
  49. package/payload/server/cloudflare-task-tracker-GQFKLY62.js +20 -0
  50. package/payload/server/cloudflare-task-tracker-OQTQWFWK.js +20 -0
  51. package/payload/server/maxy-edge.js +4 -4
  52. package/payload/server/neo4j-migrations-4PG2KB4W.js +665 -0
  53. package/payload/server/public/assets/{Checkbox-Bq6ORjz2.js → Checkbox-aCc0UGp3.js} +1 -1
  54. package/payload/server/public/assets/{admin-CstEkw-G.js → admin-D678VwpH.js} +2 -2
  55. package/payload/server/public/assets/data-DsItQm8c.js +1 -0
  56. package/payload/server/public/assets/graph-C-HOmfmU.js +1 -0
  57. package/payload/server/public/assets/{jsx-runtime-DidQeNoZ.css → jsx-runtime-BKoartnM.css} +1 -1
  58. package/payload/server/public/assets/{page-CFWoVkgV.js → page-D7LchjvY.js} +1 -1
  59. package/payload/server/public/assets/{page-Bpi_jPw6.js → page-DTmTvkNo.js} +1 -1
  60. package/payload/server/public/assets/{public-BWMwq5Jj.js → public-C7mCgRX0.js} +1 -1
  61. package/payload/server/public/assets/{useAdminFetch-B93ig7ef.js → useAdminFetch-BgDL3JGd.js} +1 -1
  62. package/payload/server/public/assets/{useVoiceRecorder-Cb0nAtOo.js → useVoiceRecorder-Bx903Mk1.js} +1 -1
  63. package/payload/server/public/data.html +5 -5
  64. package/payload/server/public/graph.html +6 -6
  65. package/payload/server/public/index.html +8 -8
  66. package/payload/server/public/public.html +5 -5
  67. package/payload/server/server.js +39 -28
  68. package/payload/platform/neo4j/migrations/001-backfill-scope.cypher +0 -30
  69. package/payload/platform/neo4j/migrations/002-project-public-agents.ts +0 -191
  70. package/payload/platform/neo4j/migrations/003-person-name-eradicate.cypher +0 -24
  71. package/payload/platform/neo4j/migrations/004-project-admin-agent.ts +0 -348
  72. package/payload/platform/neo4j/migrations/004-prune-alien-accounts.ts +0 -133
  73. package/payload/platform/neo4j/migrations/005-removed-review-feature.ts +0 -102
  74. package/payload/platform/neo4j/migrations/006-prune-bogus-whatsapp-persons.ts +0 -132
  75. package/payload/platform/neo4j/migrations/007-conversation-archive-source.ts +0 -116
  76. package/payload/platform/neo4j/migrations/008-adminuser-accountid-backfill.ts +0 -85
  77. package/payload/platform/neo4j/migrations/009-conversation-archive-title.ts +0 -197
  78. package/payload/server/public/assets/data-DwZZ7qbH.js +0 -1
  79. package/payload/server/public/assets/graph-DceEv42K.js +0 -1
  80. /package/payload/server/public/assets/{jsx-runtime-DH5S-MwB.js → jsx-runtime-WW3O7tSz.js} +0 -0
@@ -0,0 +1,665 @@
1
+ import {
2
+ ACCOUNTS_DIR
3
+ } from "./chunk-OJZPS4BL.js";
4
+ import {
5
+ getSession,
6
+ projectAgent
7
+ } from "./chunk-7PLAT6UR.js";
8
+ import "./chunk-JSBRDJBE.js";
9
+
10
+ // app/lib/neo4j-migrations.ts
11
+ import { readFileSync as readFileSync3 } from "fs";
12
+ import { resolve as resolve4 } from "path";
13
+
14
+ // ../neo4j/migrations/004-prune-alien-accounts.ts
15
+ import { readFileSync, readdirSync } from "fs";
16
+ import { resolve } from "path";
17
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18
+ async function pruneAlienAccounts(driver, platformRoot) {
19
+ const accountsDir = resolve(platformRoot, "..", "data", "accounts");
20
+ const validIds = enumerateValidAccountIds(accountsDir);
21
+ if (validIds.size === 0) {
22
+ throw new Error(
23
+ `refusing to prune: no valid accounts found under ${accountsDir} \u2014 corrupt install? not deleting anything to avoid wiping the entire graph.`
24
+ );
25
+ }
26
+ const valid = Array.from(validIds);
27
+ const session = driver.session();
28
+ try {
29
+ const peek = await session.run(
30
+ `MATCH (n)
31
+ WHERE n.accountId IS NOT NULL AND NOT n.accountId IN $valid
32
+ RETURN DISTINCT n.accountId AS aid`,
33
+ { valid }
34
+ );
35
+ const alienIds = [];
36
+ for (const record of peek.records) {
37
+ const aid = record.get("aid");
38
+ if (typeof aid === "string") alienIds.push(aid);
39
+ }
40
+ if (alienIds.length === 0) return;
41
+ const result = await session.run(
42
+ `MATCH (n)
43
+ WHERE n.accountId IS NOT NULL AND NOT n.accountId IN $valid
44
+ DETACH DELETE n
45
+ RETURN count(n) AS pruned`,
46
+ { valid }
47
+ );
48
+ const prunedRaw = result.records[0]?.get("pruned");
49
+ const pruned = typeof prunedRaw === "number" ? prunedRaw : prunedRaw?.toNumber?.() ?? 0;
50
+ console.error(
51
+ `[graph-invariant] alien-accounts pruned=${pruned} accountIds=${alienIds.join(",")}`
52
+ );
53
+ } finally {
54
+ await session.close();
55
+ }
56
+ }
57
+ function enumerateValidAccountIds(accountsDir) {
58
+ const valid = /* @__PURE__ */ new Set();
59
+ let names;
60
+ try {
61
+ names = readdirSync(accountsDir);
62
+ } catch (err) {
63
+ if (err.code === "ENOENT") return valid;
64
+ throw err;
65
+ }
66
+ for (const name of names) {
67
+ if (!UUID_RE.test(name)) continue;
68
+ const configPath = resolve(accountsDir, name, "account.json");
69
+ try {
70
+ JSON.parse(readFileSync(configPath, "utf-8"));
71
+ valid.add(name);
72
+ } catch (err) {
73
+ const code = err.code ?? "parse-error";
74
+ if (code === "ENOENT") continue;
75
+ console.error(
76
+ `[graph-invariant] account-json-skip uuid=${name} reason=${code}`
77
+ );
78
+ }
79
+ }
80
+ return valid;
81
+ }
82
+
83
+ // ../neo4j/migrations/004-project-admin-agent.ts
84
+ import { existsSync, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
85
+ import { resolve as resolve2 } from "path";
86
+ function readValidAccountDirs(accountsDir) {
87
+ const valid = [];
88
+ const entries = readdirSync2(accountsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).filter((e) => !e.name.startsWith("."));
89
+ for (const e of entries) {
90
+ const configPath = resolve2(accountsDir, e.name, "account.json");
91
+ if (!existsSync(configPath)) {
92
+ console.error(
93
+ `[platform] accounts-state STUB-DIR id=${e.name} \u2014 onboarding leak; remove or repair`
94
+ );
95
+ continue;
96
+ }
97
+ try {
98
+ JSON.parse(readFileSync2(configPath, "utf-8"));
99
+ valid.push(e.name);
100
+ } catch {
101
+ console.error(
102
+ `[platform] accounts-state CORRUPT-JSON id=${e.name} \u2014 account.json failed to parse`
103
+ );
104
+ }
105
+ }
106
+ return valid;
107
+ }
108
+ async function projectAccountAdmin(accountId, accountDir) {
109
+ const adminDir = resolve2(accountDir, "agents", "admin");
110
+ const configPath = resolve2(adminDir, "config.json");
111
+ if (!existsSync(adminDir) || !existsSync(configPath)) {
112
+ return { projected: 0, failed: 0 };
113
+ }
114
+ try {
115
+ await projectAgent(accountId, accountDir, "admin");
116
+ return { projected: 1, failed: 0 };
117
+ } catch (err) {
118
+ const msg = err instanceof Error ? err.message : String(err);
119
+ console.error(
120
+ `[admin-agent-graph-backfill] account=${accountId.slice(0, 8)} project FAILED error="${msg}"`
121
+ );
122
+ return { projected: 0, failed: 1 };
123
+ }
124
+ }
125
+ async function backfillAdminHandledBy(driver, accountId) {
126
+ const session = driver.session();
127
+ try {
128
+ const result = await session.run(
129
+ `MATCH (c:AdminConversation {accountId: $accountId})
130
+ WHERE NOT EXISTS((c)-[:HANDLED_BY]->(:Agent))
131
+ OPTIONAL MATCH (a:Agent {accountId: $accountId, slug: 'admin'})
132
+ FOREACH (_ IN CASE WHEN a IS NULL THEN [] ELSE [1] END | MERGE (c)-[:HANDLED_BY]->(a))
133
+ RETURN
134
+ count(c) AS candidates,
135
+ sum(CASE WHEN a IS NULL THEN 0 ELSE 1 END) AS edges`,
136
+ { accountId }
137
+ );
138
+ const toNum = (v) => {
139
+ if (typeof v === "number") return v;
140
+ if (v && typeof v.toNumber === "function") {
141
+ return v.toNumber();
142
+ }
143
+ return 0;
144
+ };
145
+ return {
146
+ candidates: toNum(result.records[0]?.get("candidates")),
147
+ edges: toNum(result.records[0]?.get("edges"))
148
+ };
149
+ } finally {
150
+ await session.close();
151
+ }
152
+ }
153
+ async function backfillChannel(driver, accountId) {
154
+ const session = driver.session();
155
+ try {
156
+ const result = await session.run(
157
+ `MATCH (c:Conversation {accountId: $accountId})
158
+ WHERE c.channel IS NULL
159
+ SET c.channel = 'webchat'
160
+ RETURN count(c) AS backfilled`,
161
+ { accountId }
162
+ );
163
+ const raw = result.records[0]?.get("backfilled");
164
+ if (typeof raw === "number") return raw;
165
+ if (raw && typeof raw.toNumber === "function") {
166
+ return raw.toNumber();
167
+ }
168
+ return 0;
169
+ } finally {
170
+ await session.close();
171
+ }
172
+ }
173
+ async function applyAdminAgentBackfill(driver, platformRoot) {
174
+ const accountsDir = resolve2(platformRoot, "..", "data", "accounts");
175
+ const start = Date.now();
176
+ if (!existsSync(accountsDir)) {
177
+ console.error(
178
+ `[admin-agent-graph-backfill] accounts-dir missing at ${accountsDir} \u2014 nothing to do`
179
+ );
180
+ return;
181
+ }
182
+ const accountEntries = readValidAccountDirs(accountsDir).map((name) => ({ name }));
183
+ console.error(
184
+ `[admin-agent-graph-backfill] start accounts=${accountEntries.length}`
185
+ );
186
+ let totalProjected = 0;
187
+ let totalFailed = 0;
188
+ let totalHandledByCandidates = 0;
189
+ let totalHandledByEdges = 0;
190
+ let totalChannelBackfilled = 0;
191
+ const perAccount = [];
192
+ for (const entry of accountEntries) {
193
+ const accountDir = resolve2(accountsDir, entry.name);
194
+ const accountId = entry.name;
195
+ const accountStart = Date.now();
196
+ const { projected, failed } = await projectAccountAdmin(
197
+ accountId,
198
+ accountDir
199
+ );
200
+ totalProjected += projected;
201
+ totalFailed += failed;
202
+ let handledByStats = { candidates: 0, edges: 0 };
203
+ let channelBackfilled = 0;
204
+ try {
205
+ handledByStats = await backfillAdminHandledBy(driver, accountId);
206
+ totalHandledByCandidates += handledByStats.candidates;
207
+ totalHandledByEdges += handledByStats.edges;
208
+ } catch (err) {
209
+ const msg = err instanceof Error ? err.message : String(err);
210
+ console.error(
211
+ `[admin-agent-graph-backfill] account=${accountId.slice(0, 8)} handled-by-backfill FAILED error="${msg}"`
212
+ );
213
+ }
214
+ try {
215
+ channelBackfilled = await backfillChannel(driver, accountId);
216
+ totalChannelBackfilled += channelBackfilled;
217
+ } catch (err) {
218
+ const msg = err instanceof Error ? err.message : String(err);
219
+ console.error(
220
+ `[admin-agent-graph-backfill] account=${accountId.slice(0, 8)} channel-backfill FAILED error="${msg}"`
221
+ );
222
+ }
223
+ perAccount.push({
224
+ accountId,
225
+ projected,
226
+ failed,
227
+ handledByCandidates: handledByStats.candidates,
228
+ handledByEdges: handledByStats.edges,
229
+ channelBackfilled
230
+ });
231
+ const ms2 = Date.now() - accountStart;
232
+ console.error(
233
+ `[admin-agent-graph-backfill] account=${accountId.slice(0, 8)} projected=${projected} failed=${failed} handled-by-candidates=${handledByStats.candidates} handled-by-edges=${handledByStats.edges} channel-backfilled=${channelBackfilled} ms=${ms2}`
234
+ );
235
+ }
236
+ const ms = Date.now() - start;
237
+ console.error(
238
+ `[admin-agent-graph-backfill] done totals: projected=${totalProjected} failed=${totalFailed} handled-by-candidates=${totalHandledByCandidates} handled-by-edges=${totalHandledByEdges} channel-backfilled=${totalChannelBackfilled} ms=${ms}`
239
+ );
240
+ }
241
+ async function main() {
242
+ if (!existsSync(ACCOUNTS_DIR)) {
243
+ console.error(
244
+ `[admin-agent-graph-backfill] ACCOUNTS_DIR missing at ${ACCOUNTS_DIR} \u2014 nothing to do`
245
+ );
246
+ process.exit(0);
247
+ }
248
+ const driverShim = { session: () => getSession() };
249
+ const platformRoot = resolve2(ACCOUNTS_DIR, "..", "..", "platform");
250
+ try {
251
+ await applyAdminAgentBackfill(driverShim, platformRoot);
252
+ process.exit(0);
253
+ } catch (err) {
254
+ const msg = err instanceof Error ? err.message : String(err);
255
+ console.error(`[admin-agent-graph-backfill] fatal error="${msg}"`);
256
+ process.exit(2);
257
+ }
258
+ }
259
+ if (process.argv[1]?.endsWith("004-project-admin-agent.ts")) {
260
+ main().catch((err) => {
261
+ const msg = err instanceof Error ? err.message : String(err);
262
+ console.error(`[admin-agent-graph-backfill] fatal error="${msg}"`);
263
+ process.exit(2);
264
+ });
265
+ }
266
+
267
+ // ../neo4j/migrations/005-removed-review-feature.ts
268
+ import { resolve as resolve3 } from "path";
269
+ import { unlinkSync } from "fs";
270
+ import { homedir } from "os";
271
+ import { basename, dirname } from "path";
272
+ async function removeReviewFeature(driver, platformRoot) {
273
+ const session = driver.session();
274
+ let eventsDeleted = 0;
275
+ let alertsDeleted = 0;
276
+ try {
277
+ const eventResult = await session.run(
278
+ `MATCH (e:Event)
279
+ WHERE e.actionTool = "review-digest-compose"
280
+ OR e.eventId STARTS WITH "review-digest-"
281
+ DETACH DELETE e
282
+ RETURN count(e) AS deleted`
283
+ );
284
+ eventsDeleted = toNumber(eventResult.records[0]?.get("deleted"));
285
+ const alertResult = await session.run(
286
+ `MATCH (a:ReviewAlert)
287
+ DETACH DELETE a
288
+ RETURN count(a) AS deleted`
289
+ );
290
+ alertsDeleted = toNumber(alertResult.records[0]?.get("deleted"));
291
+ } finally {
292
+ await session.close();
293
+ }
294
+ const installDirName = basename(dirname(platformRoot));
295
+ const configDir = resolve3(homedir(), `.${installDirName}`);
296
+ const artefacts = [
297
+ resolve3(configDir, "logs", "review.log"),
298
+ resolve3(configDir, "review-rules.json"),
299
+ resolve3(configDir, "review-state.json"),
300
+ resolve3(configDir, "review-pending-alerts.jsonl")
301
+ ];
302
+ for (const path of artefacts) {
303
+ try {
304
+ unlinkSync(path);
305
+ } catch (err) {
306
+ const code = err.code;
307
+ if (code === "ENOENT") continue;
308
+ console.error(
309
+ `[migration] removed-review-feature unlink-skip path=${path} reason=${code ?? "unknown"}`
310
+ );
311
+ }
312
+ }
313
+ console.error(
314
+ `[migration] removed-review-feature events=${eventsDeleted} alerts=${alertsDeleted}`
315
+ );
316
+ }
317
+ function toNumber(v) {
318
+ if (typeof v === "number") return v;
319
+ if (v && typeof v.toNumber === "function") {
320
+ return v.toNumber();
321
+ }
322
+ return 0;
323
+ }
324
+
325
+ // ../neo4j/migrations/006-prune-bogus-whatsapp-persons.ts
326
+ var POLLUTED_NAME_REGEX = "(?s).*[\\n\\[].*";
327
+ async function pruneBogusWhatsappPersons(driver) {
328
+ const session = driver.session();
329
+ try {
330
+ const nonAutoRes = await session.run(
331
+ `MATCH (p:Person {source: 'whatsapp'})
332
+ WHERE coalesce(p.participantStatus, '') <> 'auto-created'
333
+ AND p.name =~ $regex
334
+ RETURN elementId(p) AS elementId, p.name AS name`,
335
+ { regex: POLLUTED_NAME_REGEX }
336
+ );
337
+ const skippedNonAuto = nonAutoRes.records.length;
338
+ for (const record of nonAutoRes.records) {
339
+ const elementId = record.get("elementId");
340
+ const name = record.get("name");
341
+ console.error(
342
+ `[migration:whatsapp-bogus-person-prune] non-auto-match elementId=${elementId} name=${JSON.stringify(name)}`
343
+ );
344
+ }
345
+ const matchedRes = await session.run(
346
+ `MATCH (p:Person {source: 'whatsapp', participantStatus: 'auto-created'})
347
+ WHERE p.name =~ $regex
348
+ RETURN count(p) AS matched`,
349
+ { regex: POLLUTED_NAME_REGEX }
350
+ );
351
+ const matched = toNumber2(matchedRes.records[0]?.get("matched"));
352
+ if (matched === 0) {
353
+ console.error(
354
+ `[migration:whatsapp-bogus-person-prune] matched=0 deleted=0 skipped-non-auto=${skippedNonAuto}`
355
+ );
356
+ return;
357
+ }
358
+ const deleteRes = await session.run(
359
+ `MATCH (p:Person {source: 'whatsapp', participantStatus: 'auto-created'})
360
+ WHERE p.name =~ $regex
361
+ DETACH DELETE p
362
+ RETURN count(p) AS deleted`,
363
+ { regex: POLLUTED_NAME_REGEX }
364
+ );
365
+ const deleted = toNumber2(deleteRes.records[0]?.get("deleted"));
366
+ console.error(
367
+ `[migration:whatsapp-bogus-person-prune] matched=${matched} deleted=${deleted} skipped-non-auto=${skippedNonAuto}`
368
+ );
369
+ } finally {
370
+ await session.close();
371
+ }
372
+ }
373
+ function toNumber2(v) {
374
+ if (typeof v === "number") return v;
375
+ if (v && typeof v.toNumber === "function") {
376
+ return v.toNumber();
377
+ }
378
+ return 0;
379
+ }
380
+
381
+ // ../neo4j/migrations/007-conversation-archive-source.ts
382
+ async function backfillConversationArchiveSource(driver) {
383
+ const session = driver.session();
384
+ try {
385
+ const archivesSourceSet = await runCount(
386
+ session,
387
+ `MATCH (a:ConversationArchive)
388
+ WHERE a.source IS NULL
389
+ SET a.source = 'whatsapp'
390
+ RETURN count(a) AS n`
391
+ );
392
+ const archivesAgentRenamed = await runCount(
393
+ session,
394
+ `MATCH (a:ConversationArchive)
395
+ WHERE a.createdByAgent = 'whatsapp-import'
396
+ SET a.createdByAgent = 'conversation-archive'
397
+ RETURN count(a) AS n`
398
+ );
399
+ const chunksSourceSet = await runCount(
400
+ session,
401
+ `MATCH (c:Section:Conversation)
402
+ WHERE c.source IS NULL
403
+ SET c.source = 'whatsapp'
404
+ RETURN count(c) AS n`
405
+ );
406
+ const chunksAgentRenamed = await runCount(
407
+ session,
408
+ `MATCH (c:Section:Conversation)
409
+ WHERE c.createdByAgent = 'whatsapp-import'
410
+ SET c.createdByAgent = 'conversation-archive'
411
+ RETURN count(c) AS n`
412
+ );
413
+ const edgesAgentRenamed = await runCount(
414
+ session,
415
+ `MATCH (a:ConversationArchive)-[r]-()
416
+ WHERE r.createdByAgent = 'whatsapp-import'
417
+ SET r.createdByAgent = 'conversation-archive'
418
+ RETURN count(r) AS n`
419
+ );
420
+ console.error(
421
+ `[migration:conversation-archive-source] archives-source-set=${archivesSourceSet} archives-agent-renamed=${archivesAgentRenamed} chunks-source-set=${chunksSourceSet} chunks-agent-renamed=${chunksAgentRenamed} edges-agent-renamed=${edgesAgentRenamed}`
422
+ );
423
+ } finally {
424
+ await session.close();
425
+ }
426
+ }
427
+ async function runCount(session, cypher) {
428
+ const res = await session.run(cypher);
429
+ const v = res.records[0]?.get("n");
430
+ if (typeof v === "number") return v;
431
+ if (v && typeof v.toNumber === "function") {
432
+ return v.toNumber();
433
+ }
434
+ return 0;
435
+ }
436
+
437
+ // ../neo4j/migrations/008-adminuser-accountid-backfill.ts
438
+ async function backfillAdminUserAccountId(driver) {
439
+ const session = driver.session();
440
+ try {
441
+ const backfilled = await runCount2(
442
+ session,
443
+ `MATCH (au:AdminUser) WHERE au.accountId IS NULL
444
+ OPTIONAL MATCH (au)-[:ADMIN_OF]->(b:LocalBusiness)
445
+ WITH au, b WHERE b IS NOT NULL AND b.accountId IS NOT NULL
446
+ SET au.accountId = b.accountId
447
+ RETURN count(au) AS n`
448
+ );
449
+ const residualNull = await runCount2(
450
+ session,
451
+ `MATCH (au:AdminUser) WHERE au.accountId IS NULL
452
+ RETURN count(au) AS n`
453
+ );
454
+ console.error(
455
+ `[migration:adminuser-accountid] backfilled=${backfilled} residual-null=${residualNull}`
456
+ );
457
+ if (residualNull > 0) {
458
+ throw new Error(
459
+ `migration:adminuser-accountid: ${residualNull} :AdminUser node(s) still carry a null accountId after backfill \u2014 no ADMIN_OF edge to derive from. Manual reconciliation required (cypher-shell into the graph; either DELETE the orphan or attach a valid ADMIN_OF edge and re-run boot).`
460
+ );
461
+ }
462
+ } finally {
463
+ await session.close();
464
+ }
465
+ }
466
+ async function runCount2(session, cypher) {
467
+ const res = await session.run(cypher);
468
+ const v = res.records[0]?.get("n");
469
+ if (typeof v === "number") return v;
470
+ if (v && typeof v.toNumber === "function") {
471
+ return v.toNumber();
472
+ }
473
+ return 0;
474
+ }
475
+
476
+ // ../neo4j/migrations/009-conversation-archive-title.ts
477
+ var ISO_DATE_RE = /^(\d{4}-\d{2}-\d{2})/;
478
+ function isoToYmd(iso) {
479
+ if (typeof iso !== "string") return "?";
480
+ const m = iso.match(ISO_DATE_RE);
481
+ return m ? m[1] : "?";
482
+ }
483
+ function pickName(row) {
484
+ if (row.labels.includes("AdminUser")) {
485
+ if (row.displayName && row.displayName.trim()) return row.displayName.trim();
486
+ if (row.slug && row.slug.trim()) return row.slug.trim();
487
+ }
488
+ if (row.labels.includes("Person")) {
489
+ const full = [row.givenName, row.familyName].filter((s) => typeof s === "string" && s.trim().length > 0).map((s) => s.trim()).join(" ");
490
+ if (full) return full;
491
+ }
492
+ return row.elementId.slice(0, 8);
493
+ }
494
+ function asString(v) {
495
+ return typeof v === "string" ? v : void 0;
496
+ }
497
+ async function backfillConversationArchiveTitle(driver) {
498
+ const session = driver.session();
499
+ let archivesTitled = 0;
500
+ let degradedNoDates = 0;
501
+ let degradedNoNames = 0;
502
+ try {
503
+ const archives = await session.run(
504
+ `MATCH (a:ConversationArchive)
505
+ WHERE a.title IS NULL
506
+ RETURN elementId(a) AS elemId,
507
+ coalesce(a.source, 'whatsapp') AS source,
508
+ a.lastIngestedMessageAt AS lastAt`
509
+ );
510
+ for (const rec of archives.records) {
511
+ const elemId = rec.get("elemId");
512
+ const source = rec.get("source");
513
+ const fallbackLastAt = rec.get("lastAt");
514
+ const partRes = await session.run(
515
+ `MATCH (p)-[:PARTICIPANT_IN]->(a:ConversationArchive)
516
+ WHERE elementId(a) = $elemId AND (p:Person OR p:AdminUser)
517
+ RETURN elementId(p) AS elemId, labels(p) AS labels, properties(p) AS props`,
518
+ { elemId }
519
+ );
520
+ const owner = [];
521
+ const others = [];
522
+ let anyDegraded = false;
523
+ for (const r of partRes.records) {
524
+ const labels = r.get("labels") || [];
525
+ const props = r.get("props") || {};
526
+ const row = {
527
+ elementId: r.get("elemId"),
528
+ labels,
529
+ displayName: asString(props.displayName),
530
+ slug: asString(props.slug),
531
+ givenName: asString(props.givenName),
532
+ familyName: asString(props.familyName)
533
+ };
534
+ const name = pickName(row);
535
+ if (name === row.elementId.slice(0, 8)) anyDegraded = true;
536
+ if (labels.includes("AdminUser")) owner.push(row);
537
+ else others.push(row);
538
+ }
539
+ if (anyDegraded) degradedNoNames += 1;
540
+ const ownerName = owner.length > 0 ? pickName(owner[0]) : "?";
541
+ const otherNames = others.length > 0 ? others.map((o) => pickName(o)).join(", ") : "?";
542
+ const datesRes = await session.run(
543
+ `MATCH (a:ConversationArchive)-[:HAS_SECTION]->(c:Section:Conversation)
544
+ WHERE elementId(a) = $elemId
545
+ WITH c, c.firstMessageAt AS first, c.lastMessageAt AS last
546
+ RETURN min(first) AS firstAt, max(last) AS lastAt`,
547
+ { elemId }
548
+ );
549
+ let firstAt = asString(datesRes.records[0]?.get("firstAt"));
550
+ let lastAt = asString(datesRes.records[0]?.get("lastAt"));
551
+ if (!firstAt || !lastAt) {
552
+ if (fallbackLastAt) {
553
+ firstAt = firstAt ?? fallbackLastAt;
554
+ lastAt = lastAt ?? fallbackLastAt;
555
+ }
556
+ }
557
+ if (!firstAt || !lastAt) {
558
+ degradedNoDates += 1;
559
+ }
560
+ const firstYmd = isoToYmd(firstAt);
561
+ const lastYmd = isoToYmd(lastAt);
562
+ const title = `${source} \xB7 ${ownerName} \u2194 ${otherNames} \xB7 ${firstYmd}\u2192${lastYmd}`;
563
+ const upd = await session.run(
564
+ `MATCH (a:ConversationArchive)
565
+ WHERE elementId(a) = $elemId AND a.title IS NULL
566
+ SET a.title = $title
567
+ RETURN count(a) AS n`,
568
+ { elemId, title }
569
+ );
570
+ const n = upd.records[0]?.get("n");
571
+ const v = typeof n === "number" ? n : n?.toNumber?.() ?? 0;
572
+ if (v > 0) archivesTitled += 1;
573
+ }
574
+ console.error(
575
+ `[migration:conversation-archive-title] archives-titled=${archivesTitled} degraded-no-dates=${degradedNoDates} degraded-no-names=${degradedNoNames}`
576
+ );
577
+ } finally {
578
+ await session.close();
579
+ }
580
+ }
581
+
582
+ // app/lib/neo4j-migrations.ts
583
+ var BOOT_MIGRATIONS = [
584
+ { fileName: "003-person-name-eradicate.cypher", slug: "person-name-eradicate" }
585
+ ];
586
+ async function applyBootMigrations(driver, platformRoot) {
587
+ for (const migration of BOOT_MIGRATIONS) {
588
+ await applyOne(driver, platformRoot, migration);
589
+ }
590
+ try {
591
+ await pruneAlienAccounts(driver, platformRoot);
592
+ } catch (err) {
593
+ const msg = err instanceof Error ? err.message : String(err);
594
+ console.error(`[migration] failed prune-alien-accounts error="${msg}"`);
595
+ }
596
+ try {
597
+ await applyAdminAgentBackfill(driver, platformRoot);
598
+ } catch (err) {
599
+ const msg = err instanceof Error ? err.message : String(err);
600
+ console.error(`[migration] failed admin-agent-graph-backfill error="${msg}"`);
601
+ }
602
+ try {
603
+ await removeReviewFeature(driver, platformRoot);
604
+ } catch (err) {
605
+ const msg = err instanceof Error ? err.message : String(err);
606
+ console.error(`[migration] failed removed-review-feature error="${msg}"`);
607
+ }
608
+ try {
609
+ await pruneBogusWhatsappPersons(driver);
610
+ } catch (err) {
611
+ const msg = err instanceof Error ? err.message : String(err);
612
+ console.error(`[migration] failed prune-bogus-whatsapp-persons error="${msg}"`);
613
+ }
614
+ try {
615
+ await backfillConversationArchiveSource(driver);
616
+ } catch (err) {
617
+ const msg = err instanceof Error ? err.message : String(err);
618
+ console.error(`[migration] failed conversation-archive-source error="${msg}"`);
619
+ }
620
+ await backfillAdminUserAccountId(driver);
621
+ try {
622
+ await backfillConversationArchiveTitle(driver);
623
+ } catch (err) {
624
+ const msg = err instanceof Error ? err.message : String(err);
625
+ console.error(`[migration] failed conversation-archive-title error="${msg}"`);
626
+ }
627
+ }
628
+ async function applyOne(driver, platformRoot, migration) {
629
+ const path = resolve4(platformRoot, "neo4j/migrations", migration.fileName);
630
+ let cypher;
631
+ try {
632
+ cypher = readFileSync3(path, "utf-8");
633
+ } catch (err) {
634
+ const msg = err instanceof Error ? err.message : String(err);
635
+ console.error(
636
+ `[migration] failed ${migration.slug} error=cannot-read-file path=${path} msg=${msg}`
637
+ );
638
+ return;
639
+ }
640
+ const session = driver.session();
641
+ try {
642
+ const result = await session.run(cypher);
643
+ const record = result.records[0];
644
+ const removedRaw = record?.get("removed");
645
+ const removed = toNumber3(removedRaw);
646
+ console.error(
647
+ `[migration] applied ${migration.slug} removed=${removed}`
648
+ );
649
+ } catch (err) {
650
+ const msg = err instanceof Error ? err.message : String(err);
651
+ console.error(`[migration] failed ${migration.slug} error="${msg}"`);
652
+ } finally {
653
+ await session.close();
654
+ }
655
+ }
656
+ function toNumber3(v) {
657
+ if (typeof v === "number") return v;
658
+ if (v && typeof v.toNumber === "function") {
659
+ return v.toNumber();
660
+ }
661
+ return 0;
662
+ }
663
+ export {
664
+ applyBootMigrations
665
+ };
@@ -1 +1 @@
1
- import{t as e}from"./jsx-runtime-DH5S-MwB.js";var t=e();function n({checked:e,onChange:n,label:r,disabled:i}){return(0,t.jsxs)(`label`,{className:`maxy-checkbox${i?` maxy-checkbox--disabled`:``}`,children:[(0,t.jsx)(`input`,{type:`checkbox`,checked:e,onChange:e=>n(e.target.checked),disabled:i}),(0,t.jsx)(`span`,{className:`maxy-checkbox__box`,children:`✱`}),r&&(0,t.jsx)(`span`,{className:`maxy-checkbox__label`,children:r})]})}export{n as t};
1
+ import{t as e}from"./jsx-runtime-WW3O7tSz.js";var t=e();function n({checked:e,onChange:n,label:r,disabled:i}){return(0,t.jsxs)(`label`,{className:`maxy-checkbox${i?` maxy-checkbox--disabled`:``}`,children:[(0,t.jsx)(`input`,{type:`checkbox`,checked:e,onChange:e=>n(e.target.checked),disabled:i}),(0,t.jsx)(`span`,{className:`maxy-checkbox__box`,children:`✱`}),r&&(0,t.jsx)(`span`,{className:`maxy-checkbox__label`,children:r})]})}export{n as t};