@runfusion/fusion 0.75.0-beta.2 → 0.75.1-beta.0

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 (113) hide show
  1. package/dist/bin.js +184 -48
  2. package/dist/child-process-worker.js +39 -25
  3. package/dist/client/.vite/manifest.json +248 -248
  4. package/dist/client/assets/{AgentDetailView-C4Xpag_i.js → AgentDetailView-swTdiyeE.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-D-Fp7-sy.js → AgentPermissionPolicyEditor-Dq-KuDaA.js} +1 -1
  6. package/dist/client/assets/{AgentsView-BmFsoY1Q.js → AgentsView-D1GEHyH0.js} +3 -3
  7. package/dist/client/assets/{ChatView-5KsB3BVO.js → ChatView-D1czGvvT.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-CacGI8AV.js → CommandCenter-DWZ7OTOB.js} +1 -1
  9. package/dist/client/assets/{DevServerView-DufjauVn.js → DevServerView-CAcUKOXd.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-DvwqfQDS.js → DirectoryPicker-DVUl5jOk.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-BbYJFIQ1.js → DocumentsView-CnexOxtr.js} +1 -1
  12. package/dist/client/assets/{EvalsView-NBC8kOc-.js → EvalsView-rTupzcDf.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-C809yXXY.js → ExperimentalAgentOnboardingModal-CXE6Bj3V.js} +1 -1
  14. package/dist/client/assets/{GoalsView-CpNO9eI0.js → GoalsView-CjHwnnIi.js} +1 -1
  15. package/dist/client/assets/{InsightsView-Cahy5FcR.js → InsightsView-C3XocYP-.js} +1 -1
  16. package/dist/client/assets/{MemoryView-BNJAQjqn.js → MemoryView-CsNHcx_d.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-DCHTyOmJ.js → PiExtensionsManager-BPTPoZBI.js} +1 -1
  18. package/dist/client/assets/{PluginManager-CBt-CqUz.js → PluginManager-CXadn8JE.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-Dsb9RwmX.js → PullRequestView-CC03CtZJ.js} +1 -1
  20. package/dist/client/assets/{ReportModal-8FjZOcbC.js → ReportModal-Ci0na1eI.js} +1 -1
  21. package/dist/client/assets/{ResearchView-DlSG7dOd.js → ResearchView-DGWyAo8p.js} +1 -1
  22. package/dist/client/assets/{SecretsView-BSajNE-f.js → SecretsView-Cv2IuqFO.js} +1 -1
  23. package/dist/client/assets/{SessionTerminal-M2fIpHrL.js → SessionTerminal-B22RlQeo.js} +1 -1
  24. package/dist/client/assets/{SettingsModal-z6gOf7dG.js → SettingsModal-BkgBQZ19.js} +1 -1
  25. package/dist/client/assets/{SettingsModal-DLcvlp44.js → SettingsModal-CPRpRK71.js} +3 -3
  26. package/dist/client/assets/{SettingsTextareaRow-DeC19053.js → SettingsTextareaRow-0LXwMUQR.js} +1 -1
  27. package/dist/client/assets/{SetupWizardModal-BOjMePFh.js → SetupWizardModal-CyW3AlRO.js} +2 -2
  28. package/dist/client/assets/{SkillsView-BSBEC-Xm.js → SkillsView-3oqB1ubq.js} +1 -1
  29. package/dist/client/assets/{WorkflowNodeEditor-DZ9-RBqo.js → WorkflowNodeEditor-CfUK_xs1.js} +1 -1
  30. package/dist/client/assets/{agent-import-generation-COItiXMT.js → agent-import-generation-C7-Cmo3M.js} +1 -1
  31. package/dist/client/assets/{architectureDiagram-3BPJPVTR-BwlCePoB.js → architectureDiagram-3BPJPVTR-BQ7ZN1r_.js} +1 -1
  32. package/dist/client/assets/{blockDiagram-GPEHLZMM-KKrLi9v4.js → blockDiagram-GPEHLZMM-Debw1Xka.js} +1 -1
  33. package/dist/client/assets/{c4Diagram-AAUBKEIU-CkIDt_8M.js → c4Diagram-AAUBKEIU-CiU212s3.js} +1 -1
  34. package/dist/client/assets/channel-D-O4mRXx.js +1 -0
  35. package/dist/client/assets/{chunk-2J33WTMH-D9sbdaqY.js → chunk-2J33WTMH-BUJQ86zb.js} +1 -1
  36. package/dist/client/assets/{chunk-4BX2VUAB-D3DtFoAY.js → chunk-4BX2VUAB-C6GdEm1z.js} +1 -1
  37. package/dist/client/assets/{chunk-55IACEB6-gxblxRIN.js → chunk-55IACEB6-CImvrxQL.js} +1 -1
  38. package/dist/client/assets/{chunk-727SXJPM-D0AEDoHN.js → chunk-727SXJPM-Dt-A3pkM.js} +1 -1
  39. package/dist/client/assets/{chunk-AQP2D5EJ-BweoM7qY.js → chunk-AQP2D5EJ-DDj4oXTx.js} +1 -1
  40. package/dist/client/assets/{chunk-FMBD7UC4-sjblhrjI.js → chunk-FMBD7UC4-BD4r1ukC.js} +1 -1
  41. package/dist/client/assets/{chunk-ND2GUHAM-CYuRENSs.js → chunk-ND2GUHAM-DFSteefi.js} +1 -1
  42. package/dist/client/assets/{chunk-QZHKN3VN-CFKJr4kG.js → chunk-QZHKN3VN-t2zF8tjB.js} +1 -1
  43. package/dist/client/assets/classDiagram-4FO5ZUOK-BhHdSetA.js +1 -0
  44. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BhHdSetA.js +1 -0
  45. package/dist/client/assets/{cose-bilkent-S5V4N54A-6Jb6SgHM.js → cose-bilkent-S5V4N54A-tkvDkXQA.js} +1 -1
  46. package/dist/client/assets/{dagre-BM42HDAG-FQJ87Ol3.js → dagre-BM42HDAG-DD2rW7wo.js} +1 -1
  47. package/dist/client/assets/{dashboard-view-DgLbkn5n.js → dashboard-view-B1vduc8x.js} +1 -1
  48. package/dist/client/assets/{dashboard-view-DVMZNVoc.js → dashboard-view-C7jJ1lA4.js} +1 -1
  49. package/dist/client/assets/{dashboard-view-DH1Zo0m4.js → dashboard-view-CciuDS5A.js} +1 -1
  50. package/dist/client/assets/{dashboard-view-B1Rq2-73.js → dashboard-view-DruCruAc.js} +1 -1
  51. package/dist/client/assets/{diagram-2AECGRRQ-CKYOu9to.js → diagram-2AECGRRQ-DASuxGNc.js} +1 -1
  52. package/dist/client/assets/{diagram-5GNKFQAL-DPwjFnde.js → diagram-5GNKFQAL-f9WtiIUk.js} +1 -1
  53. package/dist/client/assets/{diagram-KO2AKTUF-DzZS9Nk3.js → diagram-KO2AKTUF-QivICYCd.js} +1 -1
  54. package/dist/client/assets/{diagram-LMA3HP47-DW9e5YSP.js → diagram-LMA3HP47-CMwcDFHR.js} +1 -1
  55. package/dist/client/assets/{diagram-OG6HWLK6-C7V3Mj63.js → diagram-OG6HWLK6-C-vPMeHZ.js} +1 -1
  56. package/dist/client/assets/{erDiagram-TEJ5UH35-DSQzBA0e.js → erDiagram-TEJ5UH35-CxGtk9vj.js} +1 -1
  57. package/dist/client/assets/{flowDiagram-I6XJVG4X-BUuOq9tO.js → flowDiagram-I6XJVG4X-qBZRZoV1.js} +1 -1
  58. package/dist/client/assets/{folder-open-DXNu4ED4.js → folder-open-Dysi9Zzg.js} +1 -1
  59. package/dist/client/assets/{ganttDiagram-6RSMTGT7-Bc32G_Ef.js → ganttDiagram-6RSMTGT7-DAaXroiu.js} +1 -1
  60. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-Cvfa0W6a.js → gitGraphDiagram-PVQCEYII-DNUNQot_.js} +1 -1
  61. package/dist/client/assets/{index-C5YdAqNQ.js → index-D0QgE5F5.js} +299 -299
  62. package/dist/client/assets/{infoDiagram-5YYISTIA-TqRUTfvy.js → infoDiagram-5YYISTIA-BSJmWLwh.js} +1 -1
  63. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-D33iop1_.js → ishikawaDiagram-YF4QCWOH-AyVKvoDq.js} +1 -1
  64. package/dist/client/assets/{journeyDiagram-JHISSGLW-DH-TDR5Y.js → journeyDiagram-JHISSGLW-972txzzd.js} +1 -1
  65. package/dist/client/assets/{kanban-definition-UN3LZRKU-CAcebzyW.js → kanban-definition-UN3LZRKU-1xiNvIqK.js} +1 -1
  66. package/dist/client/assets/{mermaid.core-C3Nh1Sak.js → mermaid.core-oeDA8jOm.js} +4 -4
  67. package/dist/client/assets/{mindmap-definition-RKZ34NQL-CSIccWV8.js → mindmap-definition-RKZ34NQL-DcktmI8s.js} +1 -1
  68. package/dist/client/assets/{pieDiagram-4H26LBE5-hXJ3G3MR.js → pieDiagram-4H26LBE5-ekBwLt-O.js} +1 -1
  69. package/dist/client/assets/{puzzle-DI3elldh.js → puzzle-I6pr6ZrW.js} +1 -1
  70. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-B3wpvneC.js → quadrantDiagram-W4KKPZXB-D_wjTsvn.js} +1 -1
  71. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DPpYL2ca.js → requirementDiagram-4Y6WPE33-CKGHyiJ1.js} +1 -1
  72. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BS9QXSEF.js → sankeyDiagram-5OEKKPKP-CtiZFNk5.js} +1 -1
  73. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-C_fqU0MW.js → sequenceDiagram-3UESZ5HK-B6OMrfGk.js} +1 -1
  74. package/dist/client/assets/{shield-alert-CzZYSqmH.js → shield-alert-CVMmviMG.js} +1 -1
  75. package/dist/client/assets/{standing-instructions-template-CsXA4WMZ.js → standing-instructions-template-Cg1a3tjW.js} +1 -1
  76. package/dist/client/assets/{stateDiagram-AJRCARHV-DbSZrprS.js → stateDiagram-AJRCARHV-CyTY2M_7.js} +1 -1
  77. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-Cf_ckwmM.js → stateDiagram-v2-BHNVJYJU-NWPCCiqC.js} +1 -1
  78. package/dist/client/assets/{timeline-definition-PNZ67QCA-B1ohvKjs.js → timeline-definition-PNZ67QCA-DU4TVYpw.js} +1 -1
  79. package/dist/client/assets/{upload-D7nNe9wD.js → upload-VCzeCiFH.js} +1 -1
  80. package/dist/client/assets/{users-B79Dvre4.js → users-DB3knZtm.js} +1 -1
  81. package/dist/client/assets/{vennDiagram-CIIHVFJN-CxZYCQ72.js → vennDiagram-CIIHVFJN-DX0Ppll6.js} +1 -1
  82. package/dist/client/assets/{wardley-L42UT6IY-clJem8VU.js → wardley-L42UT6IY-CBoWxpDv.js} +1 -1
  83. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-zE8lyaWe.js → wardleyDiagram-YWT4CUSO-B_eVqdhC.js} +1 -1
  84. package/dist/client/assets/{xychartDiagram-2RQKCTM6-DsdjuSxQ.js → xychartDiagram-2RQKCTM6-DeZzEX0f.js} +1 -1
  85. package/dist/client/index.html +1 -1
  86. package/dist/client/version.json +1 -1
  87. package/dist/droid-cli/package.json +1 -1
  88. package/dist/extension.js +54 -25
  89. package/dist/pi-claude-cli/package.json +1 -1
  90. package/dist/plugin-sdk/index.d.ts +1 -1
  91. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  92. package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
  93. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  94. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  95. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  96. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  97. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  98. package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
  99. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  100. package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
  101. package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
  102. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  103. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  104. package/dist/plugins/fusion-plugin-quality/package.json +1 -1
  105. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  106. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  107. package/dist/plugins/fusion-plugin-todos/package.json +1 -1
  108. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  109. package/package.json +1 -1
  110. package/dist/client/assets/channel-CeYcnOtQ.js +0 -1
  111. package/dist/client/assets/classDiagram-4FO5ZUOK-CZwh2k1X.js +0 -1
  112. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CZwh2k1X.js +0 -1
  113. package/dist/plugins/fusion-plugin-hermes-runtime/.bundled.reload-3.js +0 -19167
package/dist/bin.js CHANGED
@@ -32882,14 +32882,17 @@ function deepStripNulChars(value) {
32882
32882
  return stripNulChars(value);
32883
32883
  }
32884
32884
  if (Array.isArray(value)) {
32885
- return value.map(deepStripNulChars);
32885
+ const sanitized = value.map(deepStripNulChars);
32886
+ return sanitized.some((entry, index3) => entry !== value[index3]) ? sanitized : value;
32886
32887
  }
32887
32888
  if (value !== null && typeof value === "object") {
32888
- return Object.fromEntries(
32889
- Object.entries(value).map(
32890
- ([key, entry]) => [stripNulChars(key), deepStripNulChars(entry)]
32891
- )
32892
- );
32889
+ const entries = Object.entries(value);
32890
+ const sanitizedEntries = entries.map(([key, entry]) => [
32891
+ stripNulChars(key),
32892
+ deepStripNulChars(entry)
32893
+ ]);
32894
+ const changed = sanitizedEntries.some(([key, entry], index3) => key !== entries[index3]?.[0] || entry !== entries[index3]?.[1]);
32895
+ return changed ? Object.fromEntries(sanitizedEntries) : value;
32893
32896
  }
32894
32897
  return value;
32895
32898
  }
@@ -124947,24 +124950,28 @@ function rowToRoomMessage(row) {
124947
124950
  };
124948
124951
  }
124949
124952
  async function createChatSession(handle, session) {
124953
+ const sanitized = {
124954
+ ...session,
124955
+ inFlightGeneration: sanitizeJsonbValue(session.inFlightGeneration)
124956
+ };
124950
124957
  await handle.insert(project_exports.chatSessions).values({
124951
- id: session.id,
124952
- agentId: session.agentId,
124953
- title: session.title,
124954
- status: session.status,
124958
+ id: sanitized.id,
124959
+ agentId: sanitized.agentId,
124960
+ title: sanitized.title,
124961
+ status: sanitized.status,
124955
124962
  // FNXC:MultiProjectIsolation 2026-07-15-23:40: write the caller's domain project to owner_project_id and never project_id — the trigger/GUC owns the partition.
124956
- ownerProjectId: session.projectId,
124957
- modelProvider: session.modelProvider,
124958
- modelId: session.modelId,
124959
- thinkingLevel: session.thinkingLevel ?? null,
124960
- createdAt: session.createdAt,
124961
- updatedAt: session.updatedAt,
124962
- pinnedAt: session.pinnedAt,
124963
- cliSessionFile: session.cliSessionFile,
124964
- inFlightGeneration: session.inFlightGeneration,
124965
- cliExecutorAdapterId: session.cliExecutorAdapterId
124963
+ ownerProjectId: sanitized.projectId,
124964
+ modelProvider: sanitized.modelProvider,
124965
+ modelId: sanitized.modelId,
124966
+ thinkingLevel: sanitized.thinkingLevel ?? null,
124967
+ createdAt: sanitized.createdAt,
124968
+ updatedAt: sanitized.updatedAt,
124969
+ pinnedAt: sanitized.pinnedAt,
124970
+ cliSessionFile: sanitized.cliSessionFile,
124971
+ inFlightGeneration: sanitized.inFlightGeneration,
124972
+ cliExecutorAdapterId: sanitized.cliExecutorAdapterId
124966
124973
  });
124967
- return session;
124974
+ return sanitized;
124968
124975
  }
124969
124976
  async function getChatSession(handle, id) {
124970
124977
  const rows = await handle.select().from(project_exports.chatSessions).where(eq(project_exports.chatSessions.id, id));
@@ -125272,7 +125279,8 @@ async function setCliExecutorAdapterId(handle, id, adapterId) {
125272
125279
  async function setInFlightGeneration(handle, id, inFlightGeneration) {
125273
125280
  const existing = await getChatSession(handle, id);
125274
125281
  if (!existing) return void 0;
125275
- await handle.update(project_exports.chatSessions).set({ inFlightGeneration }).where(eq(project_exports.chatSessions.id, id));
125282
+ const sanitizedInFlightGeneration = sanitizeJsonbValue(inFlightGeneration);
125283
+ await handle.update(project_exports.chatSessions).set({ inFlightGeneration: sanitizedInFlightGeneration }).where(eq(project_exports.chatSessions.id, id));
125276
125284
  return getChatSession(handle, id);
125277
125285
  }
125278
125286
  async function addChatMessageAttachment(handle, sessionId, messageId, attachment) {
@@ -125280,7 +125288,10 @@ async function addChatMessageAttachment(handle, sessionId, messageId, attachment
125280
125288
  if (!message || message.sessionId !== sessionId) {
125281
125289
  throw new Error(`Message ${messageId} not found in session ${sessionId}`);
125282
125290
  }
125283
- const updatedAttachments = [...message.attachments ?? [], attachment];
125291
+ const updatedAttachments = sanitizeJsonbValue([
125292
+ ...message.attachments ?? [],
125293
+ attachment
125294
+ ]);
125284
125295
  await handle.update(project_exports.chatMessages).set({ attachments: updatedAttachments }).where(eq(project_exports.chatMessages.id, messageId));
125285
125296
  const updated = await getChatMessage(handle, messageId);
125286
125297
  if (!updated) throw new Error(`Failed to update message ${messageId}`);
@@ -125343,7 +125354,7 @@ async function updateChatMessageMetadata(handle, messageId, metadata, options) {
125343
125354
  }
125344
125355
  const merge2 = options?.merge !== false;
125345
125356
  const nextMetadata = metadata === null ? merge2 ? existing.metadata : null : merge2 ? { ...existing.metadata ?? {}, ...metadata } : metadata;
125346
- await handle.update(project_exports.chatMessages).set({ metadata: nextMetadata ?? null }).where(eq(project_exports.chatMessages.id, messageId));
125357
+ await handle.update(project_exports.chatMessages).set({ metadata: sanitizeJsonbValue(nextMetadata) ?? null }).where(eq(project_exports.chatMessages.id, messageId));
125347
125358
  const updated = await getChatMessage(handle, messageId);
125348
125359
  if (!updated) {
125349
125360
  throw new Error(`Failed to update message ${messageId}`);
@@ -125412,7 +125423,10 @@ async function addChatRoomMessageAttachment(handle, roomId, messageId, attachmen
125412
125423
  if (!message || message.roomId !== roomId) {
125413
125424
  throw new Error(`Message ${messageId} not found in room ${roomId}`);
125414
125425
  }
125415
- const updatedAttachments = [...message.attachments ?? [], attachment];
125426
+ const updatedAttachments = sanitizeJsonbValue([
125427
+ ...message.attachments ?? [],
125428
+ attachment
125429
+ ]);
125416
125430
  await handle.update(project_exports.chatRoomMessages).set({ attachments: updatedAttachments }).where(eq(project_exports.chatRoomMessages.id, messageId));
125417
125431
  await handle.update(project_exports.chatRooms).set({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(project_exports.chatRooms.id, roomId));
125418
125432
  const updated = await getChatRoomMessage(handle, messageId);
@@ -134435,6 +134449,19 @@ async function seedPreReleasePlanReviewContinuation(store, task, ir2, options =
134435
134449
  const item = await store.replaceActiveTaskWorkflowContinuation(input);
134436
134450
  return { seeded: true, workItemId: item.id };
134437
134451
  }
134452
+ async function resumeApprovedPlanReviewHandoff(store, task, ir2) {
134453
+ const node = resolvePreReleasePlanReviewNode(ir2);
134454
+ if (!node || node.column !== task.column) return { resumed: false, reason: "not-plan-in-place" };
134455
+ if (isTaskBlockedOnApproval(task)) return { resumed: false, reason: "awaiting-approval" };
134456
+ if (task.paused === true || task.userPaused === true) return { resumed: false, reason: "paused" };
134457
+ if (task.status === "needs-replan") return { resumed: false, reason: "needs-replan" };
134458
+ const seeded = await seedPreReleasePlanReviewContinuation(store, task, ir2, { atomic: true });
134459
+ if (seeded.seeded) return { resumed: true, reason: "seeded", workItemId: seeded.workItemId };
134460
+ return {
134461
+ resumed: false,
134462
+ reason: seeded.reason ?? "not-plan-in-place"
134463
+ };
134464
+ }
134438
134465
  function evaluateStrandedHoldContinuation(input) {
134439
134466
  if (!input.columnFlags.hold) return { stranded: false, candidate: false, reason: "not-hold-column" };
134440
134467
  const review = resolvePreReleasePlanReviewNode(input.ir);
@@ -268177,6 +268204,7 @@ __export(src_exports2, {
268177
268204
  resolveWorkspaceTaskRevertCommits: () => resolveWorkspaceTaskRevertCommits,
268178
268205
  resolveWorktrunkBinary: () => resolveWorktrunkBinary,
268179
268206
  restoreUnrelatedRootDirChanges: () => restoreUnrelatedRootDirChanges,
268207
+ resumeApprovedPlanReviewHandoff: () => resumeApprovedPlanReviewHandoff,
268180
268208
  revertWorkspaceTask: () => revertWorkspaceTask,
268181
268209
  reviewStep: () => reviewStep,
268182
268210
  runAiMerge: () => runAiMerge,
@@ -268381,6 +268409,7 @@ var init_src5 = __esm({
268381
268409
  init_remote_node_client();
268382
268410
  init_remote_node_runtime();
268383
268411
  init_hold_release();
268412
+ init_plan_review_continuation();
268384
268413
  init_step_session_executor();
268385
268414
  init_step_runner();
268386
268415
  init_pty_native();
@@ -292320,6 +292349,7 @@ var init_auth_middleware = __esm({
292320
292349
  });
292321
292350
 
292322
292351
  // ../dashboard/src/routes/register-task-workflow-routes.ts
292352
+ import { createHash as createHash29 } from "node:crypto";
292323
292353
  import { createReadStream } from "node:fs";
292324
292354
  import { readFile as readFile46, stat as stat11 } from "node:fs/promises";
292325
292355
  import { join as join89 } from "node:path";
@@ -292672,7 +292702,71 @@ function buildReviewerAgentItemId(input) {
292672
292702
  const timePart = (input.createdAt ?? "na").replace(/[:.]/g, "-");
292673
292703
  return `reviewer-${input.reviewType}-${stepPart}-${verdictPart}-${timePart}-${input.index + 1}`;
292674
292704
  }
292705
+ function parseTaskReviewVerdict(value) {
292706
+ switch (value) {
292707
+ case "APPROVE":
292708
+ case "APPROVE_WITH_NOTES":
292709
+ case "REVISE":
292710
+ case "RETHINK":
292711
+ case "UNAVAILABLE":
292712
+ return value;
292713
+ default:
292714
+ return void 0;
292715
+ }
292716
+ }
292717
+ function isCurrentWorkflowReviewResult(result2) {
292718
+ return CURRENT_WORKFLOW_REVIEW_STEP_IDS.has(result2.workflowStepId) && CURRENT_WORKFLOW_REVIEW_STATUSES.has(result2.status) && result2.verdict !== void 0 && result2.supersededAt === void 0 && result2.bypassedBy === void 0 && result2.bypassedAt === void 0 && result2.bypassReason === void 0 && result2.bypassedFromStatus === void 0 && result2.bypassedFromVerdict === void 0;
292719
+ }
292720
+ function buildWorkflowReviewItemId(task, result2) {
292721
+ const identity = JSON.stringify({
292722
+ taskId: task.id,
292723
+ workflowStepId: result2.workflowStepId,
292724
+ workflowStepName: result2.workflowStepName,
292725
+ phase: result2.phase,
292726
+ status: result2.status,
292727
+ verdict: result2.verdict,
292728
+ completedAt: result2.completedAt,
292729
+ startedAt: result2.startedAt,
292730
+ output: result2.output,
292731
+ notes: result2.notes
292732
+ });
292733
+ return `workflow-review-${createHash29("sha256").update(identity).digest("hex").slice(0, 24)}`;
292734
+ }
292735
+ function buildWorkflowReviewItems(task) {
292736
+ return (task.workflowStepResults ?? []).filter(isCurrentWorkflowReviewResult).map((result2) => {
292737
+ const reviewType = result2.workflowStepId === "plan-review" ? "plan" : "code";
292738
+ const body = result2.output?.trim() || result2.notes?.trim() || "No written feedback was provided by this review step.";
292739
+ const timestamp2 = result2.completedAt ?? result2.startedAt ?? task.updatedAt ?? task.createdAt;
292740
+ return {
292741
+ itemId: buildWorkflowReviewItemId(task, result2),
292742
+ sourceMode: "reviewer-agent",
292743
+ title: `${result2.workflowStepName || result2.workflowStepId} ${result2.verdict}`,
292744
+ body,
292745
+ author: "reviewer-agent",
292746
+ createdAt: timestamp2,
292747
+ updatedAt: timestamp2,
292748
+ reviewState: result2.verdict,
292749
+ verdict: result2.verdict,
292750
+ reviewType,
292751
+ progressStatus: null
292752
+ };
292753
+ }).sort((a, b3) => Date.parse(b3.createdAt ?? "") - Date.parse(a.createdAt ?? "") || a.itemId.localeCompare(b3.itemId));
292754
+ }
292755
+ function buildDirectReviewSummary(items) {
292756
+ const latest = items[0];
292757
+ return latest ? { summary: latest.title, verdict: latest.verdict } : null;
292758
+ }
292675
292759
  async function buildDirectTaskReviewData(task, store) {
292760
+ const structuredItems = buildWorkflowReviewItems(task);
292761
+ if (structuredItems.length > 0) {
292762
+ return {
292763
+ mode: "reviewer-agent",
292764
+ refreshable: true,
292765
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
292766
+ summary: buildDirectReviewSummary(structuredItems),
292767
+ items: structuredItems
292768
+ };
292769
+ }
292676
292770
  const agentLogs = await store.getAgentLogs(task.id);
292677
292771
  const reviewerText = agentLogs.filter((entry) => entry.agent === "reviewer" && entry.type === "text").map((entry) => entry.text).join("\n");
292678
292772
  const fallbackLogs = (task.log ?? []).filter((entry) => REVIEW_STEP_RE.test(entry.action));
@@ -292694,6 +292788,8 @@ async function buildDirectTaskReviewData(task, store) {
292694
292788
  createdAt,
292695
292789
  updatedAt: createdAt,
292696
292790
  reviewState: verdict ?? null,
292791
+ verdict: parseTaskReviewVerdict(verdict),
292792
+ reviewType,
292697
292793
  progressStatus: null
292698
292794
  });
292699
292795
  }
@@ -292711,21 +292807,18 @@ async function buildDirectTaskReviewData(task, store) {
292711
292807
  createdAt: entry.timestamp,
292712
292808
  updatedAt: entry.timestamp,
292713
292809
  reviewState: verdict ?? null,
292810
+ verdict: parseTaskReviewVerdict(verdict),
292811
+ reviewType,
292714
292812
  progressStatus: null
292715
292813
  });
292716
292814
  });
292717
292815
  }
292718
- const sorted = [...items].sort((a, b3) => Date.parse(b3.createdAt ?? "") - Date.parse(a.createdAt ?? ""));
292719
- const latest = sorted[0];
292720
- const summary = latest ? {
292721
- summary: latest.title,
292722
- verdict: latest.reviewState ?? void 0
292723
- } : null;
292816
+ const sorted = [...items].sort((a, b3) => Date.parse(b3.createdAt ?? "") - Date.parse(a.createdAt ?? "") || a.itemId.localeCompare(b3.itemId));
292724
292817
  return {
292725
292818
  mode: "reviewer-agent",
292726
292819
  refreshable: true,
292727
292820
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
292728
- summary,
292821
+ summary: buildDirectReviewSummary(sorted),
292729
292822
  items: sorted
292730
292823
  };
292731
292824
  }
@@ -294942,6 +295035,13 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
294942
295035
  await scopedStore.moveTask(task.id, reboundColumn);
294943
295036
  }
294944
295037
  const approved = await scopedStore.updateTask(task.id, approvalPatch);
295038
+ const approvedWorkflow = await resolveWorkflowIrForTask(scopedStore, approved.id);
295039
+ const handoff = await resumeApprovedPlanReviewHandoff(
295040
+ scopedStore,
295041
+ approved,
295042
+ approvedWorkflow
295043
+ );
295044
+ void handoff;
294945
295045
  await scopedStore.logEntry(task.id, "Plan approved by user").catch((error3) => {
294946
295046
  severityAuditLog33.warn(`Failed to record plan approval activity for ${task.id}: ${error3 instanceof Error ? error3.message : String(error3)}`);
294947
295047
  });
@@ -296335,7 +296435,7 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
296335
296435
  const selectedItems = Array.isArray(req.body?.selectedItems) ? req.body.selectedItems.filter((value) => {
296336
296436
  if (!value || typeof value !== "object") return false;
296337
296437
  const item = value;
296338
- return typeof item.id === "string" && item.id.trim().length > 0 && typeof item.summary === "string" && typeof item.body === "string";
296438
+ return typeof item.id === "string" && item.id.trim().length > 0 && typeof item.source === "string";
296339
296439
  }) : [];
296340
296440
  if (selectedItems.length === 0) {
296341
296441
  throw badRequest("selectedItems must be a non-empty array of review items");
@@ -296370,7 +296470,9 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
296370
296470
  htmlUrl: item.url,
296371
296471
  state: item.reviewState ?? void 0,
296372
296472
  isResolved: item.isResolved,
296373
- source: item.sourceMode === "reviewer-agent" ? "reviewer-agent" : "github-pr"
296473
+ source: item.sourceMode === "reviewer-agent" ? "reviewer-agent" : "github-pr",
296474
+ verdict: item.verdict,
296475
+ reviewType: item.reviewType
296374
296476
  }));
296375
296477
  const reviewState = {
296376
296478
  source: canonicalReviewData.mode,
@@ -296394,8 +296496,24 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
296394
296496
  if (hasUnknownSelection) {
296395
296497
  throw badRequest("selectedItems must reference existing review items");
296396
296498
  }
296397
- const modeSummary = `${reviewState.source === "pull-request" ? "pull-request" : "reviewer-agent"} \xB7 ${selectedItems.length} selected item(s)`;
296398
- const steeringItems = selectedItems.map((item, index3) => {
296499
+ const canonicalById = new Map(reviewState.items.map((item) => [item.id, item]));
296500
+ const canonicalSelections = selectedItems.map((selected) => {
296501
+ const item = canonicalById.get(selected.id);
296502
+ if (!item) throw badRequest("selectedItems must reference existing review items");
296503
+ return {
296504
+ id: item.id,
296505
+ source: expectedSource,
296506
+ summary: item.summary ?? item.body.slice(0, 120),
296507
+ body: item.body,
296508
+ author: item.author.login,
296509
+ filePath: item.path,
296510
+ lineNumber: item.line,
296511
+ threadId: item.threadId,
296512
+ url: item.htmlUrl
296513
+ };
296514
+ });
296515
+ const modeSummary = `${reviewState.source === "pull-request" ? "pull-request" : "reviewer-agent"} \xB7 ${canonicalSelections.length} selected item(s)`;
296516
+ const steeringItems = canonicalSelections.map((item, index3) => {
296399
296517
  const location = item.filePath ? `${item.filePath}${typeof item.lineNumber === "number" ? `:${item.lineNumber}` : ""}` : void 0;
296400
296518
  const snippetSource = item.body.trim() || item.summary.trim();
296401
296519
  const snippet = snippetSource.length > 220 ? `${snippetSource.slice(0, 220)}\u2026` : snippetSource;
@@ -296406,7 +296524,7 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
296406
296524
  const priorAddressingById = new Map(reviewState.addressing.map((record2) => [record2.itemId, record2]));
296407
296525
  const nextAddressing = [
296408
296526
  ...reviewState.addressing.filter((record2) => !selectedSet.has(record2.itemId)),
296409
- ...selectedItems.map((item) => {
296527
+ ...canonicalSelections.map((item) => {
296410
296528
  const existing = priorAddressingById.get(item.id);
296411
296529
  return {
296412
296530
  itemId: item.id,
@@ -296717,7 +296835,7 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
296717
296835
  }
296718
296836
  });
296719
296837
  }
296720
- var severityAuditLog33, AWAITING_PLANNING_ENRICH_LIMIT, LEGACY_WIP_LANES3, LEGACY_ARCHIVE_LANES5, REVIEW_BLOCK_RE, REVIEW_VERDICT_RE, REVIEW_STEP_RE, DUPLICATE_STOPWORDS, ARTIFACT_TYPES, ADDRESS_PR_FEEDBACK_PROMPT, __fingerprintCreateLocksForTests, RESET_TASK_FIELDS, RESET_DRIFT_CORRECTION_FIELDS;
296838
+ var severityAuditLog33, AWAITING_PLANNING_ENRICH_LIMIT, LEGACY_WIP_LANES3, LEGACY_ARCHIVE_LANES5, REVIEW_BLOCK_RE, REVIEW_VERDICT_RE, REVIEW_STEP_RE, DUPLICATE_STOPWORDS, ARTIFACT_TYPES, ADDRESS_PR_FEEDBACK_PROMPT, __fingerprintCreateLocksForTests, RESET_TASK_FIELDS, RESET_DRIFT_CORRECTION_FIELDS, CURRENT_WORKFLOW_REVIEW_STEP_IDS, CURRENT_WORKFLOW_REVIEW_STATUSES;
296721
296839
  var init_register_task_workflow_routes = __esm({
296722
296840
  "../dashboard/src/routes/register-task-workflow-routes.ts"() {
296723
296841
  "use strict";
@@ -296779,6 +296897,8 @@ var init_register_task_workflow_routes = __esm({
296779
296897
  worktreeSessionRetryCount: null,
296780
296898
  sessionFile: null
296781
296899
  };
296900
+ CURRENT_WORKFLOW_REVIEW_STEP_IDS = /* @__PURE__ */ new Set(["code-review", "plan-review"]);
296901
+ CURRENT_WORKFLOW_REVIEW_STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "advisory_failure"]);
296782
296902
  }
296783
296903
  });
296784
296904
 
@@ -300695,6 +300815,22 @@ When you need structured input, or whenever you present options, choices, or a d
300695
300815
  this.cliChatRunner = runner;
300696
300816
  this.cliChatProjectId = projectId;
300697
300817
  }
300818
+ /*
300819
+ FNXC:ChatPersistence 2026-08-05-01:54:
300820
+ Checkpoint snapshots contain environment-controlled tool output. Persistence
300821
+ is best-effort for crash recovery, but every fire-and-forget write must have
300822
+ its rejection observed so one failed jsonb write cannot become a process-wide
300823
+ unhandled rejection or interrupt the streaming turn.
300824
+ */
300825
+ persistInFlightGeneration(sessionId, snapshot) {
300826
+ try {
300827
+ void this.chatStore.setInFlightGeneration(sessionId, snapshot).catch(() => {
300828
+ diagnostics6.warn(`Failed to persist in-flight chat checkpoint for session ${sessionId}`);
300829
+ });
300830
+ } catch {
300831
+ diagnostics6.warn(`Failed to persist in-flight chat checkpoint for session ${sessionId}`);
300832
+ }
300833
+ }
300698
300834
  queueInFlightGenerationPersist(sessionId, snapshot) {
300699
300835
  const existingTimer = this.inFlightPersistTimers.get(sessionId);
300700
300836
  if (existingTimer) {
@@ -300702,7 +300838,7 @@ When you need structured input, or whenever you present options, choices, or a d
300702
300838
  }
300703
300839
  const timer2 = setTimeout(() => {
300704
300840
  this.inFlightPersistTimers.delete(sessionId);
300705
- this.chatStore.setInFlightGeneration(sessionId, snapshot);
300841
+ this.persistInFlightGeneration(sessionId, snapshot);
300706
300842
  }, IN_FLIGHT_PERSIST_DEBOUNCE_MS);
300707
300843
  this.inFlightPersistTimers.set(sessionId, timer2);
300708
300844
  }
@@ -300712,7 +300848,7 @@ When you need structured input, or whenever you present options, choices, or a d
300712
300848
  clearTimeout(existingTimer);
300713
300849
  this.inFlightPersistTimers.delete(sessionId);
300714
300850
  }
300715
- this.chatStore.setInFlightGeneration(sessionId, snapshot);
300851
+ this.persistInFlightGeneration(sessionId, snapshot);
300716
300852
  }
300717
300853
  async getChatModelSettings() {
300718
300854
  if (!this.getSettings) {
@@ -341606,8 +341742,8 @@ var init_register_settings_sync_routes = __esm({
341606
341742
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
341607
341743
  version: 1
341608
341744
  };
341609
- const { createHash: createHash31 } = await import("node:crypto");
341610
- const checksum = createHash31("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
341745
+ const { createHash: createHash32 } = await import("node:crypto");
341746
+ const checksum = createHash32("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
341611
341747
  const localPeerInfo = await central.getLocalPeerInfo();
341612
341748
  await fetchFromRemoteNode(node, "/api/settings/sync-receive", {
341613
341749
  method: "POST",
@@ -341678,7 +341814,7 @@ var init_register_settings_sync_routes = __esm({
341678
341814
  });
341679
341815
  return;
341680
341816
  }
341681
- const { createHash: createHash31 } = await import("node:crypto");
341817
+ const { createHash: createHash32 } = await import("node:crypto");
341682
341818
  const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
341683
341819
  const payloadWithoutChecksum = {
341684
341820
  global: remoteSettings.global,
@@ -341687,7 +341823,7 @@ var init_register_settings_sync_routes = __esm({
341687
341823
  exportedAt,
341688
341824
  version: 1
341689
341825
  };
341690
- const checksum = createHash31("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
341826
+ const checksum = createHash32("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
341691
341827
  const result2 = await central.applyRemoteSettings({
341692
341828
  ...payloadWithoutChecksum,
341693
341829
  checksum
@@ -368064,7 +368200,7 @@ var init_report_scrub = __esm({
368064
368200
  });
368065
368201
 
368066
368202
  // ../dashboard/src/report-pipeline.ts
368067
- import { createHash as createHash29 } from "node:crypto";
368203
+ import { createHash as createHash30 } from "node:crypto";
368068
368204
  function resolveReportMode(actionType, settings) {
368069
368205
  return settings.reportModeByAction?.[actionType] ?? settings.reportMode ?? "draft-review";
368070
368206
  }
@@ -368180,7 +368316,7 @@ async function embedScreenshot(args) {
368180
368316
  try {
368181
368317
  const extension = EXT_BY_MIME[attachment.mimeType];
368182
368318
  if (!extension) return args.report;
368183
- const safeId2 = createHash29("sha256").update(`${args.report.sessionToken ?? ""}:${attachment.artifactId}`).digest("hex").slice(0, 32);
368319
+ const safeId2 = createHash30("sha256").update(`${args.report.sessionToken ?? ""}:${attachment.artifactId}`).digest("hex").slice(0, 32);
368184
368320
  const path14 = `.fusion-reports/${safeId2}/screenshot.${extension}`;
368185
368321
  const uploaded = await args.client.uploadImageAsset({ owner: args.owner, repo: args.repo, path: path14, contentBase64: Buffer.from(attachment.bytes).toString("base64"), message: "chore: add Fusion report screenshot", mimeType: attachment.mimeType });
368186
368322
  const alt = markdownEscapeImageAlt(scrubReportText(attachment.filename, args.scrubContext) || "Report screenshot");
@@ -371283,7 +371419,7 @@ var init_types6 = __esm({
371283
371419
  });
371284
371420
 
371285
371421
  // ../dashboard/src/stt/model-manager.ts
371286
- import { createHash as createHash30, randomUUID as randomUUID64 } from "node:crypto";
371422
+ import { createHash as createHash31, randomUUID as randomUUID64 } from "node:crypto";
371287
371423
  import { mkdir as mkdir38, open as open4, readFile as readFile56, readdir as readdir14, rename as rename10, rm as rm21, stat as stat16, writeFile as writeFile37 } from "node:fs/promises";
371288
371424
  import { join as join118, relative as relative22, resolve as resolve68, sep as sep18 } from "node:path";
371289
371425
  function parseSafeTarListing(listing) {
@@ -371372,7 +371508,7 @@ function createVoiceModelManager(options = {}) {
371372
371508
  if (!response.ok || !response.body) throw Object.assign(new Error("download failed"), { voiceReason: "network" });
371373
371509
  if (!current(g2, signal)) return state;
371374
371510
  const total = Number(response.headers.get("content-length")) || void 0;
371375
- const hash = createHash30("sha256");
371511
+ const hash = createHash31("sha256");
371376
371512
  let bytes = 0;
371377
371513
  for await (const chunk of response.body) {
371378
371514
  if (!current(g2, signal)) return state;
@@ -32880,14 +32880,17 @@ function deepStripNulChars(value) {
32880
32880
  return stripNulChars(value);
32881
32881
  }
32882
32882
  if (Array.isArray(value)) {
32883
- return value.map(deepStripNulChars);
32883
+ const sanitized = value.map(deepStripNulChars);
32884
+ return sanitized.some((entry, index2) => entry !== value[index2]) ? sanitized : value;
32884
32885
  }
32885
32886
  if (value !== null && typeof value === "object") {
32886
- return Object.fromEntries(
32887
- Object.entries(value).map(
32888
- ([key, entry]) => [stripNulChars(key), deepStripNulChars(entry)]
32889
- )
32890
- );
32887
+ const entries = Object.entries(value);
32888
+ const sanitizedEntries = entries.map(([key, entry]) => [
32889
+ stripNulChars(key),
32890
+ deepStripNulChars(entry)
32891
+ ]);
32892
+ const changed = sanitizedEntries.some(([key, entry], index2) => key !== entries[index2]?.[0] || entry !== entries[index2]?.[1]);
32893
+ return changed ? Object.fromEntries(sanitizedEntries) : value;
32891
32894
  }
32892
32895
  return value;
32893
32896
  }
@@ -124945,24 +124948,28 @@ function rowToRoomMessage(row) {
124945
124948
  };
124946
124949
  }
124947
124950
  async function createChatSession(handle, session) {
124951
+ const sanitized = {
124952
+ ...session,
124953
+ inFlightGeneration: sanitizeJsonbValue(session.inFlightGeneration)
124954
+ };
124948
124955
  await handle.insert(project_exports.chatSessions).values({
124949
- id: session.id,
124950
- agentId: session.agentId,
124951
- title: session.title,
124952
- status: session.status,
124956
+ id: sanitized.id,
124957
+ agentId: sanitized.agentId,
124958
+ title: sanitized.title,
124959
+ status: sanitized.status,
124953
124960
  // FNXC:MultiProjectIsolation 2026-07-15-23:40: write the caller's domain project to owner_project_id and never project_id — the trigger/GUC owns the partition.
124954
- ownerProjectId: session.projectId,
124955
- modelProvider: session.modelProvider,
124956
- modelId: session.modelId,
124957
- thinkingLevel: session.thinkingLevel ?? null,
124958
- createdAt: session.createdAt,
124959
- updatedAt: session.updatedAt,
124960
- pinnedAt: session.pinnedAt,
124961
- cliSessionFile: session.cliSessionFile,
124962
- inFlightGeneration: session.inFlightGeneration,
124963
- cliExecutorAdapterId: session.cliExecutorAdapterId
124961
+ ownerProjectId: sanitized.projectId,
124962
+ modelProvider: sanitized.modelProvider,
124963
+ modelId: sanitized.modelId,
124964
+ thinkingLevel: sanitized.thinkingLevel ?? null,
124965
+ createdAt: sanitized.createdAt,
124966
+ updatedAt: sanitized.updatedAt,
124967
+ pinnedAt: sanitized.pinnedAt,
124968
+ cliSessionFile: sanitized.cliSessionFile,
124969
+ inFlightGeneration: sanitized.inFlightGeneration,
124970
+ cliExecutorAdapterId: sanitized.cliExecutorAdapterId
124964
124971
  });
124965
- return session;
124972
+ return sanitized;
124966
124973
  }
124967
124974
  async function getChatSession(handle, id) {
124968
124975
  const rows = await handle.select().from(project_exports.chatSessions).where(eq(project_exports.chatSessions.id, id));
@@ -125270,7 +125277,8 @@ async function setCliExecutorAdapterId(handle, id, adapterId) {
125270
125277
  async function setInFlightGeneration(handle, id, inFlightGeneration) {
125271
125278
  const existing = await getChatSession(handle, id);
125272
125279
  if (!existing) return void 0;
125273
- await handle.update(project_exports.chatSessions).set({ inFlightGeneration }).where(eq(project_exports.chatSessions.id, id));
125280
+ const sanitizedInFlightGeneration = sanitizeJsonbValue(inFlightGeneration);
125281
+ await handle.update(project_exports.chatSessions).set({ inFlightGeneration: sanitizedInFlightGeneration }).where(eq(project_exports.chatSessions.id, id));
125274
125282
  return getChatSession(handle, id);
125275
125283
  }
125276
125284
  async function addChatMessageAttachment(handle, sessionId, messageId, attachment) {
@@ -125278,7 +125286,10 @@ async function addChatMessageAttachment(handle, sessionId, messageId, attachment
125278
125286
  if (!message || message.sessionId !== sessionId) {
125279
125287
  throw new Error(`Message ${messageId} not found in session ${sessionId}`);
125280
125288
  }
125281
- const updatedAttachments = [...message.attachments ?? [], attachment];
125289
+ const updatedAttachments = sanitizeJsonbValue([
125290
+ ...message.attachments ?? [],
125291
+ attachment
125292
+ ]);
125282
125293
  await handle.update(project_exports.chatMessages).set({ attachments: updatedAttachments }).where(eq(project_exports.chatMessages.id, messageId));
125283
125294
  const updated = await getChatMessage(handle, messageId);
125284
125295
  if (!updated) throw new Error(`Failed to update message ${messageId}`);
@@ -125341,7 +125352,7 @@ async function updateChatMessageMetadata(handle, messageId, metadata, options) {
125341
125352
  }
125342
125353
  const merge2 = options?.merge !== false;
125343
125354
  const nextMetadata = metadata === null ? merge2 ? existing.metadata : null : merge2 ? { ...existing.metadata ?? {}, ...metadata } : metadata;
125344
- await handle.update(project_exports.chatMessages).set({ metadata: nextMetadata ?? null }).where(eq(project_exports.chatMessages.id, messageId));
125355
+ await handle.update(project_exports.chatMessages).set({ metadata: sanitizeJsonbValue(nextMetadata) ?? null }).where(eq(project_exports.chatMessages.id, messageId));
125345
125356
  const updated = await getChatMessage(handle, messageId);
125346
125357
  if (!updated) {
125347
125358
  throw new Error(`Failed to update message ${messageId}`);
@@ -125410,7 +125421,10 @@ async function addChatRoomMessageAttachment(handle, roomId, messageId, attachmen
125410
125421
  if (!message || message.roomId !== roomId) {
125411
125422
  throw new Error(`Message ${messageId} not found in room ${roomId}`);
125412
125423
  }
125413
- const updatedAttachments = [...message.attachments ?? [], attachment];
125424
+ const updatedAttachments = sanitizeJsonbValue([
125425
+ ...message.attachments ?? [],
125426
+ attachment
125427
+ ]);
125414
125428
  await handle.update(project_exports.chatRoomMessages).set({ attachments: updatedAttachments }).where(eq(project_exports.chatRoomMessages.id, messageId));
125415
125429
  await handle.update(project_exports.chatRooms).set({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(project_exports.chatRooms.id, roomId));
125416
125430
  const updated = await getChatRoomMessage(handle, messageId);