@rubytech/create-realagent 1.0.818 → 1.0.820

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 (58) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.d.ts +2 -0
  3. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.d.ts.map +1 -0
  4. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +168 -0
  5. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -0
  6. package/payload/platform/lib/graph-write/dist/index.d.ts +28 -5
  7. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
  8. package/payload/platform/lib/graph-write/dist/index.js +97 -3
  9. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  10. package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +191 -0
  11. package/payload/platform/lib/graph-write/src/index.ts +90 -9
  12. package/payload/platform/neo4j/schema.cypher +24 -0
  13. package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +25 -8
  14. package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -1
  15. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +70 -20
  16. package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
  17. package/payload/platform/plugins/docs/references/graph.md +20 -0
  18. package/payload/platform/plugins/docs/references/internals.md +16 -0
  19. package/payload/platform/plugins/email/PLUGIN.md +2 -0
  20. package/payload/platform/plugins/memory/PLUGIN.md +6 -0
  21. package/payload/platform/plugins/memory/mcp/dist/index.js +15 -4
  22. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  23. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +8 -0
  24. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  25. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +26 -2
  26. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  27. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +20 -0
  28. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
  29. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +40 -1
  30. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  31. package/payload/platform/plugins/memory/references/schema-base.md +8 -0
  32. package/payload/platform/plugins/tasks/PLUGIN.md +2 -2
  33. package/payload/platform/plugins/tasks/mcp/dist/index.js +10 -5
  34. package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -1
  35. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts +27 -1
  36. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts.map +1 -1
  37. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js +45 -2
  38. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js.map +1 -1
  39. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.d.ts +20 -1
  40. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.d.ts.map +1 -1
  41. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.js +46 -6
  42. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.js.map +1 -1
  43. package/payload/server/chunk-5OG7TUQL.js +315 -0
  44. package/payload/server/chunk-CZGOB575.js +593 -0
  45. package/payload/server/chunk-NUXYHO6N.js +10079 -0
  46. package/payload/server/chunk-SALVIGXH.js +1116 -0
  47. package/payload/server/chunk-ZT6LKDTP.js +2238 -0
  48. package/payload/server/client-pool-IQU6H43X.js +32 -0
  49. package/payload/server/cloudflare-task-tracker-Q4X5BYR7.js +17 -0
  50. package/payload/server/maxy-edge.js +4 -3
  51. package/payload/server/neo4j-migrations-CYIKMSEO.js +366 -0
  52. package/payload/server/public/assets/admin-BoGPEBe_.js +352 -0
  53. package/payload/server/public/assets/{graph-DeH6ulGh.js → graph-LLMJa4Ch.js} +1 -1
  54. package/payload/server/public/assets/{page-WIAWD2Oi.js → page-DoaF3DB0.js} +1 -1
  55. package/payload/server/public/graph.html +2 -2
  56. package/payload/server/public/index.html +2 -2
  57. package/payload/server/server.js +276 -16
  58. package/payload/server/public/assets/admin-CdVYoqKD.js +0 -352
@@ -51,7 +51,7 @@ import {
51
51
  vncLog,
52
52
  waitForExit,
53
53
  writeChromiumWrapper
54
- } from "./chunk-AJLGI7Y3.js";
54
+ } from "./chunk-NUXYHO6N.js";
55
55
  import {
56
56
  agentLogStream,
57
57
  clearSessionHistory,
@@ -79,12 +79,27 @@ import {
79
79
  sigtermFlushStreamLogs,
80
80
  unregisterSession,
81
81
  validateSession
82
- } from "./chunk-ON3LBL2Y.js";
82
+ } from "./chunk-SALVIGXH.js";
83
83
  import {
84
84
  ACCOUNTS_DIR,
85
+ PLATFORM_ROOT,
86
+ getDefaultAccountId,
87
+ resolveAccount,
88
+ resolveAgentConfig,
89
+ resolveDefaultAgentSlug,
90
+ resolveUserAccounts,
91
+ validateAgentSlug
92
+ } from "./chunk-5OG7TUQL.js";
93
+ import {
94
+ CLOUDFLARE_TASK_DIAGNOSTICS,
95
+ appendCloudflareSteps,
96
+ completeCloudflareTask,
97
+ openCloudflareTask,
98
+ readTunnelState
99
+ } from "./chunk-CZGOB575.js";
100
+ import {
85
101
  GREETING_DIRECTIVE,
86
102
  HAIKU_MODEL,
87
- PLATFORM_ROOT,
88
103
  __commonJS,
89
104
  __toESM,
90
105
  backfillNullUserIdConversations,
@@ -99,7 +114,6 @@ import {
99
114
  findRecentConversation,
100
115
  generateSessionLabel,
101
116
  getAgentSessionIdForConversation,
102
- getDefaultAccountId,
103
117
  getGroupParticipants,
104
118
  getMessagesSince,
105
119
  getRecentMessages,
@@ -110,16 +124,11 @@ import {
110
124
  loadOnboardingStep,
111
125
  projectAgent,
112
126
  renameConversation,
113
- resolveAccount,
114
- resolveAgentConfig,
115
- resolveDefaultAgentSlug,
116
- resolveUserAccounts,
117
127
  runBootMigrations,
118
- validateAgentSlug,
119
128
  verifyAndGetConversationUpdatedAt,
120
129
  verifyConversationOwnership,
121
130
  writeAdminUserAndPerson
122
- } from "./chunk-PXQA2MA3.js";
131
+ } from "./chunk-ZT6LKDTP.js";
123
132
 
124
133
  // ../lib/graph-trash/dist/index.js
125
134
  var require_dist = __commonJS({
@@ -7275,7 +7284,7 @@ var app11 = new Hono();
7275
7284
  app11.post("/cancel", requireAdminSession, async (c) => {
7276
7285
  const session_key = c.var.sessionKey;
7277
7286
  try {
7278
- const { interruptClient: interruptClient2 } = await import("./client-pool-GBY5I2KQ.js");
7287
+ const { interruptClient: interruptClient2 } = await import("./client-pool-IQU6H43X.js");
7279
7288
  await interruptClient2(session_key);
7280
7289
  return c.json({ ok: true });
7281
7290
  } catch (err) {
@@ -8642,6 +8651,12 @@ function resolvePort() {
8642
8651
  }
8643
8652
  return n;
8644
8653
  }
8654
+ function isValidTunnelId(s) {
8655
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
8656
+ }
8657
+ function isValidTunnelName(s) {
8658
+ return /^[A-Za-z0-9_.-]{1,64}$/.test(s);
8659
+ }
8645
8660
  function validateBody(body) {
8646
8661
  if (!body || typeof body !== "object") return { field: "request", message: "Invalid request body" };
8647
8662
  if (typeof body.adminLabel !== "string" || !isValidLabel(body.adminLabel)) {
@@ -8666,6 +8681,20 @@ function validateBody(body) {
8666
8681
  return { field: "request", message: "Apex must be a valid domain or omitted." };
8667
8682
  }
8668
8683
  }
8684
+ const idSet = typeof body.tunnelId === "string" && body.tunnelId.length > 0;
8685
+ const nameSet = typeof body.tunnelName === "string" && body.tunnelName.length > 0;
8686
+ if (!idSet && !nameSet) {
8687
+ return { field: "tunnel", message: "Pick an existing tunnel from the list, or type a name to create a new one." };
8688
+ }
8689
+ if (idSet && nameSet) {
8690
+ return { field: "tunnel", message: "Pick OR create \u2014 not both. Clear one of the tunnel inputs." };
8691
+ }
8692
+ if (idSet && !isValidTunnelId(body.tunnelId)) {
8693
+ return { field: "tunnel", message: "Selected tunnel UUID is malformed." };
8694
+ }
8695
+ if (nameSet && !isValidTunnelName(body.tunnelName)) {
8696
+ return { field: "tunnel", message: "New-tunnel name must be 1\u201364 characters of letters, digits, dot, dash, underscore." };
8697
+ }
8669
8698
  return null;
8670
8699
  }
8671
8700
  var app21 = new Hono();
@@ -8778,6 +8807,91 @@ ${result.stderr}` : ""}`;
8778
8807
  );
8779
8808
  return c.json(success, 200);
8780
8809
  });
8810
+ var TUNNELS_TIMEOUT_MS = 30 * 1e3;
8811
+ app21.get("/tunnels", requireAdminSession, async (c) => {
8812
+ const started = Date.now();
8813
+ const sessionKey = c.var.sessionKey;
8814
+ let correlationId;
8815
+ let sessionKeyTail;
8816
+ let streamLogPath;
8817
+ function tag() {
8818
+ if (!correlationId) return "";
8819
+ return ` conversationId=${correlationId} session_key=${sessionKeyTail ?? "unknown"}`;
8820
+ }
8821
+ function log(line) {
8822
+ console.log(`[cloudflare-tunnels] ${line}${tag()}`);
8823
+ }
8824
+ function logErr(line) {
8825
+ console.error(`[cloudflare-tunnels] ${line}${tag()}`);
8826
+ }
8827
+ function err(field, message, output) {
8828
+ logErr(`phase=error field=${field} reason="${message.slice(0, 160).replace(/"/g, "'")}"`);
8829
+ if (streamLogPath) {
8830
+ writeRouteMilestone(
8831
+ streamLogPath,
8832
+ "cloudflare-tunnels",
8833
+ `phase=error field=${field} reason="${message.slice(0, 160).replace(/"/g, "'")}"`
8834
+ );
8835
+ }
8836
+ const body = { ok: false, field, message, output, correlationId, streamLogPath };
8837
+ return c.json(body, 200);
8838
+ }
8839
+ const accountId = getAccountIdForSession(sessionKey);
8840
+ correlationId = getConversationIdForSession(sessionKey);
8841
+ sessionKeyTail = sessionKey.slice(-8);
8842
+ if (!accountId) return err("session", "No account bound to session \u2014 refresh chat.");
8843
+ if (!correlationId) return err("session", "No active conversation for session \u2014 refresh chat.");
8844
+ streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
8845
+ const brand = loadBrandInfo();
8846
+ const certPath = resolve14(homedir(), brand.configDir, "cloudflared", "cert.pem");
8847
+ const { existsSync: existsSync22 } = await import("fs");
8848
+ if (!existsSync22(certPath)) {
8849
+ return err("cert", `Cloudflare origin certificate is not on disk yet (${certPath}). Complete the Cloudflare login first by submitting the form once \u2014 the OAuth flow writes cert.pem.`);
8850
+ }
8851
+ const result = await runFormSpawn({
8852
+ scriptPath: "cloudflared",
8853
+ args: ["--origincert", certPath, "tunnel", "list", "--output", "json"],
8854
+ streamLogPath,
8855
+ correlationId,
8856
+ timeoutMs: TUNNELS_TIMEOUT_MS,
8857
+ log,
8858
+ logErr,
8859
+ broadcast: broadcastToConversation
8860
+ });
8861
+ const combined = `${result.stdout}${result.stderr ? `
8862
+ ---
8863
+ ${result.stderr}` : ""}`;
8864
+ if (result.code !== 0) {
8865
+ const message = result.timedOut ? `cloudflared tunnel list timed out after ${TUNNELS_TIMEOUT_MS / 1e3}s` : `cloudflared tunnel list exited with code ${result.code ?? "null"}${result.signal ? ` (signal ${result.signal})` : ""}`;
8866
+ return err("script", message, combined);
8867
+ }
8868
+ let tunnels;
8869
+ try {
8870
+ const parsed = JSON.parse(result.stdout.trim());
8871
+ if (!Array.isArray(parsed)) throw new Error("cloudflared response is not an array");
8872
+ tunnels = parsed.filter((t) => t.deleted_at == null).map((t) => {
8873
+ if (typeof t.id !== "string" || typeof t.name !== "string") {
8874
+ throw new Error("tunnel entry missing id or name");
8875
+ }
8876
+ return { id: t.id, name: t.name };
8877
+ });
8878
+ } catch (e) {
8879
+ return err(
8880
+ "script",
8881
+ `cloudflared returned malformed JSON: ${e instanceof Error ? e.message : String(e)}`,
8882
+ combined
8883
+ );
8884
+ }
8885
+ const total = Date.now() - started;
8886
+ log(`phase=response-sent total_ms=${total} count=${tunnels.length}`);
8887
+ writeRouteMilestone(
8888
+ streamLogPath,
8889
+ "cloudflare-tunnels",
8890
+ `phase=response-sent total_ms=${total} count=${tunnels.length} source=cloudflared`
8891
+ );
8892
+ const success = { ok: true, tunnels, correlationId, streamLogPath };
8893
+ return c.json(success, 200);
8894
+ });
8781
8895
  app21.post("/setup", requireAdminSession, async (c) => {
8782
8896
  const started = Date.now();
8783
8897
  const sessionKey = c.var.sessionKey;
@@ -8867,10 +8981,35 @@ app21.post("/setup", requireAdminSession, async (c) => {
8867
8981
  if (await isActionActive("cloudflare-setup")) {
8868
8982
  return err("request", "Another Cloudflare setup is already running. Wait for it to finish before starting a new one.");
8869
8983
  }
8984
+ let cloudflareTask;
8985
+ try {
8986
+ cloudflareTask = await openCloudflareTask({
8987
+ accountId,
8988
+ conversationKey: sessionKey,
8989
+ inputsProvided: ["adminLabel", "adminDomain", publicFqdn ? "publicLabel" : null, apex ? "apex" : null, "password"].filter((s) => typeof s === "string"),
8990
+ inputs: {
8991
+ adminLabel: body.adminLabel,
8992
+ adminDomain: body.adminDomain,
8993
+ publicLabel: body.publicLabel,
8994
+ publicDomain: body.publicDomain,
8995
+ apex: body.apex
8996
+ },
8997
+ messageId: typeof body.messageId === "string" && body.messageId.length > 0 ? body.messageId : void 0
8998
+ });
8999
+ log(`phase=task-opened taskId=${cloudflareTask.taskId}`);
9000
+ } catch (e) {
9001
+ return err("script", `Failed to open process-provenance Task record: ${e instanceof Error ? e.message : String(e)}`);
9002
+ }
8870
9003
  let actionId;
8871
9004
  let unit;
8872
9005
  try {
8873
- const launched = await launchAction("cloudflare-setup", { positional: args, streamLogPath, accountDir });
9006
+ const launched = await launchAction("cloudflare-setup", {
9007
+ positional: args,
9008
+ streamLogPath,
9009
+ accountDir,
9010
+ tunnelId: typeof body.tunnelId === "string" && body.tunnelId.length > 0 ? body.tunnelId : void 0,
9011
+ tunnelName: typeof body.tunnelName === "string" && body.tunnelName.length > 0 ? body.tunnelName : void 0
9012
+ });
8874
9013
  actionId = launched.actionId;
8875
9014
  unit = launched.unit;
8876
9015
  } catch (e) {
@@ -8879,8 +9018,27 @@ app21.post("/setup", requireAdminSession, async (c) => {
8879
9018
  log(`phase=action-launched id=${actionId} unit=${unit}`);
8880
9019
  void (async () => {
8881
9020
  const status = await waitForExit(unit, SETUP_TIMEOUT_MS);
9021
+ try {
9022
+ const appendedSteps = await appendCloudflareSteps(cloudflareTask.taskId, accountId, streamLogPath);
9023
+ log(`phase=task-steps-appended count=${appendedSteps.length}`);
9024
+ } catch (e) {
9025
+ logErr(`phase=task-steps-append-failed reason="${e instanceof Error ? e.message : String(e)}"`);
9026
+ }
8882
9027
  if (!status || status.execMainStatus !== 0) {
8883
9028
  logErr(`phase=post-exit-skipped reason=${status ? `exit=${status.execMainStatus}` : "unit-gone"} id=${actionId}`);
9029
+ try {
9030
+ const diagnostic = status ? `${CLOUDFLARE_TASK_DIAGNOSTICS.scriptExitedNonzero} exit=${status.execMainStatus}` : CLOUDFLARE_TASK_DIAGNOSTICS.endpointDiedPreReconcile;
9031
+ await completeCloudflareTask({
9032
+ taskId: cloudflareTask.taskId,
9033
+ taskElementId: cloudflareTask.taskElementId,
9034
+ accountId,
9035
+ conversationKey: sessionKey,
9036
+ status: "failed",
9037
+ errorMessage: diagnostic
9038
+ });
9039
+ } catch (e) {
9040
+ logErr(`phase=task-close-failed status=failed reason="${e instanceof Error ? e.message : String(e)}"`);
9041
+ }
8884
9042
  return;
8885
9043
  }
8886
9044
  const candidates = [publicFqdn, apex].filter((h) => typeof h === "string" && h.length > 0);
@@ -8900,6 +9058,30 @@ app21.post("/setup", requireAdminSession, async (c) => {
8900
9058
  logErr(`phase=alias-domain-write host=${host} result=error reason="${e instanceof Error ? e.message : String(e)}"`);
8901
9059
  }
8902
9060
  }
9061
+ try {
9062
+ const tunnelState = readTunnelState(brand.configDir);
9063
+ const allHostnames = [adminFqdn, publicFqdn, apex].filter(
9064
+ (h) => typeof h === "string" && h.length > 0
9065
+ );
9066
+ const hostnameRecords = allHostnames.map((value) => ({
9067
+ hostnameValue: value,
9068
+ // Apex heuristic: exactly one dot in the FQDN. Mirrors setup-tunnel.sh:332.
9069
+ isApex: (value.match(/\./g)?.length ?? 0) === 1
9070
+ }));
9071
+ await completeCloudflareTask({
9072
+ taskId: cloudflareTask.taskId,
9073
+ taskElementId: cloudflareTask.taskElementId,
9074
+ accountId,
9075
+ conversationKey: sessionKey,
9076
+ tunnelId: tunnelState?.tunnelId,
9077
+ tunnelName: tunnelState?.tunnelName,
9078
+ hostnames: tunnelState ? hostnameRecords : void 0,
9079
+ status: "completed"
9080
+ });
9081
+ log(`phase=task-closed status=completed tunnelId=${tunnelState?.tunnelId ?? "absent"} hostnames=${allHostnames.length}`);
9082
+ } catch (e) {
9083
+ logErr(`phase=task-close-failed status=completed reason="${e instanceof Error ? e.message : String(e)}"`);
9084
+ }
8903
9085
  log(`phase=done action=${actionId}`);
8904
9086
  })().catch((e) => logErr(`post-exit handler threw: ${e}`));
8905
9087
  const total = Date.now() - started;
@@ -10300,6 +10482,12 @@ async function handleDefault(c, accountId) {
10300
10482
  var NEIGHBOURHOOD_SEARCH_DEFAULT_LIMIT = 100;
10301
10483
  var NEIGHBOURHOOD_SEARCH_MAX_LIMIT = 2e3;
10302
10484
  var NEIGHBOURHOOD_LIMIT = 2e3;
10485
+ var CLUSTER_CONVERSATION_LABELS = /* @__PURE__ */ new Set([
10486
+ "Conversation",
10487
+ "AdminConversation",
10488
+ "PublicConversation"
10489
+ ]);
10490
+ var MAX_CLUSTER_MESSAGES = 200;
10303
10491
  async function handleNeighbourhood(c, accountId) {
10304
10492
  const elementId = c.req.query("elementId");
10305
10493
  if (!elementId) {
@@ -10337,7 +10525,12 @@ async function handleNeighbourhood(c, accountId) {
10337
10525
  // `neo4j.int` keeps the LIMIT value as a 64-bit Integer on the wire —
10338
10526
  // bare JS `number` works on Pi 4 today but errors on driver versions
10339
10527
  // that strictly type-check LIMIT clauses against Long.
10340
- neighbourhoodLimit: neo4j2.int(NEIGHBOURHOOD_LIMIT)
10528
+ neighbourhoodLimit: neo4j2.int(NEIGHBOURHOOD_LIMIT),
10529
+ // Task 886 §C — same Long-int discipline for the cluster-expand cap.
10530
+ // Used by NEIGHBOURHOOD_CYPHER's `siblingMessages[0..$clusterMessagesCap]`
10531
+ // slice. The search-intersect variant ignores it (cluster-expand
10532
+ // does not apply when search is narrowing the canvas).
10533
+ clusterMessagesCap: neo4j2.int(MAX_CLUSTER_MESSAGES)
10341
10534
  };
10342
10535
  if (allowedIds !== null) cypherParams.allowedIds = allowedIds;
10343
10536
  const result = await session.executeRead(async (tx) => {
@@ -10360,6 +10553,15 @@ async function handleNeighbourhood(c, accountId) {
10360
10553
  console.error(
10361
10554
  `[graph-page] load mode=neighbourhood account=${accountId} agentActions=${includeAgentActions} elementId=${elementId} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
10362
10555
  );
10556
+ if (allowedIds === null) {
10557
+ const conversationNode = nodes.find((n) => n.labels.some((lbl) => CLUSTER_CONVERSATION_LABELS.has(lbl)));
10558
+ const messageCount = nodes.filter((n) => n.labels.includes("Message")).length;
10559
+ if (conversationNode && messageCount >= 2) {
10560
+ console.error(
10561
+ `[graph] cluster-expand kind=conversation conversationId=${conversationNode.id} messageCount=${messageCount} reason=click-target-is-${nodes[0]?.labels.includes("Message") ? "message" : "conversation"}`
10562
+ );
10563
+ }
10564
+ }
10363
10565
  if (allowedIds !== null) {
10364
10566
  console.error(
10365
10567
  `[graph-page] neighbourhood-search-intersect q="${q}" allowed=${allowedIds.length} root=${elementId} rendered=${nodes.length}`
@@ -10460,15 +10662,50 @@ var NEIGHBOURHOOD_CYPHER = `
10460
10662
  AND n.accountId = $accountId
10461
10663
  AND NOT n:Trashed
10462
10664
  AND n.deletedAt IS NULL
10665
+
10666
+ // Resolve the owning Conversation: either n itself if it carries a
10667
+ // Conversation sublabel, or the Conversation reached by one PART_OF
10668
+ // hop (Messages \u2192 Conversation). For non-cluster roots both branches
10669
+ // null out and \`conv\` stays null.
10670
+ OPTIONAL MATCH (n)-[:PART_OF]->(convFromMsg:Conversation)
10671
+ WHERE convFromMsg.accountId = $accountId
10672
+ AND NOT convFromMsg:Trashed
10673
+ AND convFromMsg.deletedAt IS NULL
10674
+ WITH n, CASE
10675
+ WHEN any(lbl IN labels(n) WHERE lbl IN ['Conversation','AdminConversation','PublicConversation']) THEN n
10676
+ ELSE convFromMsg
10677
+ END AS conv
10678
+
10679
+ // Pull all sibling messages of that conversation (or all messages of
10680
+ // n itself when n is a Conversation). EXCLUDE n from the collection
10681
+ // so the later \`[n] + \u2026\` doesn't double-count.
10682
+ OPTIONAL MATCH (conv)<-[:PART_OF]-(siblingMsg:Message)
10683
+ WHERE siblingMsg.accountId = $accountId
10684
+ AND NOT siblingMsg:Trashed
10685
+ AND siblingMsg.deletedAt IS NULL
10686
+ AND elementId(siblingMsg) <> elementId(n)
10687
+ WITH n, conv, collect(DISTINCT siblingMsg) AS siblingMessages
10688
+ WITH n, conv, siblingMessages[0..$clusterMessagesCap] AS clusterMessages
10689
+
10463
10690
  OPTIONAL MATCH (n)-[]-(m)
10464
10691
  WHERE m.accountId = $accountId
10465
10692
  AND NOT m:Trashed
10466
10693
  AND m.deletedAt IS NULL
10467
10694
  AND NOT any(lbl IN labels(m) WHERE lbl IN $agentActionLabels)
10468
- WITH n, m
10695
+ WITH n, conv, clusterMessages, m
10469
10696
  LIMIT $neighbourhoodLimit
10470
- WITH n, collect(DISTINCT m) AS neighbours
10471
- WITH [n] + [x IN neighbours WHERE x IS NOT NULL] AS windowNodes
10697
+ WITH n, conv, clusterMessages, collect(DISTINCT m) AS neighbours
10698
+
10699
+ // Compose the window: root + (conv if non-null and not already n) +
10700
+ // cluster messages + 1-hop neighbours not already in the cluster.
10701
+ WITH [n]
10702
+ + (CASE WHEN conv IS NOT NULL AND elementId(conv) <> elementId(n) THEN [conv] ELSE [] END)
10703
+ + clusterMessages
10704
+ + [x IN neighbours WHERE x IS NOT NULL
10705
+ AND elementId(x) <> elementId(n)
10706
+ AND (conv IS NULL OR elementId(x) <> elementId(conv))
10707
+ AND none(c IN clusterMessages WHERE elementId(c) = elementId(x))]
10708
+ AS windowNodes
10472
10709
  WITH windowNodes, [x IN windowNodes | elementId(x)] AS windowIds
10473
10710
  UNWIND windowNodes AS w
10474
10711
  OPTIONAL MATCH (w)-[r]-(o)
@@ -12356,6 +12593,29 @@ var bootEntitlement = bootAccountConfig ? resolveEntitlement(
12356
12593
  }
12357
12594
  ) : null;
12358
12595
  autoDeliverPremiumPlugins(bootEntitlement?.purchasedPlugins ?? void 0);
12596
+ (async () => {
12597
+ if (!bootAccount) return;
12598
+ try {
12599
+ const { recoverRunningCloudflareTasks } = await import("./cloudflare-task-tracker-Q4X5BYR7.js");
12600
+ const result = await recoverRunningCloudflareTasks(
12601
+ bootAccount.accountId,
12602
+ configDirForWhatsApp,
12603
+ // No conversationKey at boot — the tracker's writeNodeWithEdges path
12604
+ // requires it for the Conversation join, but the recovery path is
12605
+ // operating on Tasks whose Conversation is already linked. Pass null
12606
+ // and let the tracker pick up the linked Conversation via the Task's
12607
+ // existing edge.
12608
+ null
12609
+ );
12610
+ if (result.scanned > 0) {
12611
+ console.error(
12612
+ `[task] reconciler-startup kind=cloudflare-tunnel-login scanned=${result.scanned} resolved=${result.resolved.length}`
12613
+ );
12614
+ }
12615
+ } catch (err) {
12616
+ console.error(`[task] reconciler-startup failed: ${err instanceof Error ? err.message : String(err)}`);
12617
+ }
12618
+ })();
12359
12619
  var bootEnabled = Array.isArray(bootAccountConfig?.enabledPlugins) ? bootAccountConfig.enabledPlugins : [];
12360
12620
  var bootDelivered = [];
12361
12621
  var bootDistMissing = [];