dominds 1.24.2 → 1.24.4

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 (117) hide show
  1. package/dist/apps/runtime.js +0 -3
  2. package/dist/dialog-display-state.js +4 -2
  3. package/dist/dialog-global-registry.d.ts +15 -1
  4. package/dist/dialog-global-registry.js +73 -5
  5. package/dist/dialog.d.ts +3 -3
  6. package/dist/dialog.js +4 -2
  7. package/dist/docs/tellask-background-continuation-refactor.zh.md +144 -15
  8. package/dist/llm/gen/mock.js +2 -0
  9. package/dist/llm/kernel-driver/drive.js +84 -16
  10. package/dist/llm/kernel-driver/flow.js +375 -100
  11. package/dist/llm/kernel-driver/loop.js +85 -46
  12. package/dist/llm/kernel-driver/reply-guidance.js +0 -3
  13. package/dist/llm/kernel-driver/tellask-special.d.ts +1 -0
  14. package/dist/llm/kernel-driver/tellask-special.js +3 -0
  15. package/dist/llm/kernel-driver/types.d.ts +10 -5
  16. package/dist/persistence.d.ts +30 -1
  17. package/dist/persistence.js +186 -4
  18. package/dist/runtime/driver-messages.d.ts +1 -0
  19. package/dist/runtime/driver-messages.js +7 -2
  20. package/dist/server/websocket-handler.d.ts +14 -0
  21. package/dist/server/websocket-handler.js +42 -3
  22. package/package.json +4 -4
  23. package/webapp/dist/assets/{_basePickBy-DBvh4H3k.js → _basePickBy-ZLV93S3E.js} +3 -3
  24. package/webapp/dist/assets/{_basePickBy-DBvh4H3k.js.map → _basePickBy-ZLV93S3E.js.map} +1 -1
  25. package/webapp/dist/assets/{_baseUniq-CQmc9B-o.js → _baseUniq-D0wSOJ06.js} +2 -2
  26. package/webapp/dist/assets/{_baseUniq-CQmc9B-o.js.map → _baseUniq-D0wSOJ06.js.map} +1 -1
  27. package/webapp/dist/assets/{arc-DfLiOX_4.js → arc-BHclbMTS.js} +2 -2
  28. package/webapp/dist/assets/{arc-DfLiOX_4.js.map → arc-BHclbMTS.js.map} +1 -1
  29. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-CaTVJ5ev.js → architectureDiagram-2XIMDMQ5-CK99gE_D.js} +7 -7
  30. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-CaTVJ5ev.js.map → architectureDiagram-2XIMDMQ5-CK99gE_D.js.map} +1 -1
  31. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-DlmAtUca.js → blockDiagram-WCTKOSBZ-fE5MBTEU.js} +7 -7
  32. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-DlmAtUca.js.map → blockDiagram-WCTKOSBZ-fE5MBTEU.js.map} +1 -1
  33. package/webapp/dist/assets/{c4Diagram-IC4MRINW-CNHKD5Sl.js → c4Diagram-IC4MRINW-BSLyPyoU.js} +3 -3
  34. package/webapp/dist/assets/{c4Diagram-IC4MRINW-CNHKD5Sl.js.map → c4Diagram-IC4MRINW-BSLyPyoU.js.map} +1 -1
  35. package/webapp/dist/assets/{channel-BTWhZYd5.js → channel-DSvMpp-a.js} +2 -2
  36. package/webapp/dist/assets/{channel-BTWhZYd5.js.map → channel-DSvMpp-a.js.map} +1 -1
  37. package/webapp/dist/assets/{chunk-4BX2VUAB-CV1n0Uhy.js → chunk-4BX2VUAB-OXEX170k.js} +2 -2
  38. package/webapp/dist/assets/{chunk-4BX2VUAB-CV1n0Uhy.js.map → chunk-4BX2VUAB-OXEX170k.js.map} +1 -1
  39. package/webapp/dist/assets/{chunk-55IACEB6-C5r77i_p.js → chunk-55IACEB6-BFQ_spQD.js} +2 -2
  40. package/webapp/dist/assets/{chunk-55IACEB6-C5r77i_p.js.map → chunk-55IACEB6-BFQ_spQD.js.map} +1 -1
  41. package/webapp/dist/assets/{chunk-FMBD7UC4-DJ1yHxrH.js → chunk-FMBD7UC4-CbQ2BBPs.js} +2 -2
  42. package/webapp/dist/assets/{chunk-FMBD7UC4-DJ1yHxrH.js.map → chunk-FMBD7UC4-CbQ2BBPs.js.map} +1 -1
  43. package/webapp/dist/assets/{chunk-JSJVCQXG-Ahg9hCCm.js → chunk-JSJVCQXG-C4P1mjCL.js} +2 -2
  44. package/webapp/dist/assets/{chunk-JSJVCQXG-Ahg9hCCm.js.map → chunk-JSJVCQXG-C4P1mjCL.js.map} +1 -1
  45. package/webapp/dist/assets/{chunk-KX2RTZJC-qBrewKt0.js → chunk-KX2RTZJC-BMd-daMY.js} +2 -2
  46. package/webapp/dist/assets/{chunk-KX2RTZJC-qBrewKt0.js.map → chunk-KX2RTZJC-BMd-daMY.js.map} +1 -1
  47. package/webapp/dist/assets/{chunk-NQ4KR5QH-1lEdM6Wi.js → chunk-NQ4KR5QH-B_ZhWMXR.js} +4 -4
  48. package/webapp/dist/assets/{chunk-NQ4KR5QH-1lEdM6Wi.js.map → chunk-NQ4KR5QH-B_ZhWMXR.js.map} +1 -1
  49. package/webapp/dist/assets/{chunk-QZHKN3VN-ChVR749G.js → chunk-QZHKN3VN-Cbf92xIw.js} +2 -2
  50. package/webapp/dist/assets/{chunk-QZHKN3VN-ChVR749G.js.map → chunk-QZHKN3VN-Cbf92xIw.js.map} +1 -1
  51. package/webapp/dist/assets/{chunk-WL4C6EOR-BAUXgk0K.js → chunk-WL4C6EOR-PtH-blkK.js} +6 -6
  52. package/webapp/dist/assets/{chunk-WL4C6EOR-BAUXgk0K.js.map → chunk-WL4C6EOR-PtH-blkK.js.map} +1 -1
  53. package/webapp/dist/assets/{classDiagram-VBA2DB6C-DlqyhKve.js → classDiagram-VBA2DB6C-Dc3ncaD0.js} +7 -7
  54. package/webapp/dist/assets/{classDiagram-VBA2DB6C-DlqyhKve.js.map → classDiagram-VBA2DB6C-Dc3ncaD0.js.map} +1 -1
  55. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-DlqyhKve.js → classDiagram-v2-RAHNMMFH-Dc3ncaD0.js} +7 -7
  56. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-DlqyhKve.js.map → classDiagram-v2-RAHNMMFH-Dc3ncaD0.js.map} +1 -1
  57. package/webapp/dist/assets/{clone-BFiIqUsc.js → clone-E9Ad85BC.js} +2 -2
  58. package/webapp/dist/assets/{clone-BFiIqUsc.js.map → clone-E9Ad85BC.js.map} +1 -1
  59. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-JYvhtd6J.js → cose-bilkent-S5V4N54A-B-nj0o74.js} +2 -2
  60. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-JYvhtd6J.js.map → cose-bilkent-S5V4N54A-B-nj0o74.js.map} +1 -1
  61. package/webapp/dist/assets/{dagre-KLK3FWXG-CCGcQh6w.js → dagre-KLK3FWXG-CyJYNIbm.js} +7 -7
  62. package/webapp/dist/assets/{dagre-KLK3FWXG-CCGcQh6w.js.map → dagre-KLK3FWXG-CyJYNIbm.js.map} +1 -1
  63. package/webapp/dist/assets/{diagram-E7M64L7V-BXC4AxAd.js → diagram-E7M64L7V-C8eweQ7b.js} +8 -8
  64. package/webapp/dist/assets/{diagram-E7M64L7V-BXC4AxAd.js.map → diagram-E7M64L7V-C8eweQ7b.js.map} +1 -1
  65. package/webapp/dist/assets/{diagram-IFDJBPK2-B--Sb3XT.js → diagram-IFDJBPK2-DMdygRl0.js} +7 -7
  66. package/webapp/dist/assets/{diagram-IFDJBPK2-B--Sb3XT.js.map → diagram-IFDJBPK2-DMdygRl0.js.map} +1 -1
  67. package/webapp/dist/assets/{diagram-P4PSJMXO-CVqgtrh3.js → diagram-P4PSJMXO-BQDZHb0a.js} +7 -7
  68. package/webapp/dist/assets/{diagram-P4PSJMXO-CVqgtrh3.js.map → diagram-P4PSJMXO-BQDZHb0a.js.map} +1 -1
  69. package/webapp/dist/assets/{erDiagram-INFDFZHY-BniHaRTt.js → erDiagram-INFDFZHY-C1HaXN6E.js} +5 -5
  70. package/webapp/dist/assets/{erDiagram-INFDFZHY-BniHaRTt.js.map → erDiagram-INFDFZHY-C1HaXN6E.js.map} +1 -1
  71. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-wLKFBWTR.js → flowDiagram-PKNHOUZH-24nNqQyo.js} +7 -7
  72. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-wLKFBWTR.js.map → flowDiagram-PKNHOUZH-24nNqQyo.js.map} +1 -1
  73. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-DrptcitZ.js → ganttDiagram-A5KZAMGK-BWPOFaLV.js} +3 -3
  74. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-DrptcitZ.js.map → ganttDiagram-A5KZAMGK-BWPOFaLV.js.map} +1 -1
  75. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-C6l5aP44.js → gitGraphDiagram-K3NZZRJ6-D7_L-p_Y.js} +8 -8
  76. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-C6l5aP44.js.map → gitGraphDiagram-K3NZZRJ6-D7_L-p_Y.js.map} +1 -1
  77. package/webapp/dist/assets/{graph-DXuQGYQN.js → graph-OHu4dL2n.js} +3 -3
  78. package/webapp/dist/assets/{graph-DXuQGYQN.js.map → graph-OHu4dL2n.js.map} +1 -1
  79. package/webapp/dist/assets/{index-DuQ1OCMG.js → index-CDCDAfqP.js} +165 -67
  80. package/webapp/dist/assets/{index-DuQ1OCMG.js.map → index-CDCDAfqP.js.map} +1 -1
  81. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-BbleCSjW.js → infoDiagram-LFFYTUFH-CvaBM5j6.js} +6 -6
  82. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-BbleCSjW.js.map → infoDiagram-LFFYTUFH-CvaBM5j6.js.map} +1 -1
  83. package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56-DmV-LZuk.js → ishikawaDiagram-PHBUUO56-DB1l2Uue.js} +2 -2
  84. package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56-DmV-LZuk.js.map → ishikawaDiagram-PHBUUO56-DB1l2Uue.js.map} +1 -1
  85. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-D3sQFfac.js → journeyDiagram-4ABVD52K-TQR6_teO.js} +5 -5
  86. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-D3sQFfac.js.map → journeyDiagram-4ABVD52K-TQR6_teO.js.map} +1 -1
  87. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-BEeBlBtM.js → kanban-definition-K7BYSVSG-B-BOuC-U.js} +3 -3
  88. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-BEeBlBtM.js.map → kanban-definition-K7BYSVSG-B-BOuC-U.js.map} +1 -1
  89. package/webapp/dist/assets/{layout-g7jjgV-W.js → layout-B8yqIqbx.js} +5 -5
  90. package/webapp/dist/assets/{layout-g7jjgV-W.js.map → layout-B8yqIqbx.js.map} +1 -1
  91. package/webapp/dist/assets/{linear-D_X91Yek.js → linear-CoLfiZKK.js} +2 -2
  92. package/webapp/dist/assets/{linear-D_X91Yek.js.map → linear-CoLfiZKK.js.map} +1 -1
  93. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-NkMAIgRY.js → mindmap-definition-YRQLILUH-P70BMIHI.js} +4 -4
  94. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-NkMAIgRY.js.map → mindmap-definition-YRQLILUH-P70BMIHI.js.map} +1 -1
  95. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-Z6E4GEPC.js → pieDiagram-SKSYHLDU-DsS_4dTB.js} +8 -8
  96. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-Z6E4GEPC.js.map → pieDiagram-SKSYHLDU-DsS_4dTB.js.map} +1 -1
  97. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-BH8hfOuU.js → quadrantDiagram-337W2JSQ-DoM9PEq-.js} +3 -3
  98. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-BH8hfOuU.js.map → quadrantDiagram-337W2JSQ-DoM9PEq-.js.map} +1 -1
  99. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-DRJkvoQI.js → requirementDiagram-Z7DCOOCP-Bn3lYMMI.js} +4 -4
  100. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-DRJkvoQI.js.map → requirementDiagram-Z7DCOOCP-Bn3lYMMI.js.map} +1 -1
  101. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-D2VwjtJo.js → sankeyDiagram-WA2Y5GQK-97kCegRT.js} +2 -2
  102. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-D2VwjtJo.js.map → sankeyDiagram-WA2Y5GQK-97kCegRT.js.map} +1 -1
  103. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-Cq-gEPOw.js → sequenceDiagram-2WXFIKYE-DXqjQjf6.js} +4 -4
  104. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-Cq-gEPOw.js.map → sequenceDiagram-2WXFIKYE-DXqjQjf6.js.map} +1 -1
  105. package/webapp/dist/assets/{stateDiagram-RAJIS63D-CFM8Jqke.js → stateDiagram-RAJIS63D-DQcTPKWP.js} +9 -9
  106. package/webapp/dist/assets/{stateDiagram-RAJIS63D-CFM8Jqke.js.map → stateDiagram-RAJIS63D-DQcTPKWP.js.map} +1 -1
  107. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-Dip5iGX_.js → stateDiagram-v2-FVOUBMTO-DHmxRVJn.js} +5 -5
  108. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-Dip5iGX_.js.map → stateDiagram-v2-FVOUBMTO-DHmxRVJn.js.map} +1 -1
  109. package/webapp/dist/assets/{timeline-definition-YZTLITO2-OI9JzMjX.js → timeline-definition-YZTLITO2-BlovQQ4B.js} +3 -3
  110. package/webapp/dist/assets/{timeline-definition-YZTLITO2-OI9JzMjX.js.map → timeline-definition-YZTLITO2-BlovQQ4B.js.map} +1 -1
  111. package/webapp/dist/assets/{treemap-KZPCXAKY-CtNF416A.js → treemap-KZPCXAKY-CGu93c9S.js} +5 -5
  112. package/webapp/dist/assets/{treemap-KZPCXAKY-CtNF416A.js.map → treemap-KZPCXAKY-CGu93c9S.js.map} +1 -1
  113. package/webapp/dist/assets/{vennDiagram-LZ73GAT5-CjaPj4FZ.js → vennDiagram-LZ73GAT5-Do1jprrz.js} +2 -2
  114. package/webapp/dist/assets/{vennDiagram-LZ73GAT5-CjaPj4FZ.js.map → vennDiagram-LZ73GAT5-Do1jprrz.js.map} +1 -1
  115. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-21mBt9iu.js → xychartDiagram-JWTSCODW-BKa1DxVq.js} +3 -3
  116. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-21mBt9iu.js.map → xychartDiagram-JWTSCODW-BKa1DxVq.js.map} +1 -1
  117. package/webapp/dist/index.html +1 -1
@@ -13,6 +13,18 @@ const log_1 = require("../../log");
13
13
  const persistence_1 = require("../../persistence");
14
14
  const persistence_errors_1 = require("../../persistence-errors");
15
15
  const engine_1 = require("./engine");
16
+ function formatDriveError(error) {
17
+ if (error instanceof Error) {
18
+ return {
19
+ name: error.name,
20
+ message: error.message,
21
+ ...(typeof error.stack === 'string' && error.stack.trim() !== ''
22
+ ? { stack: error.stack }
23
+ : {}),
24
+ };
25
+ }
26
+ return { message: String(error) };
27
+ }
16
28
  function formatDriveTriggerForLog(trigger) {
17
29
  return {
18
30
  action: trigger.action,
@@ -26,7 +38,7 @@ function formatDriveTriggerForLog(trigger) {
26
38
  };
27
39
  }
28
40
  async function listLiveDialogsWithDurableDriveWork() {
29
- const liveDialogs = dialog_global_registry_1.globalDialogRegistry.getAll();
41
+ const liveDialogs = dialog_global_registry_1.globalDialogRegistry.consumeQueuedMainDialogs();
30
42
  const queued = [];
31
43
  for (const mainDialog of liveDialogs) {
32
44
  let watchedDialogIds;
@@ -42,6 +54,7 @@ async function listLiveDialogsWithDurableDriveWork() {
42
54
  }
43
55
  const candidateDialogs = [mainDialog];
44
56
  let hadCandidateInspectionError = false;
57
+ let hadStalledCandidateForRoot = false;
45
58
  for (const watchedDialogId of watchedDialogIds) {
46
59
  let watchedDialog;
47
60
  try {
@@ -95,13 +108,24 @@ async function listLiveDialogsWithDurableDriveWork() {
95
108
  if (!(0, dialog_drive_work_1.hasDurableDriveWork)(latest)) {
96
109
  continue;
97
110
  }
111
+ const durableWorkFingerprint = persistence_1.DialogPersistence.buildBackendDriveDurableWorkFingerprint(latest, dialog.id.selfId === dialog.id.rootId ? watchedDialogIds : []);
112
+ if (latest.backendDriveStall?.durableWorkFingerprint === durableWorkFingerprint) {
113
+ hadStalledCandidateForRoot = true;
114
+ log_1.log.warn('Backend driver skipped stalled durable work pending new facts', undefined, {
115
+ dialogId: dialog.id.valueOf(),
116
+ rootId: dialog.id.rootId,
117
+ selfId: dialog.id.selfId,
118
+ stallRecordId: latest.backendDriveStall.recordId,
119
+ });
120
+ continue;
121
+ }
98
122
  hasQueuedCandidateForRoot = true;
99
123
  queued.push({
100
124
  rootDialog: mainDialog,
101
125
  dialog,
102
126
  });
103
127
  }
104
- if (!hasQueuedCandidateForRoot && !hadCandidateInspectionError) {
128
+ if (!hasQueuedCandidateForRoot && !hadCandidateInspectionError && !hadStalledCandidateForRoot) {
105
129
  dialog_global_registry_1.globalDialogRegistry.clearDriveWake(mainDialog.id.rootId, {
106
130
  source: 'kernel_driver_backend_loop',
107
131
  reason: 'no_durable_drive_work',
@@ -111,29 +135,6 @@ async function listLiveDialogsWithDurableDriveWork() {
111
135
  }
112
136
  return queued;
113
137
  }
114
- async function reassertLiveRootWakeForDurableWork() {
115
- for (const mainDialog of dialog_global_registry_1.globalDialogRegistry.getAll()) {
116
- try {
117
- const rootHasPendingNextStepTriggers = await persistence_1.DialogPersistence.hasPendingNextStepTriggers(mainDialog.id, mainDialog.status);
118
- const watchedDialogIds = await persistence_1.DialogPersistence.loadDriveWatchedDialogIds(mainDialog.id, mainDialog.status);
119
- if (!rootHasPendingNextStepTriggers && watchedDialogIds.length === 0) {
120
- continue;
121
- }
122
- dialog_global_registry_1.globalDialogRegistry.wakeDrive(mainDialog.id.rootId, {
123
- source: 'kernel_driver_backend_loop',
124
- reason: rootHasPendingNextStepTriggers
125
- ? 'root_next_step_still_pending'
126
- : 'drive_watch_still_pending',
127
- });
128
- }
129
- catch (error) {
130
- log_1.log.error('Backend driver failed to reassert root wake for durable work', error, {
131
- rootId: mainDialog.id.rootId,
132
- selfId: mainDialog.id.selfId,
133
- });
134
- }
135
- }
136
- }
137
138
  async function driveQueuedDialogsOnce() {
138
139
  const dialogsToDrive = await listLiveDialogsWithDurableDriveWork();
139
140
  for (const { rootDialog, dialog } of dialogsToDrive) {
@@ -143,7 +144,6 @@ async function driveQueuedDialogsOnce() {
143
144
  await persistence_1.DialogPersistence.removeDriveWatchForDialog(dialog.id, dialog.status);
144
145
  continue;
145
146
  }
146
- const currentHasPendingNextStepTriggers = (latestForDrive?.nextStep.triggers.length ?? 0) > 0;
147
147
  const currentResumeInProgressGeneration = (0, dialog_generation_run_1.getRecoverableGenerationRunState)(latestForDrive) !== undefined;
148
148
  const currentHasBackendDurableWork = (0, dialog_drive_work_1.hasDurableDriveWork)(latestForDrive);
149
149
  const executionMarker = latestForDrive?.executionMarker;
@@ -203,20 +203,27 @@ async function driveQueuedDialogsOnce() {
203
203
  const stillHasDurableWork = (0, dialog_drive_work_1.hasDurableDriveWork)(latestAfterDrive);
204
204
  const shouldStayQueued = dialog.hasUpNext() || !status.canDrive || stillHasDurableWork;
205
205
  if (shouldStayQueued) {
206
- dialog_global_registry_1.globalDialogRegistry.wakeDrive(rootDialog.id.rootId, {
207
- source: 'kernel_driver_backend_loop',
208
- reason: dialog.hasUpNext()
209
- ? 'post_drive_upnext_pending'
210
- : stillHasDurableWork
211
- ? 'post_drive_durable_work_pending'
212
- : 'post_drive_suspended',
213
- });
214
- if (dialog.id.selfId === dialog.id.rootId) {
215
- await persistence_1.DialogPersistence.upsertRootDriveWakeTrigger(dialog.id, dialog.hasUpNext()
216
- ? 'post_drive_upnext_pending'
217
- : stillHasDurableWork
218
- ? 'post_drive_durable_work_pending'
219
- : 'post_drive_suspended', dialog.status);
206
+ const canRetryImmediately = dialog.hasUpNext() || (status.canDrive && stillHasDurableWork);
207
+ if (canRetryImmediately) {
208
+ dialog_global_registry_1.globalDialogRegistry.wakeDrive(rootDialog.id.rootId, {
209
+ source: 'kernel_driver_backend_loop',
210
+ reason: dialog.hasUpNext()
211
+ ? 'post_drive_upnext_pending'
212
+ : 'post_drive_durable_work_pending',
213
+ });
214
+ if (dialog.id.selfId === dialog.id.rootId) {
215
+ await persistence_1.DialogPersistence.upsertRootDriveWakeTrigger(dialog.id, dialog.hasUpNext() ? 'post_drive_upnext_pending' : 'post_drive_durable_work_pending', dialog.status);
216
+ }
217
+ }
218
+ else {
219
+ log_1.log.debug('Backend driver left durable work parked until the blocking state changes', undefined, {
220
+ dialogId: dialog.id.valueOf(),
221
+ rootId: dialog.id.rootId,
222
+ selfId: dialog.id.selfId,
223
+ waitingQ4H: status.q4h,
224
+ backgroundCalleeDialogs: status.backgroundCalleeDialogs,
225
+ stillHasDurableWork,
226
+ });
220
227
  }
221
228
  }
222
229
  else {
@@ -289,14 +296,47 @@ async function driveQueuedDialogsOnce() {
289
296
  const rootHasPendingNextStepTriggers = await persistence_1.DialogPersistence.hasPendingNextStepTriggers(rootId, dialog.status);
290
297
  const watchedDialogIds = await persistence_1.DialogPersistence.loadDriveWatchedDialogIds(rootId, dialog.status);
291
298
  if (rootHasPendingNextStepTriggers || watchedDialogIds.length > 0) {
292
- dialog_global_registry_1.globalDialogRegistry.wakeDrive(dialog.id.rootId, {
293
- source: 'kernel_driver_backend_loop',
294
- reason: 'drive_error_durable_work_pending',
299
+ const durableWorkFingerprint = persistence_1.DialogPersistence.buildBackendDriveDurableWorkFingerprint(latestAfterError, dialog.id.selfId === dialog.id.rootId ? watchedDialogIds : []);
300
+ const record = await persistence_1.DialogPersistence.appendBackendDriveStallRecord(dialog.id, {
301
+ dialogId: dialog.id.valueOf(),
302
+ rootId: dialog.id.rootId,
303
+ selfId: dialog.id.selfId,
304
+ status: dialog.status,
305
+ reason: 'backend_drive_error',
306
+ durableWorkFingerprint,
307
+ latestSummary: latestAfterError === null
308
+ ? null
309
+ : {
310
+ currentCourse: latestAfterError.currentCourse,
311
+ status: latestAfterError.status,
312
+ generating: latestAfterError.generating ?? false,
313
+ displayState: latestAfterError.displayState ?? null,
314
+ executionMarker: latestAfterError.executionMarker ?? null,
315
+ generationRunState: latestAfterError.generationRunState ?? null,
316
+ nextStepTriggerCount: latestAfterError.nextStep.triggers.length,
317
+ pendingRuntimePromptMsgId: latestAfterError.pendingRuntimePrompt?.msgId ?? null,
318
+ replyDelivery: latestAfterError.replyDelivery ?? null,
319
+ userWait: latestAfterError.userWait ?? null,
320
+ sideDialogFinalResponse: latestAfterError.sideDialogFinalResponse ?? null,
321
+ },
322
+ error: formatDriveError(err),
323
+ context: {
324
+ rootHasPendingNextStepTriggers,
325
+ watchedDialogCount: watchedDialogIds.length,
326
+ },
327
+ }, dialog.status);
328
+ log_1.log.warn('Backend driver persisted stalled durable work after drive error', undefined, {
329
+ dialogId: dialog.id.valueOf(),
330
+ rootId: dialog.id.rootId,
331
+ selfId: dialog.id.selfId,
332
+ stallRecordId: record.recordId,
333
+ rootHasPendingNextStepTriggers,
334
+ watchedDialogCount: watchedDialogIds.length,
295
335
  });
296
336
  }
297
337
  }
298
- catch (requeueErr) {
299
- log_1.log.error('Failed to requeue durable work after backend drive error', requeueErr, {
338
+ catch (stallErr) {
339
+ log_1.log.error('Failed to persist backend drive stall after drive error', stallErr, {
300
340
  dialogId: dialog.id.valueOf(),
301
341
  rootId: dialog.id.rootId,
302
342
  selfId: dialog.id.selfId,
@@ -304,7 +344,6 @@ async function driveQueuedDialogsOnce() {
304
344
  }
305
345
  }
306
346
  }
307
- await reassertLiveRootWakeForDurableWork();
308
347
  }
309
348
  function isBackendDriverAborted(options) {
310
349
  return options?.abortSignal?.aborted === true;
@@ -125,9 +125,6 @@ async function resolveFreshCurrentSideDialogAssignmentDirective(args) {
125
125
  if (!latest) {
126
126
  return undefined;
127
127
  }
128
- if (latest.pendingRuntimePrompt?.msgId !== args.prompt.msgId) {
129
- return undefined;
130
- }
131
128
  if (latest.sideDialogFinalResponse?.callId === currentAssignmentDirective.targetCallId.trim()) {
132
129
  return undefined;
133
130
  }
@@ -122,6 +122,7 @@ export type TellaskFunctionRoundResult = Readonly<{
122
122
  hasImmediateTellaskOutputs: boolean;
123
123
  immediateTellaskOutputCallIds: readonly string[];
124
124
  shouldStopAfterReplyTool: boolean;
125
+ shouldStopAfterPendingTellaskWait: boolean;
125
126
  }>;
126
127
  export declare function processTellaskFunctionRound(args: {
127
128
  dlg: Dialog;
@@ -2390,6 +2390,7 @@ async function processTellaskFunctionRound(args) {
2390
2390
  const tellaskToolOutputs = [];
2391
2391
  const immediateTellaskOutputCallIds = [];
2392
2392
  let hasImmediateTellaskOutputs = false;
2393
+ let shouldStopAfterPendingTellaskWait = false;
2393
2394
  for (const output of tellaskExecution.toolOutputs) {
2394
2395
  if (output.type === 'func_result_msg') {
2395
2396
  const result = output;
@@ -2445,6 +2446,7 @@ async function processTellaskFunctionRound(args) {
2445
2446
  });
2446
2447
  tellaskFuncResultByCallId.set(call.callId, pendingResult);
2447
2448
  tellaskFuncResults.push(pendingResult);
2449
+ shouldStopAfterPendingTellaskWait = true;
2448
2450
  }
2449
2451
  for (const result of tellaskFuncResults) {
2450
2452
  await persistTellaskFuncResult(args.dlg, result);
@@ -2459,5 +2461,6 @@ async function processTellaskFunctionRound(args) {
2459
2461
  hasImmediateTellaskOutputs,
2460
2462
  immediateTellaskOutputCallIds,
2461
2463
  shouldStopAfterReplyTool: orderedInvalidCalls.length === 0 && tellaskExecution.successfulReplyCallIds.length > 0,
2464
+ shouldStopAfterPendingTellaskWait,
2462
2465
  };
2463
2466
  }
@@ -1,6 +1,6 @@
1
1
  import type { DialogDisplayState, DialogInterruptionReason } from '@longrun-ai/kernel/types/display-state';
2
2
  import type { DialogDiligencePrompt, DialogPrompt, DialogRunControlSpec, DialogRuntimeGuidePrompt, DialogRuntimePrompt, DialogRuntimeReplyPrompt, DialogRuntimeSideDialogPrompt, DialogUserPrompt } from '@longrun-ai/kernel/types/drive-intent';
3
- import type { CallSiteCourseNo, CallSiteGenseqNo } from '@longrun-ai/kernel/types/storage';
3
+ import type { CallSiteCourseNo, CallSiteGenseqNo, DialogBusinessContinuation } from '@longrun-ai/kernel/types/storage';
4
4
  import type { Dialog, DialogID } from '../../dialog';
5
5
  export type KernelDriverRunControl = DialogRunControlSpec;
6
6
  export type KernelDriverDriveSource = 'unspecified' | 'ws_user_message' | 'ws_user_answer' | 'ws_diligence_push' | 'ws_resume_dialog' | 'ws_resume_all' | 'kernel_driver_backend_loop' | 'kernel_driver_follow_up' | 'kernel_driver_sideDialog_init' | 'kernel_driver_sideDialog_resume' | 'kernel_driver_fbr_sideDialog_round' | 'kernel_driver_type_a_askerDialog_call' | 'kernel_driver_supply_response_caller_revive' | 'kernel_driver_idle_reminder_wake';
@@ -8,7 +8,14 @@ export type KernelDriverDriveOptions = Readonly<{
8
8
  suppressDiligencePush?: boolean;
9
9
  allowResumeFromInterrupted?: boolean;
10
10
  resumeInProgressGeneration?: boolean;
11
- criticalUserInterjectionRuntimeGuide?: string;
11
+ /**
12
+ * Business continuation identity for no-prompt driver iterations.
13
+ *
14
+ * This is deliberately part of the drive contract instead of being rediscovered from old
15
+ * transcript/assignment records. A continuation must tell the next iteration what business
16
+ * obligation it is continuing, or the driver treats it as no business continuation.
17
+ */
18
+ businessContinuation?: DialogBusinessContinuation;
12
19
  noPromptSideDialogResumeEntitlement?: Readonly<{
13
20
  callerDialogId: string;
14
21
  reason: 'reply_tellask_back_delivered';
@@ -21,9 +28,6 @@ export type KernelDriverDriveOptions = Readonly<{
21
28
  sideDialogId?: string;
22
29
  callType?: 'A' | 'B' | 'C';
23
30
  callId?: string;
24
- }> | Readonly<{
25
- callerDialogId: string;
26
- reason: 'reply_obligation_follow_up';
27
31
  }> | Readonly<{
28
32
  callerDialogId: string;
29
33
  reason: 'resolved_pending_sideDialog_reply';
@@ -115,6 +119,7 @@ export type KernelDriverCoreResult = {
115
119
  lastAssistantThinkingGenseq: number | null;
116
120
  lastFunctionCallGenseq: number | null;
117
121
  lastAssistantReplyTarget?: KernelDriverCalleeReplyTarget;
122
+ lastBusinessContinuation: DialogBusinessContinuation;
118
123
  fbrConclusion?: {
119
124
  responseText: string;
120
125
  responseGenseq: number;
@@ -87,7 +87,7 @@ export declare class DiskFileDialogStore extends DialogStore {
87
87
  * CRITICAL: This must be called BEFORE any substream events (thinking_start, markdown_start, etc.)
88
88
  * to ensure proper event ordering on the frontend.
89
89
  */
90
- notifyGeneratingStart(dialog: Dialog, msgId?: string): Promise<void>;
90
+ notifyGeneratingStart(dialog: Dialog, msgId?: string): Promise<readonly DialogNextStepTrigger[]>;
91
91
  private getResultArrivalBatchIdsFromAcceptedTriggers;
92
92
  /**
93
93
  * Notify end of LLM generation for frontend bubble management
@@ -259,6 +259,28 @@ type DialogLatestMutation = {
259
259
  kind: 'replace';
260
260
  next: DialogLatestFile;
261
261
  };
262
+ export type BackendDriveStallWrite = Readonly<{
263
+ dialogId: string;
264
+ rootId: string;
265
+ selfId: string;
266
+ status: DialogStatusKind;
267
+ reason: 'backend_drive_error';
268
+ durableWorkFingerprint: string;
269
+ latestSummary: Record<string, unknown> | null;
270
+ error: {
271
+ name?: string;
272
+ message: string;
273
+ stack?: string;
274
+ };
275
+ context: {
276
+ rootHasPendingNextStepTriggers: boolean;
277
+ watchedDialogCount: number;
278
+ };
279
+ }>;
280
+ export type BackendDriveStallRecord = BackendDriveStallWrite & Readonly<{
281
+ recordId: string;
282
+ recordedAt: string;
283
+ }>;
262
284
  type MainDialogWriteBackCancellationToken = Readonly<{
263
285
  scopeKey: string;
264
286
  generation: number;
@@ -285,6 +307,7 @@ export declare class DialogPersistence {
285
307
  private static readonly q4hWriteBack;
286
308
  private static readonly activeCalleesMutexes;
287
309
  private static readonly driveWatchMutexes;
310
+ private static readonly backendDriveStallMutexes;
288
311
  private static readonly courseAppendMutexes;
289
312
  private static readonly mainDialogWriteBackCancelGenerations;
290
313
  private static getLatestWriteBackMutex;
@@ -293,8 +316,10 @@ export declare class DialogPersistence {
293
316
  private static getCourseAppendMutexKey;
294
317
  private static getActiveCalleesMutex;
295
318
  private static getDriveWatchMutex;
319
+ private static getBackendDriveStallMutex;
296
320
  private static getLatestWriteBackKey;
297
321
  private static getDriveWatchKey;
322
+ private static getBackendDriveStallKey;
298
323
  private static getQ4HWriteBackKey;
299
324
  private static getActiveCalleesKey;
300
325
  private static getMainDialogWriteBackCancelScopeKey;
@@ -477,7 +502,11 @@ export declare class DialogPersistence {
477
502
  private static normalizeDriveWatchFile;
478
503
  private static loadDriveWatchFromDisk;
479
504
  private static writeDriveWatchToDisk;
505
+ private static getBackendDriveStallJsonlPath;
506
+ static buildBackendDriveDurableWorkFingerprint(latest: DialogLatestFile | null, watchedDialogIds?: readonly DialogID[]): string;
507
+ static appendBackendDriveStallRecord(dialogId: DialogID, write: BackendDriveStallWrite, status?: DialogStatusKind): Promise<BackendDriveStallRecord>;
480
508
  private static mutateDriveWatch;
509
+ private static removeWithRetry;
481
510
  static loadDriveWatchedDialogIds(rootDialogId: DialogID, status?: DialogStatusKind): Promise<readonly DialogID[]>;
482
511
  private static setDialogDriveWatched;
483
512
  static syncDriveWatchForDialogLatest(dialogId: DialogID, latest: DialogLatestFile, status?: DialogStatusKind): Promise<void>;
@@ -98,6 +98,7 @@ function summarizeLatestProjectionState(latest) {
98
98
  displayState: latest.displayState ?? null,
99
99
  executionMarker: latest.executionMarker ?? null,
100
100
  generationRunState: latest.generationRunState ?? null,
101
+ backendDriveStall: latest.backendDriveStall ?? null,
101
102
  nextStepTriggerCount: latest.nextStep.triggers.length,
102
103
  userWait: latest.userWait ?? null,
103
104
  replyDelivery: latest.replyDelivery ?? null,
@@ -135,6 +136,7 @@ function summarizeLatestMutationPatch(patch) {
135
136
  displayState: patch.displayState ?? null,
136
137
  executionMarker: patch.executionMarker ?? null,
137
138
  generationRunState: patch.generationRunState ?? null,
139
+ backendDriveStall: patch.backendDriveStall ?? null,
138
140
  nextStepTriggerCount: patch.nextStep?.triggers.length ?? null,
139
141
  userWait: patch.userWait ?? null,
140
142
  replyDelivery: patch.replyDelivery ?? null,
@@ -412,6 +414,16 @@ function getErrorCode(error) {
412
414
  const maybeCode = error.code;
413
415
  return typeof maybeCode === 'string' ? maybeCode : undefined;
414
416
  }
417
+ const RETRYABLE_FILESYSTEM_ERROR_CODES = new Set(['ENOENT', 'EPERM', 'EACCES', 'EBUSY']);
418
+ const FILESYSTEM_RETRY_BASE_DELAY_MS = 20;
419
+ const FILESYSTEM_RETRY_MAX_DELAY_MS = 250;
420
+ function getFilesystemRetryDelayMs(attempt) {
421
+ const delayMs = FILESYSTEM_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
422
+ return Math.min(FILESYSTEM_RETRY_MAX_DELAY_MS, delayMs);
423
+ }
424
+ async function sleepForFilesystemRetry(attempt) {
425
+ await new Promise((resolve) => setTimeout(resolve, getFilesystemRetryDelayMs(attempt)));
426
+ }
415
427
  function isGenericUnexpectedEofLikeError(error) {
416
428
  const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
417
429
  return message.includes('unexpected eof') || message.includes('unexpected end');
@@ -1417,6 +1429,32 @@ function parseDialogGenerationRunState(value) {
1417
1429
  }
1418
1430
  return null;
1419
1431
  }
1432
+ function parseDialogBackendDriveStallState(value) {
1433
+ if (!isRecord(value))
1434
+ return null;
1435
+ if (value.kind !== 'backend_drive_error')
1436
+ return null;
1437
+ if (typeof value.recordId !== 'string' || value.recordId.trim() === '')
1438
+ return null;
1439
+ if (typeof value.durableWorkFingerprint !== 'string' ||
1440
+ value.durableWorkFingerprint.trim() === '') {
1441
+ return null;
1442
+ }
1443
+ if (typeof value.failedAt !== 'string' || value.failedAt.trim() === '')
1444
+ return null;
1445
+ if (value.errorName !== undefined && typeof value.errorName !== 'string')
1446
+ return null;
1447
+ if (typeof value.errorMessage !== 'string' || value.errorMessage.trim() === '')
1448
+ return null;
1449
+ return {
1450
+ kind: 'backend_drive_error',
1451
+ recordId: value.recordId,
1452
+ durableWorkFingerprint: value.durableWorkFingerprint,
1453
+ failedAt: value.failedAt,
1454
+ ...(value.errorName === undefined ? {} : { errorName: value.errorName }),
1455
+ errorMessage: value.errorMessage,
1456
+ };
1457
+ }
1420
1458
  function parseStringArrayField(value) {
1421
1459
  if (!Array.isArray(value))
1422
1460
  return null;
@@ -1461,6 +1499,35 @@ function parseDialogFollowupReason(value) {
1461
1499
  return null;
1462
1500
  }
1463
1501
  }
1502
+ function parseDialogBusinessContinuation(value) {
1503
+ if (!isRecord(value))
1504
+ return null;
1505
+ switch (value.kind) {
1506
+ case 'none':
1507
+ return { kind: 'none' };
1508
+ case 'inter_dialog_reply': {
1509
+ const tellaskReplyDirective = parseTellaskReplyDirective(value.tellaskReplyDirective);
1510
+ if (tellaskReplyDirective === null)
1511
+ return null;
1512
+ if (value.calleeDialogReplyTarget === undefined) {
1513
+ return {
1514
+ kind: 'inter_dialog_reply',
1515
+ tellaskReplyDirective,
1516
+ };
1517
+ }
1518
+ const calleeDialogReplyTarget = parseDialogCalleeReplyTarget(value.calleeDialogReplyTarget);
1519
+ if (calleeDialogReplyTarget === null)
1520
+ return null;
1521
+ return {
1522
+ kind: 'inter_dialog_reply',
1523
+ tellaskReplyDirective,
1524
+ calleeDialogReplyTarget,
1525
+ };
1526
+ }
1527
+ default:
1528
+ return null;
1529
+ }
1530
+ }
1464
1531
  function parseDialogNextStepTrigger(value) {
1465
1532
  if (!isRecord(value))
1466
1533
  return null;
@@ -1527,6 +1594,13 @@ function parseDialogNextStepTrigger(value) {
1527
1594
  return null;
1528
1595
  reasons.push(reason);
1529
1596
  }
1597
+ const continuation = (() => {
1598
+ if (value.continuation === undefined)
1599
+ return undefined;
1600
+ return parseDialogBusinessContinuation(value.continuation);
1601
+ })();
1602
+ if (continuation === null)
1603
+ return null;
1530
1604
  return {
1531
1605
  ...base,
1532
1606
  kind: 'followup',
@@ -1535,6 +1609,7 @@ function parseDialogNextStepTrigger(value) {
1535
1609
  genseq: (0, storage_1.toCallSiteGenseqNo)(genseq),
1536
1610
  },
1537
1611
  reasons,
1612
+ ...(continuation === undefined ? {} : { continuation }),
1538
1613
  };
1539
1614
  }
1540
1615
  case 'mainline_diligence': {
@@ -1906,6 +1981,12 @@ function parseDialogLatestFile(value) {
1906
1981
  : parseDialogGenerationRunState(generationRunStateRaw);
1907
1982
  if (generationRunState === null)
1908
1983
  return null;
1984
+ const backendDriveStallRaw = value.backendDriveStall;
1985
+ const backendDriveStall = backendDriveStallRaw === undefined
1986
+ ? undefined
1987
+ : parseDialogBackendDriveStallState(backendDriveStallRaw);
1988
+ if (backendDriveStall === null)
1989
+ return null;
1909
1990
  const nextStepRaw = value.nextStep;
1910
1991
  if (nextStepRaw === undefined)
1911
1992
  return null;
@@ -2062,6 +2143,7 @@ function parseDialogLatestFile(value) {
2062
2143
  displayState,
2063
2144
  executionMarker,
2064
2145
  generationRunState,
2146
+ backendDriveStall,
2065
2147
  nextStep,
2066
2148
  userWait,
2067
2149
  replyDelivery,
@@ -2675,6 +2757,7 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
2675
2757
  generating: true,
2676
2758
  displayState: { kind: 'proceeding' },
2677
2759
  executionMarker: undefined,
2760
+ backendDriveStall: undefined,
2678
2761
  nextStep,
2679
2762
  generationRunState: {
2680
2763
  kind: 'open',
@@ -2697,6 +2780,7 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
2697
2780
  catch (err) {
2698
2781
  log_1.log.warn('Failed to persist gen_start event', err);
2699
2782
  }
2783
+ return acceptedTriggers;
2700
2784
  }
2701
2785
  getResultArrivalBatchIdsFromAcceptedTriggers(triggerIds) {
2702
2786
  const batchIds = [];
@@ -4731,6 +4815,14 @@ class DialogPersistence {
4731
4815
  this.driveWatchMutexes.set(key, created);
4732
4816
  return created;
4733
4817
  }
4818
+ static getBackendDriveStallMutex(key) {
4819
+ const existing = this.backendDriveStallMutexes.get(key);
4820
+ if (existing)
4821
+ return existing;
4822
+ const created = new async_fifo_mutex_1.AsyncFifoMutex();
4823
+ this.backendDriveStallMutexes.set(key, created);
4824
+ return created;
4825
+ }
4734
4826
  static getLatestWriteBackKey(dialogId, status) {
4735
4827
  // Include dialogs root dir to avoid cross-test/process.cwd collisions.
4736
4828
  return `${this.getDialogsRootDir()}|${status}|${dialogId.valueOf()}`;
@@ -4738,6 +4830,9 @@ class DialogPersistence {
4738
4830
  static getDriveWatchKey(rootDialogId, status) {
4739
4831
  return `${this.getDialogsRootDir()}|${status}|${rootDialogId.rootId}|drive-watch`;
4740
4832
  }
4833
+ static getBackendDriveStallKey(dialogId, status) {
4834
+ return `${this.getDialogsRootDir()}|${status}|${dialogId.valueOf()}|backend-drive-stall`;
4835
+ }
4741
4836
  static getQ4HWriteBackKey(dialogId, status) {
4742
4837
  // Include dialogs root dir to avoid cross-test/process.cwd collisions.
4743
4838
  return `${this.getDialogsRootDir()}|${status}|${dialogId.valueOf()}|q4h`;
@@ -6696,7 +6791,7 @@ class DialogPersistence {
6696
6791
  const normalized = this.normalizeDriveWatchFile(file);
6697
6792
  const filePath = this.getDriveWatchFilePath(rootDialogId, status);
6698
6793
  if (normalized.dialogs.length === 0) {
6699
- await fs.promises.rm(filePath, { force: true });
6794
+ await this.removeWithRetry(filePath);
6700
6795
  return;
6701
6796
  }
6702
6797
  const dialogPath = this.getMainDialogPath(rootDialogId, status);
@@ -6706,6 +6801,73 @@ class DialogPersistence {
6706
6801
  await fs.promises.writeFile(tempFile, jsonContent, 'utf-8');
6707
6802
  await this.renameWithRetry(tempFile, filePath, 5);
6708
6803
  }
6804
+ static getBackendDriveStallJsonlPath(dialogId, status) {
6805
+ return path.join(this.getDialogEventsPath(dialogId, status), 'backend-drive-stalls.jsonl');
6806
+ }
6807
+ static buildBackendDriveDurableWorkFingerprint(latest, watchedDialogIds = []) {
6808
+ if (latest === null) {
6809
+ return JSON.stringify({ latest: null });
6810
+ }
6811
+ const nextStepTriggers = sortNextStepTriggersForConsumption(latest.nextStep.triggers).map((trigger) => ({
6812
+ triggerId: trigger.triggerId,
6813
+ kind: trigger.kind,
6814
+ seq: trigger.seq,
6815
+ createdAt: trigger.createdAt,
6816
+ payload: trigger,
6817
+ }));
6818
+ return JSON.stringify({
6819
+ currentCourse: latest.currentCourse,
6820
+ status: latest.status,
6821
+ generating: latest.generating ?? false,
6822
+ displayState: latest.displayState ?? null,
6823
+ executionMarker: latest.executionMarker ?? null,
6824
+ nextStepTriggers,
6825
+ pendingRuntimePromptMsgId: latest.pendingRuntimePrompt?.msgId ?? null,
6826
+ generationRunState: latest.generationRunState ?? null,
6827
+ replyDelivery: latest.replyDelivery ?? null,
6828
+ sideDialogFinalResponse: latest.sideDialogFinalResponse ?? null,
6829
+ latestAssignmentAnchor: latest.latestAssignmentAnchor ?? null,
6830
+ userWait: latest.userWait ?? null,
6831
+ watchedDialogIds: watchedDialogIds.map((dialogId) => dialogId.valueOf()).sort(),
6832
+ });
6833
+ }
6834
+ static async appendBackendDriveStallRecord(dialogId, write, status = 'running') {
6835
+ const recordedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
6836
+ const record = {
6837
+ ...write,
6838
+ recordId: (0, node_crypto_1.randomUUID)(),
6839
+ recordedAt,
6840
+ };
6841
+ const key = this.getBackendDriveStallKey(dialogId, status);
6842
+ const mutex = this.getBackendDriveStallMutex(key);
6843
+ const release = await mutex.acquire();
6844
+ try {
6845
+ const filePath = this.getBackendDriveStallJsonlPath(dialogId, status);
6846
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
6847
+ await fs.promises.appendFile(filePath, `${JSON.stringify(record)}\n`, 'utf-8');
6848
+ await this.mutateDialogLatest(dialogId, () => ({
6849
+ kind: 'patch',
6850
+ patch: {
6851
+ backendDriveStall: {
6852
+ kind: 'backend_drive_error',
6853
+ recordId: record.recordId,
6854
+ durableWorkFingerprint: write.durableWorkFingerprint,
6855
+ failedAt: recordedAt,
6856
+ ...(write.error.name === undefined ? {} : { errorName: write.error.name }),
6857
+ errorMessage: write.error.message,
6858
+ },
6859
+ },
6860
+ }), status);
6861
+ return record;
6862
+ }
6863
+ finally {
6864
+ release();
6865
+ const current = this.backendDriveStallMutexes.get(key);
6866
+ if (current === mutex && !mutex.isLocked()) {
6867
+ this.backendDriveStallMutexes.delete(key);
6868
+ }
6869
+ }
6870
+ }
6709
6871
  static async mutateDriveWatch(rootDialogId, mutator, status = 'running') {
6710
6872
  const key = this.getDriveWatchKey(rootDialogId, status);
6711
6873
  const mutex = this.getDriveWatchMutex(key);
@@ -6720,6 +6882,24 @@ class DialogPersistence {
6720
6882
  release();
6721
6883
  }
6722
6884
  }
6885
+ static async removeWithRetry(filePath, maxRetries = 5) {
6886
+ let lastError;
6887
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
6888
+ try {
6889
+ await fs.promises.rm(filePath, { force: true });
6890
+ return;
6891
+ }
6892
+ catch (error) {
6893
+ lastError = error instanceof Error ? error : new Error(String(error));
6894
+ const errorCode = getErrorCode(error);
6895
+ if (!RETRYABLE_FILESYSTEM_ERROR_CODES.has(errorCode ?? '') || attempt === maxRetries) {
6896
+ throw error;
6897
+ }
6898
+ await sleepForFilesystemRetry(attempt);
6899
+ }
6900
+ }
6901
+ throw lastError;
6902
+ }
6723
6903
  static async loadDriveWatchedDialogIds(rootDialogId, status = 'running') {
6724
6904
  try {
6725
6905
  const file = await this.loadDriveWatchFromDisk(rootDialogId, status);
@@ -7753,11 +7933,12 @@ class DialogPersistence {
7753
7933
  throw error;
7754
7934
  }
7755
7935
  lastError = error instanceof Error ? error : new Error(String(error));
7756
- if (getErrorCode(error) !== 'ENOENT' || attempt === maxRetries) {
7936
+ const errorCode = getErrorCode(error);
7937
+ if (!RETRYABLE_FILESYSTEM_ERROR_CODES.has(errorCode ?? '') || attempt === maxRetries) {
7757
7938
  throw error;
7758
7939
  }
7759
- // Exponential backoff for ENOENT (race condition or sync issue)
7760
- await new Promise((resolve) => setTimeout(resolve, 20 * attempt));
7940
+ // Exponential backoff for transient filesystem contention.
7941
+ await sleepForFilesystemRetry(attempt);
7761
7942
  }
7762
7943
  }
7763
7944
  throw lastError;
@@ -8603,5 +8784,6 @@ DialogPersistence.q4hWriteBackMutexes = new Map();
8603
8784
  DialogPersistence.q4hWriteBack = new Map();
8604
8785
  DialogPersistence.activeCalleesMutexes = new Map();
8605
8786
  DialogPersistence.driveWatchMutexes = new Map();
8787
+ DialogPersistence.backendDriveStallMutexes = new Map();
8606
8788
  DialogPersistence.courseAppendMutexes = new Map();
8607
8789
  DialogPersistence.mainDialogWriteBackCancelGenerations = new Map();