dominds 1.23.5 → 1.23.7

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 (115) hide show
  1. package/dist/dialog-display-state.js +151 -35
  2. package/dist/dialog-interruption.js +2 -0
  3. package/dist/llm/api-quirks.d.ts +1 -0
  4. package/dist/llm/api-quirks.js +74 -1
  5. package/dist/llm/gen/openai-compatible.js +2 -3
  6. package/dist/llm/kernel-driver/drive.js +16 -0
  7. package/dist/llm/kernel-driver/flow.js +162 -33
  8. package/dist/llm/kernel-driver/runtime.js +3 -1
  9. package/dist/llm/kernel-driver/types.d.ts +1 -0
  10. package/dist/minds/system-prompt.js +4 -4
  11. package/dist/persistence.js +106 -14
  12. package/dist/runtime/inter-dialog-format.js +4 -4
  13. package/dist/runtime/reply-prompt-copy.js +4 -4
  14. package/dist/tools/prompts/control/en/principles.md +1 -1
  15. package/dist/tools/prompts/control/en/scenarios.md +1 -1
  16. package/dist/tools/prompts/control/en/tools.md +1 -1
  17. package/dist/tools/prompts/control/zh/principles.md +1 -1
  18. package/dist/tools/prompts/control/zh/scenarios.md +1 -1
  19. package/dist/tools/prompts/control/zh/tools.md +1 -1
  20. package/package.json +3 -3
  21. package/webapp/dist/assets/{_basePickBy-DMD1UhXs.js → _basePickBy-528dB5Tu.js} +3 -3
  22. package/webapp/dist/assets/{_basePickBy-DMD1UhXs.js.map → _basePickBy-528dB5Tu.js.map} +1 -1
  23. package/webapp/dist/assets/{_baseUniq-CsE8Qvwt.js → _baseUniq-DkdKmFUs.js} +2 -2
  24. package/webapp/dist/assets/{_baseUniq-CsE8Qvwt.js.map → _baseUniq-DkdKmFUs.js.map} +1 -1
  25. package/webapp/dist/assets/{arc-0h8sV6e1.js → arc-BXvXVeL_.js} +2 -2
  26. package/webapp/dist/assets/{arc-0h8sV6e1.js.map → arc-BXvXVeL_.js.map} +1 -1
  27. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-BbMESECO.js → architectureDiagram-2XIMDMQ5-Ck1IMDXl.js} +7 -7
  28. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-BbMESECO.js.map → architectureDiagram-2XIMDMQ5-Ck1IMDXl.js.map} +1 -1
  29. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-DwkN-9a4.js → blockDiagram-WCTKOSBZ-DLRhkTKE.js} +7 -7
  30. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-DwkN-9a4.js.map → blockDiagram-WCTKOSBZ-DLRhkTKE.js.map} +1 -1
  31. package/webapp/dist/assets/{c4Diagram-IC4MRINW-CGYONEh1.js → c4Diagram-IC4MRINW-D2Hc1l7q.js} +3 -3
  32. package/webapp/dist/assets/{c4Diagram-IC4MRINW-CGYONEh1.js.map → c4Diagram-IC4MRINW-D2Hc1l7q.js.map} +1 -1
  33. package/webapp/dist/assets/{channel-DbSJhm5-.js → channel-DuagLVFr.js} +2 -2
  34. package/webapp/dist/assets/{channel-DbSJhm5-.js.map → channel-DuagLVFr.js.map} +1 -1
  35. package/webapp/dist/assets/{chunk-4BX2VUAB-D1inRfgf.js → chunk-4BX2VUAB-BVowxdVQ.js} +2 -2
  36. package/webapp/dist/assets/{chunk-4BX2VUAB-D1inRfgf.js.map → chunk-4BX2VUAB-BVowxdVQ.js.map} +1 -1
  37. package/webapp/dist/assets/{chunk-55IACEB6-DL1IDg_h.js → chunk-55IACEB6-DOqixome.js} +2 -2
  38. package/webapp/dist/assets/{chunk-55IACEB6-DL1IDg_h.js.map → chunk-55IACEB6-DOqixome.js.map} +1 -1
  39. package/webapp/dist/assets/{chunk-FMBD7UC4-CugIlRDV.js → chunk-FMBD7UC4-BQE3IRbI.js} +2 -2
  40. package/webapp/dist/assets/{chunk-FMBD7UC4-CugIlRDV.js.map → chunk-FMBD7UC4-BQE3IRbI.js.map} +1 -1
  41. package/webapp/dist/assets/{chunk-JSJVCQXG-DKHSdeu1.js → chunk-JSJVCQXG-BWvy_u2h.js} +2 -2
  42. package/webapp/dist/assets/{chunk-JSJVCQXG-DKHSdeu1.js.map → chunk-JSJVCQXG-BWvy_u2h.js.map} +1 -1
  43. package/webapp/dist/assets/{chunk-KX2RTZJC-DCU9tkq6.js → chunk-KX2RTZJC-DsSmqNSf.js} +2 -2
  44. package/webapp/dist/assets/{chunk-KX2RTZJC-DCU9tkq6.js.map → chunk-KX2RTZJC-DsSmqNSf.js.map} +1 -1
  45. package/webapp/dist/assets/{chunk-NQ4KR5QH-DN3O2s2M.js → chunk-NQ4KR5QH-B3jQt0DX.js} +4 -4
  46. package/webapp/dist/assets/{chunk-NQ4KR5QH-DN3O2s2M.js.map → chunk-NQ4KR5QH-B3jQt0DX.js.map} +1 -1
  47. package/webapp/dist/assets/{chunk-QZHKN3VN-e3ztIJg0.js → chunk-QZHKN3VN-CWST9WcY.js} +2 -2
  48. package/webapp/dist/assets/{chunk-QZHKN3VN-e3ztIJg0.js.map → chunk-QZHKN3VN-CWST9WcY.js.map} +1 -1
  49. package/webapp/dist/assets/{chunk-WL4C6EOR-Dv907NPM.js → chunk-WL4C6EOR-DjGCVqJN.js} +6 -6
  50. package/webapp/dist/assets/{chunk-WL4C6EOR-Dv907NPM.js.map → chunk-WL4C6EOR-DjGCVqJN.js.map} +1 -1
  51. package/webapp/dist/assets/{classDiagram-VBA2DB6C-DOTXtxYZ.js → classDiagram-VBA2DB6C-BnjkPcus.js} +7 -7
  52. package/webapp/dist/assets/{classDiagram-VBA2DB6C-DOTXtxYZ.js.map → classDiagram-VBA2DB6C-BnjkPcus.js.map} +1 -1
  53. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-DOTXtxYZ.js → classDiagram-v2-RAHNMMFH-BnjkPcus.js} +7 -7
  54. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-DOTXtxYZ.js.map → classDiagram-v2-RAHNMMFH-BnjkPcus.js.map} +1 -1
  55. package/webapp/dist/assets/{clone-6lYQMWpu.js → clone-BlToIURl.js} +2 -2
  56. package/webapp/dist/assets/{clone-6lYQMWpu.js.map → clone-BlToIURl.js.map} +1 -1
  57. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-DoJeDXV0.js → cose-bilkent-S5V4N54A-BDVnPWt2.js} +2 -2
  58. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-DoJeDXV0.js.map → cose-bilkent-S5V4N54A-BDVnPWt2.js.map} +1 -1
  59. package/webapp/dist/assets/{dagre-KLK3FWXG-F_n_vhV9.js → dagre-KLK3FWXG-aEZUtpHt.js} +7 -7
  60. package/webapp/dist/assets/{dagre-KLK3FWXG-F_n_vhV9.js.map → dagre-KLK3FWXG-aEZUtpHt.js.map} +1 -1
  61. package/webapp/dist/assets/{diagram-E7M64L7V-Crwhgyjv.js → diagram-E7M64L7V-CvNVSxxk.js} +8 -8
  62. package/webapp/dist/assets/{diagram-E7M64L7V-Crwhgyjv.js.map → diagram-E7M64L7V-CvNVSxxk.js.map} +1 -1
  63. package/webapp/dist/assets/{diagram-IFDJBPK2-CIt1nnn5.js → diagram-IFDJBPK2-Cvwaoava.js} +7 -7
  64. package/webapp/dist/assets/{diagram-IFDJBPK2-CIt1nnn5.js.map → diagram-IFDJBPK2-Cvwaoava.js.map} +1 -1
  65. package/webapp/dist/assets/{diagram-P4PSJMXO-qowipEfV.js → diagram-P4PSJMXO-ffnT7Lr_.js} +7 -7
  66. package/webapp/dist/assets/{diagram-P4PSJMXO-qowipEfV.js.map → diagram-P4PSJMXO-ffnT7Lr_.js.map} +1 -1
  67. package/webapp/dist/assets/{erDiagram-INFDFZHY-DV2BcYNa.js → erDiagram-INFDFZHY-DvGIVeJS.js} +5 -5
  68. package/webapp/dist/assets/{erDiagram-INFDFZHY-DV2BcYNa.js.map → erDiagram-INFDFZHY-DvGIVeJS.js.map} +1 -1
  69. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-CAbWV161.js → flowDiagram-PKNHOUZH-BkQUpSc9.js} +7 -7
  70. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-CAbWV161.js.map → flowDiagram-PKNHOUZH-BkQUpSc9.js.map} +1 -1
  71. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-CfdR7FRr.js → ganttDiagram-A5KZAMGK-BlG96EZZ.js} +3 -3
  72. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-CfdR7FRr.js.map → ganttDiagram-A5KZAMGK-BlG96EZZ.js.map} +1 -1
  73. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-DuJFTELz.js → gitGraphDiagram-K3NZZRJ6-CnyjUBR4.js} +8 -8
  74. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-DuJFTELz.js.map → gitGraphDiagram-K3NZZRJ6-CnyjUBR4.js.map} +1 -1
  75. package/webapp/dist/assets/{graph-cjRyzujT.js → graph-D-OO7MVR.js} +3 -3
  76. package/webapp/dist/assets/{graph-cjRyzujT.js.map → graph-D-OO7MVR.js.map} +1 -1
  77. package/webapp/dist/assets/{index-DgfF56L4.js → index-DvqI98wY.js} +39 -33
  78. package/webapp/dist/assets/{index-DgfF56L4.js.map → index-DvqI98wY.js.map} +1 -1
  79. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-3wx-7AdD.js → infoDiagram-LFFYTUFH-Bid564Un.js} +6 -6
  80. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-3wx-7AdD.js.map → infoDiagram-LFFYTUFH-Bid564Un.js.map} +1 -1
  81. package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56-g6CMb1Qc.js → ishikawaDiagram-PHBUUO56-BoU1GXkx.js} +2 -2
  82. package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56-g6CMb1Qc.js.map → ishikawaDiagram-PHBUUO56-BoU1GXkx.js.map} +1 -1
  83. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-DdCcmOBO.js → journeyDiagram-4ABVD52K-C-JJRe4y.js} +5 -5
  84. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-DdCcmOBO.js.map → journeyDiagram-4ABVD52K-C-JJRe4y.js.map} +1 -1
  85. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-BFw2emGl.js → kanban-definition-K7BYSVSG-BPGHC2fL.js} +3 -3
  86. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-BFw2emGl.js.map → kanban-definition-K7BYSVSG-BPGHC2fL.js.map} +1 -1
  87. package/webapp/dist/assets/{layout-Clazq06r.js → layout-BFpoiNr0.js} +5 -5
  88. package/webapp/dist/assets/{layout-Clazq06r.js.map → layout-BFpoiNr0.js.map} +1 -1
  89. package/webapp/dist/assets/{linear-jdsBGgvD.js → linear-BwnDVwt9.js} +2 -2
  90. package/webapp/dist/assets/{linear-jdsBGgvD.js.map → linear-BwnDVwt9.js.map} +1 -1
  91. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-DLSZrW6l.js → mindmap-definition-YRQLILUH-D4aHh1Ye.js} +4 -4
  92. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-DLSZrW6l.js.map → mindmap-definition-YRQLILUH-D4aHh1Ye.js.map} +1 -1
  93. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-Uj-Zpci6.js → pieDiagram-SKSYHLDU-DIp7yy6V.js} +8 -8
  94. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-Uj-Zpci6.js.map → pieDiagram-SKSYHLDU-DIp7yy6V.js.map} +1 -1
  95. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-DO7Sl1nV.js → quadrantDiagram-337W2JSQ-uKOhvCPR.js} +3 -3
  96. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-DO7Sl1nV.js.map → quadrantDiagram-337W2JSQ-uKOhvCPR.js.map} +1 -1
  97. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-WrurrDKQ.js → requirementDiagram-Z7DCOOCP-Da_5DlcQ.js} +4 -4
  98. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-WrurrDKQ.js.map → requirementDiagram-Z7DCOOCP-Da_5DlcQ.js.map} +1 -1
  99. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-gcxbxuZB.js → sankeyDiagram-WA2Y5GQK-P3UD1XYS.js} +2 -2
  100. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-gcxbxuZB.js.map → sankeyDiagram-WA2Y5GQK-P3UD1XYS.js.map} +1 -1
  101. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-B98U2Npa.js → sequenceDiagram-2WXFIKYE-jY-eNlAg.js} +4 -4
  102. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-B98U2Npa.js.map → sequenceDiagram-2WXFIKYE-jY-eNlAg.js.map} +1 -1
  103. package/webapp/dist/assets/{stateDiagram-RAJIS63D-BUgfHMbd.js → stateDiagram-RAJIS63D-HMXNbLUd.js} +9 -9
  104. package/webapp/dist/assets/{stateDiagram-RAJIS63D-BUgfHMbd.js.map → stateDiagram-RAJIS63D-HMXNbLUd.js.map} +1 -1
  105. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-C8gH0rSW.js → stateDiagram-v2-FVOUBMTO-C-50Qbn8.js} +5 -5
  106. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-C8gH0rSW.js.map → stateDiagram-v2-FVOUBMTO-C-50Qbn8.js.map} +1 -1
  107. package/webapp/dist/assets/{timeline-definition-YZTLITO2-DnVikX3B.js → timeline-definition-YZTLITO2-CkiLYjSG.js} +3 -3
  108. package/webapp/dist/assets/{timeline-definition-YZTLITO2-DnVikX3B.js.map → timeline-definition-YZTLITO2-CkiLYjSG.js.map} +1 -1
  109. package/webapp/dist/assets/{treemap-KZPCXAKY-BjhjT1IM.js → treemap-KZPCXAKY-DKYYu8t-.js} +5 -5
  110. package/webapp/dist/assets/{treemap-KZPCXAKY-BjhjT1IM.js.map → treemap-KZPCXAKY-DKYYu8t-.js.map} +1 -1
  111. package/webapp/dist/assets/{vennDiagram-LZ73GAT5-CXjPMxrl.js → vennDiagram-LZ73GAT5-n9k6D3Up.js} +2 -2
  112. package/webapp/dist/assets/{vennDiagram-LZ73GAT5-CXjPMxrl.js.map → vennDiagram-LZ73GAT5-n9k6D3Up.js.map} +1 -1
  113. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-ByKmk3Cb.js → xychartDiagram-JWTSCODW-DJZb5SW1.js} +3 -3
  114. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-ByKmk3Cb.js.map → xychartDiagram-JWTSCODW-DJZb5SW1.js.map} +1 -1
  115. package/webapp/dist/index.html +1 -1
@@ -23,6 +23,26 @@ const idle_reminder_wake_1 = require("./idle-reminder-wake");
23
23
  const reply_guidance_1 = require("./reply-guidance");
24
24
  const sideDialog_1 = require("./sideDialog");
25
25
  const tellask_special_1 = require("./tellask-special");
26
+ function buildRuntimeReplyReminderFollowUp(args) {
27
+ const common = {
28
+ prompt: args.prompt,
29
+ msgId: (0, id_1.generateShortId)(),
30
+ grammar: 'markdown',
31
+ origin: 'runtime',
32
+ userLanguageCode: args.language,
33
+ tellaskReplyDirective: args.directive,
34
+ };
35
+ return args.sideDialogReplyTarget === undefined
36
+ ? {
37
+ kind: 'runtime_reply_reminder',
38
+ ...common,
39
+ }
40
+ : {
41
+ kind: 'runtime_sideDialog_reply_reminder',
42
+ ...common,
43
+ sideDialogReplyTarget: args.sideDialogReplyTarget,
44
+ };
45
+ }
26
46
  async function queueReplyReminderFollowUp(args) {
27
47
  if (args.followUp.kind === 'runtime_sideDialog_reply_reminder') {
28
48
  await args.dialog.queueRuntimeSideDialogPrompt({
@@ -74,6 +94,10 @@ function resolveDirectFallbackResponse(args) {
74
94
  source: 'saying',
75
95
  };
76
96
  }
97
+ // Thinking-only output is intentionally a fallback candidate: some providers/models can finish a
98
+ // Side Dialog with useful content in thinking and no public saying. This helper only extracts the
99
+ // candidate; callers below must still reject it when a same-round function/tellask call needs
100
+ // auto-continuation, when the dialog is suspended, or when another follow-up prompt is queued.
77
101
  if (args.driveResult.lastAssistantThinkingContent !== null &&
78
102
  args.driveResult.lastAssistantThinkingContent.trim() !== '') {
79
103
  if (typeof args.driveResult.lastAssistantThinkingGenseq !== 'number' ||
@@ -185,6 +209,79 @@ async function loadPendingDiagnosticsSnapshot(args) {
185
209
  };
186
210
  }
187
211
  }
212
+ async function hasAssistantOutputAfterAssignmentAnchor(args) {
213
+ const events = await persistence_1.DialogPersistence.loadCourseEvents(args.dialog.id, args.dialog.currentCourse, args.dialog.status);
214
+ let assignmentGenseq;
215
+ for (const event of events) {
216
+ if (event.type === 'tellask_anchor_record' &&
217
+ event.anchorRole === 'assignment' &&
218
+ event.callId === args.callId) {
219
+ assignmentGenseq = event.genseq;
220
+ continue;
221
+ }
222
+ if (assignmentGenseq !== undefined &&
223
+ (event.type === 'agent_thought_record' || event.type === 'agent_words_record') &&
224
+ event.genseq >= assignmentGenseq &&
225
+ event.content.trim() !== '') {
226
+ return true;
227
+ }
228
+ }
229
+ return false;
230
+ }
231
+ async function resolveStrandedSideDialogReplyReminderFollowUp(args) {
232
+ const latest = await persistence_1.DialogPersistence.loadDialogLatest(args.dialog.id, args.dialog.status);
233
+ const displayState = latest?.displayState;
234
+ const isRecoverableProjection = displayState?.kind === 'idle_waiting_user' ||
235
+ (displayState?.kind === 'stopped' && displayState.reason.kind === 'pending_reply_obligation');
236
+ if (!latest ||
237
+ !isRecoverableProjection ||
238
+ latest.pendingCourseStartPrompt !== undefined ||
239
+ latest.executionMarker?.kind === 'dead') {
240
+ return undefined;
241
+ }
242
+ const directive = await (0, tellask_special_1.loadActiveTellaskReplyDirective)(args.dialog);
243
+ if (!directive) {
244
+ return undefined;
245
+ }
246
+ const ownerDialogId = directive.targetDialogId.trim();
247
+ if (ownerDialogId === '') {
248
+ throw new Error(`stranded sideDialog reply recovery invariant violation: empty targetDialogId ` +
249
+ `(dialogId=${args.dialog.id.valueOf()}, targetCallId=${directive.targetCallId})`);
250
+ }
251
+ const pending = await persistence_1.DialogPersistence.loadPendingSideDialogs(new dialog_1.DialogID(ownerDialogId, args.dialog.id.rootId), args.dialog.status);
252
+ const pendingRecord = pending.find((record) => record.sideDialogId === args.dialog.id.selfId && record.callId === directive.targetCallId);
253
+ if (!pendingRecord) {
254
+ return undefined;
255
+ }
256
+ if (!(await hasAssistantOutputAfterAssignmentAnchor({
257
+ dialog: args.dialog,
258
+ callId: pendingRecord.callId,
259
+ }))) {
260
+ return undefined;
261
+ }
262
+ const language = (0, work_language_1.getWorkLanguage)();
263
+ const sideDialogReplyTarget = {
264
+ ownerDialogId,
265
+ callType: pendingRecord.callType,
266
+ callId: pendingRecord.callId,
267
+ callSiteCourse: pendingRecord.callSiteCourse,
268
+ callSiteGenseq: pendingRecord.callSiteGenseq,
269
+ };
270
+ return {
271
+ kind: 'runtime_sideDialog_reply_reminder',
272
+ prompt: await buildReplyToolReminderPrompt({
273
+ dlg: args.dialog,
274
+ directive,
275
+ language,
276
+ }),
277
+ msgId: (0, id_1.generateShortId)(),
278
+ grammar: 'markdown',
279
+ origin: 'runtime',
280
+ userLanguageCode: language,
281
+ tellaskReplyDirective: directive,
282
+ sideDialogReplyTarget,
283
+ };
284
+ }
188
285
  async function clearConsumedDeferredRootQueueIfIdle(dialog) {
189
286
  if (dialog.id.selfId !== dialog.id.rootId) {
190
287
  return;
@@ -366,6 +463,8 @@ async function inspectNoPromptSideDialogDrive(args) {
366
463
  const inProgressGenerationResumeAllowed = args.driveOptions?.resumeInProgressGeneration === true;
367
464
  const supplyResponseParentReviveAllowed = source === 'kernel_driver_supply_response_parent_revive' &&
368
465
  hasNoPromptSideDialogResumeEntitlement(args.dialog, args.driveOptions);
466
+ const pendingReplyObligationResumeAllowed = latest?.executionMarker?.kind === 'interrupted' &&
467
+ latest.executionMarker.reason.kind === 'pending_reply_obligation';
369
468
  if (lastEvent?.type === 'tellask_anchor_record' && lastEvent.anchorRole === 'response') {
370
469
  return {
371
470
  shouldReject: true,
@@ -378,7 +477,8 @@ async function inspectNoPromptSideDialogDrive(args) {
378
477
  }
379
478
  if (!explicitInterruptedResumeAllowed &&
380
479
  !inProgressGenerationResumeAllowed &&
381
- !supplyResponseParentReviveAllowed) {
480
+ !supplyResponseParentReviveAllowed &&
481
+ !pendingReplyObligationResumeAllowed) {
382
482
  return {
383
483
  shouldReject: true,
384
484
  source,
@@ -558,6 +658,7 @@ async function executeDriveRound(args) {
558
658
  let activeRunPrimed = false;
559
659
  let ownsActiveRun = false;
560
660
  let interruptedBySignal = false;
661
+ let shouldRefreshDisplayStateAfterActiveRunCleared = false;
561
662
  let followUp;
562
663
  let driveResult;
563
664
  let sideDialogReplyTarget;
@@ -640,6 +741,29 @@ async function executeDriveRound(args) {
640
741
  // suspended by pending Q4H or sideDialogs. This prevents duplicate generations when
641
742
  // multiple wake-ups race around the same sideDialog completion boundary.
642
743
  if (!humanPrompt) {
744
+ if (dialog instanceof dialog_1.SideDialog && !dialog.hasUpNext()) {
745
+ const strandedReplyReminder = await resolveStrandedSideDialogReplyReminderFollowUp({
746
+ dialog,
747
+ });
748
+ if (strandedReplyReminder !== undefined) {
749
+ await queueReplyReminderFollowUp({ dialog, followUp: strandedReplyReminder });
750
+ args.scheduleDrive(dialog, {
751
+ waitInQue: true,
752
+ driveOptions: {
753
+ source: 'kernel_driver_follow_up',
754
+ reason: 'follow_up_prompt',
755
+ },
756
+ });
757
+ log_1.log.warn('kernel-driver recovered stranded sideDialog reply obligation by queueing reply reminder', undefined, {
758
+ dialogId: dialog.id.valueOf(),
759
+ rootId: dialog.id.rootId,
760
+ selfId: dialog.id.selfId,
761
+ targetCallId: strandedReplyReminder.tellaskReplyDirective.targetCallId,
762
+ targetOwnerDialogId: strandedReplyReminder.sideDialogReplyTarget.ownerDialogId,
763
+ });
764
+ return;
765
+ }
766
+ }
643
767
  if (dialog instanceof dialog_1.SideDialog && !dialog.hasUpNext()) {
644
768
  try {
645
769
  const inspection = await inspectNoPromptSideDialogDrive({ dialog, driveOptions });
@@ -899,12 +1023,20 @@ async function executeDriveRound(args) {
899
1023
  (driveResult.fbrConclusion !== undefined ||
900
1024
  resolveDirectFallbackResponse({ driveResult, dialog }) !== undefined)) {
901
1025
  if (driveResult.fbrConclusion) {
902
- await (0, sideDialog_1.supplySideDialogResponseToAssignedAskerIfPendingV2)({
1026
+ const suppliedFbrConclusion = await (0, sideDialog_1.supplySideDialogResponseToAssignedAskerIfPendingV2)({
903
1027
  sideDialog: dialog,
904
1028
  responseText: driveResult.fbrConclusion.responseText,
905
1029
  responseGenseq: driveResult.fbrConclusion.responseGenseq,
1030
+ replyResolution: {
1031
+ callId: driveResult.fbrConclusion.replyResolutionCallId,
1032
+ replyCallName: 'replyTellaskSessionless',
1033
+ },
906
1034
  scheduleDrive: args.scheduleDrive,
907
1035
  });
1036
+ if (!suppliedFbrConclusion) {
1037
+ throw new Error(`FBR conclusion delivery invariant violation: no pending asker target for dialog=${dialog.id.valueOf()}`);
1038
+ }
1039
+ shouldRefreshDisplayStateAfterActiveRunCleared = true;
908
1040
  }
909
1041
  else {
910
1042
  const directFallbackResponse = resolveDirectFallbackResponse({ driveResult, dialog });
@@ -916,8 +1048,10 @@ async function executeDriveRound(args) {
916
1048
  driveResult.lastFunctionCallGenseq > 0 &&
917
1049
  directFallbackResponse.responseGenseq <= driveResult.lastFunctionCallGenseq;
918
1050
  if (hasInProgressFunctionCall) {
919
- // Any function call means execution is still in-progress. Only supply when the tellaskee
920
- // has produced a newer assistant saying after the latest function call.
1051
+ // A candidate direct fallback, including thinking-only output, must be newer than the
1052
+ // latest same-round function/tellask call. Otherwise the call is still the active move
1053
+ // and may auto-continue; the candidate is merely pre-tool reasoning/progress, not final
1054
+ // tellasker delivery.
921
1055
  log_1.log.debug('kernel-driver skip sideDialog response supply because latest assistant output is not after function calls', undefined, {
922
1056
  rootId: dialog.id.rootId,
923
1057
  selfId: dialog.id.selfId,
@@ -948,35 +1082,16 @@ async function executeDriveRound(args) {
948
1082
  else {
949
1083
  if (!activePromptWasReplyToolReminder) {
950
1084
  const language = (0, work_language_1.getWorkLanguage)();
951
- followUp =
952
- sideDialogReplyTarget === undefined
953
- ? {
954
- kind: 'runtime_reply_reminder',
955
- prompt: await buildReplyToolReminderPrompt({
956
- dlg: dialog,
957
- directive: activeTellaskReplyDirective,
958
- language,
959
- }),
960
- msgId: (0, id_1.generateShortId)(),
961
- grammar: 'markdown',
962
- origin: 'runtime',
963
- userLanguageCode: language,
964
- tellaskReplyDirective: activeTellaskReplyDirective,
965
- }
966
- : {
967
- kind: 'runtime_sideDialog_reply_reminder',
968
- prompt: await buildReplyToolReminderPrompt({
969
- dlg: dialog,
970
- directive: activeTellaskReplyDirective,
971
- language,
972
- }),
973
- msgId: (0, id_1.generateShortId)(),
974
- grammar: 'markdown',
975
- origin: 'runtime',
976
- userLanguageCode: language,
977
- tellaskReplyDirective: activeTellaskReplyDirective,
978
- sideDialogReplyTarget,
979
- };
1085
+ followUp = buildRuntimeReplyReminderFollowUp({
1086
+ directive: activeTellaskReplyDirective,
1087
+ prompt: await buildReplyToolReminderPrompt({
1088
+ dlg: dialog,
1089
+ directive: activeTellaskReplyDirective,
1090
+ language,
1091
+ }),
1092
+ language,
1093
+ sideDialogReplyTarget,
1094
+ });
980
1095
  log_1.log.debug('kernel-driver queued sideDialog replyTellask reminder after plain reply', undefined, {
981
1096
  dialogId: dialog.id.valueOf(),
982
1097
  targetCallId: activeTellaskReplyDirective.targetCallId,
@@ -1064,6 +1179,8 @@ async function executeDriveRound(args) {
1064
1179
  Number.isFinite(driveResult.lastFunctionCallGenseq) &&
1065
1180
  driveResult.lastFunctionCallGenseq > 0 &&
1066
1181
  directFallbackResponse.responseGenseq <= driveResult.lastFunctionCallGenseq;
1182
+ // Same rule as Side Dialog final delivery: direct fallback is allowed only after the
1183
+ // candidate content is known to be post-tool and no same-round call is waiting to continue.
1067
1184
  if (!hasInProgressFunctionCall) {
1068
1185
  if (!activePromptWasReplyToolReminder) {
1069
1186
  const language = (0, work_language_1.getWorkLanguage)();
@@ -1226,6 +1343,18 @@ async function executeDriveRound(args) {
1226
1343
  if (activeRunPrimed && ownsActiveRun) {
1227
1344
  (0, dialog_display_state_1.clearActiveRun)(dialog.id);
1228
1345
  }
1346
+ if (shouldRefreshDisplayStateAfterActiveRunCleared && !(0, dialog_display_state_1.hasActiveRun)(dialog.id)) {
1347
+ try {
1348
+ await (0, dialog_display_state_1.setDialogDisplayState)(dialog.id, await (0, dialog_display_state_1.computeIdleDisplayState)(dialog));
1349
+ }
1350
+ catch (error) {
1351
+ log_1.log.warn('kernel-driver failed to refresh display state after FBR auto-delivery', error, {
1352
+ dialogId: dialog.id.valueOf(),
1353
+ rootId: dialog.id.rootId,
1354
+ selfId: dialog.id.selfId,
1355
+ });
1356
+ }
1357
+ }
1229
1358
  release();
1230
1359
  (0, idle_reminder_wake_1.maybeStartIdleReminderWake)(dialog, {
1231
1360
  scheduleDrive: args.scheduleDrive,
@@ -867,6 +867,8 @@ async function runLlmRequestWithRetry(params) {
867
867
  errorText: detail,
868
868
  },
869
869
  });
870
+ }
871
+ if (failure.kind === 'rejected' && handledFailure.handling.kind !== 'give_up') {
870
872
  let streamErrorEmitted = false;
871
873
  try {
872
874
  await params.dlg.streamError(detail);
@@ -941,7 +943,7 @@ async function runLlmRequestWithRetry(params) {
941
943
  reason: interruptionReason,
942
944
  });
943
945
  }
944
- log_1.log.warn('LLM retriable failure stopped retry flow', undefined, {
946
+ log_1.log.warn('LLM failure stopped retry flow', undefined, {
945
947
  provider: params.provider,
946
948
  dialogId: params.dlg.id.valueOf(),
947
949
  rootId: params.dlg.id.rootId,
@@ -114,6 +114,7 @@ export type KernelDriverCoreResult = {
114
114
  fbrConclusion?: {
115
115
  responseText: string;
116
116
  responseGenseq: number;
117
+ replyResolutionCallId: string;
117
118
  };
118
119
  };
119
120
  export declare function createKernelDriverRuntimeState(): KernelDriverRuntimeState;
@@ -130,7 +130,7 @@ function buildSideDialogTellaskerReplyMarkerRules(language) {
130
130
  '- 当前支线未完成时,不要默认直接 `tellaskBack`。先判断当前团队规程/SOP/职责卡能否明确负责人:若能明确且属于执行性处理,直接 `tellask` / `tellaskSessionless` 对应负责人;只有当必须向诉请者补充需求、做业务裁决、澄清验收口径、提供缺失输入,或现有规程无法明确判责时,才发起 `tellaskBack({ tellaskContent: "..." })`,并在 `tellaskContent` 中给出具体问题。',
131
131
  '- 是否存在“待你收口的跨对话回复义务”、以及精确该调用哪个 reply 函数,均由运行时程序化判断;运行时会在 assignment 或最新 runtime/user 提示里直接点名。',
132
132
  '- 若运行时点名了精确 reply 函数名,你只需调用那个被点名的函数;不要自己判断 `reply*` 变体。禁止调用 `tellaskBack` 发送最终结果,也禁止用 `tellask` 向诉请者发送最终结果。',
133
- `- 只有在运行时当前明确点名了某个精确 reply 函数,且你通过那个函数回复时,运行时才会把该回复作为完成结果投递给诉请者,并在传递正文中添加 ${runtimeMarkers.finalCompleted}。`,
133
+ `- 正式完成路径:只有在运行时当前明确点名了某个精确 reply 函数,且你通过那个函数回复时,运行时才会把该回复作为完成结果投递给诉请者,并在传递正文中添加 ${runtimeMarkers.finalCompleted}。不要依赖 direct-reply fallback;它只是运行时临时过渡兜底,不是正式回复机制。`,
134
134
  '- 若运行时当前明确提示“没有待完成的跨对话回复义务”,就直接继续当前本地对话;不要凭记忆再次调用 `reply*`。',
135
135
  '- "不得发普通文本中间汇报"只针对未完成态;若你已经完成任务并能给出最终交付,就应使用运行时当前点名的精确 reply 函数,不要使用 `tellaskBack` 或 `tellask`。',
136
136
  '- 例外:FBR 支线为工具禁用模式(不得调用 `tellaskBack`);其回贴标记(`' +
@@ -145,7 +145,7 @@ function buildSideDialogTellaskerReplyMarkerRules(language) {
145
145
  '- If the current Side Dialog is unfinished, do not default to `tellaskBack`. First judge whether current team SOP / role ownership already identifies the responsible executor: if yes and the issue is execution work, directly use `tellask` / `tellaskSessionless` for that owner; use `tellaskBack({ tellaskContent: "..." })` only when the tellasker must provide clarification, business decision, acceptance-criteria confirmation, missing input, or when existing SOP cannot determine ownership. Put concrete questions in `tellaskContent`.',
146
146
  '- Runtime programmatically decides whether there is an active inter-dialog reply obligation for you, and which exact reply function name applies; runtime will state that directly in the assignment or the latest runtime/user prompt.',
147
147
  '- If runtime names an exact reply function, call that named function and do not choose a `reply*` variant by yourself. Do not use `tellaskBack` or `tellask` to send final delivery.',
148
- `- Only replies sent through the exact reply function currently named by runtime are delivered to the tellasker as completion results and marked with ${runtimeMarkers.finalCompleted}.`,
148
+ `- Formal completion path: only replies sent through the exact reply function currently named by runtime are delivered to the tellasker as completion results and marked with ${runtimeMarkers.finalCompleted}. Do not rely on direct-reply fallback; it is only a temporary runtime transition safeguard, not the formal reply mechanism.`,
149
149
  '- If runtime explicitly tells you there is no active inter-dialog reply obligation right now, just continue the current local conversation; do not call `reply*` again from memory.',
150
150
  '- "Do not post a plain-text progress update" only applies to unfinished states; if the task is done and you can deliver the final result, use the exact reply function currently named by runtime instead of `tellaskBack` or `tellask`.',
151
151
  '- Exception: FBR Side Dialog is tool-less (no \`tellaskBack\`); its reply markers (`' +
@@ -169,7 +169,7 @@ function buildTellaskReplyMarkerScopePolicy(language, dialogScope) {
169
169
  '- `tellaskBack` 只允许用于回问诉请者;仅当必须向诉请者补需求/澄清/裁决/缺失输入,或现有团队规程无法明确判责时才使用。禁止用 `tellaskBack` 发送最终结果。',
170
170
  '- 当前支线未完成时,不得把“阻塞/不确定”机械等同于 `tellaskBack`;若团队规程/SOP/职责卡已明确负责人,应直接 `tellask` / `tellaskSessionless` 对应负责人,不得发普通文本中间汇报。',
171
171
  `- ${(0, reply_prompt_copy_1.buildSideDialogCompletionRule)('zh')}`,
172
- `- 仅当运行时当前明确点名了某个精确 reply 函数,且你通过那个函数回复时,运行时才会把该回复投递给诉请者并标注 ${runtimeMarkers.finalCompleted}。`,
172
+ `- 正式完成路径中,仅当运行时当前明确点名了某个精确 reply 函数,且你通过那个函数回复时,运行时才会把该回复投递给诉请者并标注 ${runtimeMarkers.finalCompleted};不要依赖 direct-reply fallback,它只是运行时临时过渡兜底,不是正式回复机制。`,
173
173
  '- 若运行时当前明确提示“没有待完成的跨对话回复义务”,说明这轮不是待你收口的跨对话回复义务;不要重复调用 `reply*`。',
174
174
  ],
175
175
  en: [
@@ -178,7 +178,7 @@ function buildTellaskReplyMarkerScopePolicy(language, dialogScope) {
178
178
  '- `tellaskBack` is only for asking the tellasker back; use it only when tellasker clarification / decision / missing input is required, or current team SOP cannot determine ownership. Do not use `tellaskBack` to send final results.',
179
179
  '- If the current Side Dialog is unfinished, do not mechanically map “blocked / uncertain” to `tellaskBack`; when team SOP / role ownership already identifies the responsible owner, directly use `tellask` / `tellaskSessionless` for that owner instead of posting a plain-text progress update.',
180
180
  `- ${(0, reply_prompt_copy_1.buildSideDialogCompletionRule)('en')}`,
181
- `- Runtime marks ${runtimeMarkers.finalCompleted} and delivers to the tellasker only when runtime currently names an exact reply function and you reply through that named function.`,
181
+ `- In the formal completion path, runtime marks ${runtimeMarkers.finalCompleted} and delivers to the tellasker only when runtime currently names an exact reply function and you reply through that named function; do not rely on direct-reply fallback, which is only a temporary runtime transition safeguard, not the formal reply mechanism.`,
182
182
  '- If runtime currently tells you there is no active inter-dialog reply obligation, then this turn is not awaiting another inter-dialog closure from you; do not call `reply*` again.',
183
183
  ],
184
184
  }),
@@ -220,6 +220,67 @@ function normalizeGeneratingDisplayStateMismatch(dialogId, status, previous, lat
220
220
  executionMarker: hasInterruptedExecutionMarker ? undefined : latest.executionMarker,
221
221
  };
222
222
  }
223
+ function hasActiveReplyObligationInAskerStackState(state) {
224
+ const top = state?.askerStack[state.askerStack.length - 1];
225
+ return top?.tellaskReplyObligation !== undefined;
226
+ }
227
+ function blockerDisplayState(args) {
228
+ if (args.hasQ4H && args.hasSideDialogs) {
229
+ return { kind: 'blocked', reason: { kind: 'needs_human_input_and_sideDialogs' } };
230
+ }
231
+ if (args.hasQ4H) {
232
+ return { kind: 'blocked', reason: { kind: 'needs_human_input' } };
233
+ }
234
+ if (args.hasSideDialogs) {
235
+ return { kind: 'blocked', reason: { kind: 'waiting_for_sideDialogs' } };
236
+ }
237
+ return undefined;
238
+ }
239
+ async function normalizeSideDialogIdleWhileReplyObligationPending(dialogId, status, previous, latest, askerStackState, context) {
240
+ if (status !== 'running' || dialogId.selfId === dialogId.rootId) {
241
+ return latest;
242
+ }
243
+ if (latest.displayState?.kind !== 'idle_waiting_user') {
244
+ return latest;
245
+ }
246
+ if (!hasActiveReplyObligationInAskerStackState(askerStackState)) {
247
+ return latest;
248
+ }
249
+ const blockerState = blockerDisplayState({
250
+ hasQ4H: (await DialogPersistence.loadQuestions4HumanState(dialogId, status)).length > 0,
251
+ hasSideDialogs: (await DialogPersistence.loadPendingSideDialogs(dialogId, status)).length > 0,
252
+ });
253
+ const top = askerStackState?.askerStack[askerStackState.askerStack.length - 1];
254
+ const healedDisplayState = blockerState ?? pendingReplyObligationDisplayState();
255
+ const healedExecutionMarker = healedDisplayState.kind === 'stopped' ? pendingReplyObligationExecutionMarker() : undefined;
256
+ emitInvariantWarning('Dialog latest projection invariant warning: sideDialog with active reply obligation attempted to enter idle displayState; healing from persistence facts', {
257
+ trigger: context.trigger,
258
+ mutationKind: context.mutationKind,
259
+ latestSource: context.latestSource,
260
+ latestWriteBackKey: context.latestWriteBackKey,
261
+ patchSummary: context.patchSummary,
262
+ dialogId: dialogId.valueOf(),
263
+ rootId: dialogId.rootId,
264
+ selfId: dialogId.selfId,
265
+ status,
266
+ targetCallId: top?.tellaskReplyObligation?.targetCallId ?? null,
267
+ blockedByQ4H: blockerState?.kind === 'blocked' && blockerState.reason.kind !== 'waiting_for_sideDialogs',
268
+ blockedBySideDialogs: blockerState?.kind === 'blocked' && blockerState.reason.kind !== 'needs_human_input',
269
+ before: summarizeLatestProjectionState(previous),
270
+ afterBeforeHealing: summarizeLatestProjectionState(latest),
271
+ healedTo: {
272
+ displayState: healedDisplayState,
273
+ executionMarker: healedExecutionMarker,
274
+ },
275
+ callStack: captureInvariantWarningStack(),
276
+ });
277
+ return {
278
+ ...latest,
279
+ lastModified: (0, time_1.formatUnifiedTimestamp)(new Date()),
280
+ displayState: healedDisplayState,
281
+ executionMarker: healedDisplayState.kind === 'stopped' ? healedExecutionMarker : undefined,
282
+ };
283
+ }
223
284
  const quarantiningMainDialogs = new Set();
224
285
  const PERSISTABLE_DIALOG_STATUSES = ['running', 'completed', 'archived'];
225
286
  const RUN_STATUS_DIR = 'run';
@@ -459,6 +520,12 @@ function parseDialogLlmRetryRecoveryAction(value) {
459
520
  return { kind: 'none' };
460
521
  case 'diligence_push_once':
461
522
  return { kind: 'diligence_push_once' };
523
+ case 'runtime_prompt_once': {
524
+ const content = value.content;
525
+ if (typeof content !== 'string' || content.trim() === '')
526
+ return null;
527
+ return { kind: 'runtime_prompt_once', content };
528
+ }
462
529
  default:
463
530
  return null;
464
531
  }
@@ -475,6 +542,8 @@ function parseDialogInterruptionReason(value) {
475
542
  return { kind: 'server_restart' };
476
543
  case 'pending_course_start':
477
544
  return { kind: 'pending_course_start' };
545
+ case 'pending_reply_obligation':
546
+ return { kind: 'pending_reply_obligation' };
478
547
  case 'fork_continue_ready':
479
548
  return { kind: 'fork_continue_ready' };
480
549
  case 'system_stop': {
@@ -509,6 +578,19 @@ function parseDialogInterruptionReason(value) {
509
578
  return null;
510
579
  }
511
580
  }
581
+ function pendingReplyObligationDisplayState() {
582
+ return {
583
+ kind: 'stopped',
584
+ reason: { kind: 'pending_reply_obligation' },
585
+ continueEnabled: true,
586
+ };
587
+ }
588
+ function pendingReplyObligationExecutionMarker() {
589
+ return {
590
+ kind: 'interrupted',
591
+ reason: { kind: 'pending_reply_obligation' },
592
+ };
593
+ }
512
594
  function resolveStoppedContinueEnabled(reason) {
513
595
  return (0, dialog_interruption_1.isInterruptionReasonManualResumeEligible)(reason);
514
596
  }
@@ -1810,6 +1892,7 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
1810
1892
  },
1811
1893
  };
1812
1894
  await this.appendEvent(askerDialog, parentCourse, sideDialogCreatedRecord);
1895
+ const initialSideDialogDisplayState = pendingReplyObligationDisplayState();
1813
1896
  // Initialize latest.yaml via the mutation API (write-back will flush).
1814
1897
  await DialogPersistence.mutateDialogLatest(sideDialogId, () => ({
1815
1898
  kind: 'replace',
@@ -1820,7 +1903,8 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
1820
1903
  messageCount: 0,
1821
1904
  functionCallCount: 0,
1822
1905
  sideDialogCount: 0,
1823
- displayState: { kind: 'idle_waiting_user' },
1906
+ displayState: initialSideDialogDisplayState,
1907
+ executionMarker: pendingReplyObligationExecutionMarker(),
1824
1908
  disableDiligencePush: false,
1825
1909
  },
1826
1910
  }));
@@ -1857,7 +1941,7 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
1857
1941
  currentCourse: 1,
1858
1942
  createdAt: nowTs,
1859
1943
  lastModified: nowTs,
1860
- displayState: { kind: 'idle_waiting_user' },
1944
+ displayState: initialSideDialogDisplayState,
1861
1945
  sessionSlug: options.sessionSlug,
1862
1946
  assignmentFromAsker: {
1863
1947
  callName: options.callName,
@@ -6469,10 +6553,27 @@ class DialogPersistence {
6469
6553
  lastModified: (0, time_1.formatUnifiedTimestamp)(new Date()),
6470
6554
  status: 'active',
6471
6555
  };
6556
+ const askerStackState = status === 'running' && dialogId.selfId !== dialogId.rootId
6557
+ ? await this.loadSideDialogAskerStackState(dialogId, status)
6558
+ : null;
6472
6559
  const mutation = mutator(existing);
6560
+ const mutationContext = {
6561
+ trigger: 'mutateDialogLatest',
6562
+ mutationKind: mutation.kind,
6563
+ patchSummary: mutation.kind === 'patch'
6564
+ ? summarizeLatestMutationPatch(mutation.patch)
6565
+ : mutation.kind === 'replace'
6566
+ ? summarizeLatestProjectionState(mutation.next)
6567
+ : null,
6568
+ latestSource: staged ? 'staged' : latestFromDisk ? 'disk' : 'default_bootstrap',
6569
+ latestWriteBackKey: key,
6570
+ };
6473
6571
  let updated;
6474
6572
  if (mutation.kind === 'noop') {
6475
- return existing;
6573
+ updated = await normalizeSideDialogIdleWhileReplyObligationPending(dialogId, status, existing, existing, askerStackState, mutationContext);
6574
+ if (updated === existing) {
6575
+ return existing;
6576
+ }
6476
6577
  }
6477
6578
  else if (mutation.kind === 'replace') {
6478
6579
  updated = {
@@ -6491,17 +6592,8 @@ class DialogPersistence {
6491
6592
  const _exhaustive = mutation;
6492
6593
  throw new Error(`Unhandled dialog latest mutation: ${String(_exhaustive)}`);
6493
6594
  }
6494
- updated = normalizeGeneratingDisplayStateMismatch(dialogId, status, existing, updated, {
6495
- trigger: 'mutateDialogLatest',
6496
- mutationKind: mutation.kind,
6497
- patchSummary: mutation.kind === 'patch'
6498
- ? summarizeLatestMutationPatch(mutation.patch)
6499
- : mutation.kind === 'replace'
6500
- ? summarizeLatestProjectionState(mutation.next)
6501
- : null,
6502
- latestSource: staged ? 'staged' : latestFromDisk ? 'disk' : 'default_bootstrap',
6503
- latestWriteBackKey: key,
6504
- });
6595
+ updated = normalizeGeneratingDisplayStateMismatch(dialogId, status, existing, updated, mutationContext);
6596
+ updated = await normalizeSideDialogIdleWhileReplyObligationPending(dialogId, status, existing, updated, askerStackState, mutationContext);
6505
6597
  this.assertMainDialogWriteBackNotCanceled(effectiveCancellationToken, 'mutateDialogLatest:before-stage');
6506
6598
  const pending = this.latestWriteBack.get(key);
6507
6599
  if (!pending) {
@@ -263,11 +263,11 @@ function formatTellaskResponseContent(input) {
263
263
  const deliveryNotice = input.deliveryMode === 'direct_fallback'
264
264
  ? input.directFallbackSource === 'thinking_only'
265
265
  ? language === 'zh'
266
- ? '> 系统提示:本次回贴未调用 replyTellask* 工具,且模型仅产出 thinkingDominds 已将该 thinking 内容按 direct-reply fallback 投递,并保留此标记便于追踪。\n\n'
267
- : '> System note: this reply did not use a replyTellask* tool, and the model only produced thinking. Dominds delivered that thinking content via direct-reply fallback and kept this marker for traceability.\n\n'
266
+ ? '> 系统提示:本次回贴未调用 replyTellask* 工具,且模型仅产出 thinking;在确认本轮没有待续推工具调用后,Dominds 已通过 direct-reply fallback 临时过渡兜底投递该 thinking 内容。此路径不是正式回复机制,保留本标记仅用于追踪。\n\n'
267
+ : '> System note: this reply did not use a replyTellask* tool, and the model only produced thinking. After confirming that no tool call in this round was waiting for auto-continuation, Dominds delivered that thinking content through direct-reply fallback as a temporary transition safeguard. This path is not the formal reply mechanism; this marker is kept only for traceability.\n\n'
268
268
  : language === 'zh'
269
- ? '> 系统提示:本次回贴未调用 replyTellask* 工具,Dominds 已按 direct-reply fallback 投递,并保留此标记便于追踪。\n\n'
270
- : '> System note: this reply did not use a replyTellask* tool. Dominds delivered it via direct-reply fallback and kept this marker for traceability.\n\n'
269
+ ? '> 系统提示:本次回贴未调用 replyTellask* 工具;在确认本轮没有待续推工具调用后,Dominds 已通过 direct-reply fallback 临时过渡兜底投递。此路径不是正式回复机制,保留本标记仅用于追踪。\n\n'
270
+ : '> System note: this reply did not use a replyTellask* tool. After confirming that no tool call in this round was waiting for auto-continuation, Dominds delivered it through direct-reply fallback as a temporary transition safeguard. This path is not the formal reply mechanism; this marker is kept only for traceability.\n\n'
271
271
  : '';
272
272
  if (isFbr) {
273
273
  const title = language === 'zh' ? '【扪心自问(FBR)支线对话回贴】' : '[FBR Side Dialog response]';
@@ -149,18 +149,18 @@ function buildReplyToolReminderText(args) {
149
149
  ? [
150
150
  prefix,
151
151
  '',
152
- `你刚才已经写了正文,但还没调用 \`${args.directive.expectedReplyCallName}\`。`,
152
+ `你刚才已经产出了可作为回贴的内容,但还没调用 \`${args.directive.expectedReplyCallName}\`。`,
153
153
  '',
154
154
  buildReplyToolReminderLine(args),
155
- '如果你再次直接输出最终消息而仍不调用该工具,运行时会按 direct-reply fallback 投递,并在 UI/传递正文中明确标注。',
155
+ '不要依赖 direct-reply fallback;它只是运行时临时过渡兜底,不是正式回复机制。请现在调用正确的 reply 工具完成回复。',
156
156
  ].join('\n')
157
157
  : [
158
158
  prefix,
159
159
  '',
160
- `You already wrote the reply body, but you still have not called \`${args.directive.expectedReplyCallName}\`.`,
160
+ `You already produced content that can be delivered as the reply, but you still have not called \`${args.directive.expectedReplyCallName}\`.`,
161
161
  '',
162
162
  buildReplyToolReminderLine(args),
163
- 'If you still emit a plain final message without the tool, runtime will deliver it via direct-reply fallback and label that path explicitly in UI and transfer text.',
163
+ 'Do not rely on direct-reply fallback; it is only a temporary runtime transition safeguard, not the formal reply mechanism. Call the correct reply tool now.',
164
164
  ].join('\n');
165
165
  }
166
166
  function isReplyToolReminderPromptContent(content) {
@@ -103,7 +103,7 @@ Taskdoc is a **task contract** and the task's **team-shared source of current tr
103
103
  - If the current Side Dialog is complete and the assignment header says `replyTellask`: call `replyTellask({ replyContent })`
104
104
  - If the current Side Dialog is complete and the assignment header says `replyTellaskSessionless`: call `replyTellaskSessionless({ replyContent })`
105
105
  - If you are answering a tellasker `tellaskBack` follow-up and runtime exposes `replyTellaskBack`: call `replyTellaskBack({ replyContent })`
106
- - Plain text is not the normal completion channel for inter-dialog delivery; if you emit plain text instead of the reply tool, runtime may temporarily inject a `role=user` reminder telling you to use the correct reply function
106
+ - Plain text is not the completion channel for inter-dialog delivery; if you produce final deliverable content instead of the reply tool, runtime may temporarily inject a `role=user` reminder telling you to use the correct reply function. Do not rely on direct-reply fallback; it is only a temporary runtime transition safeguard, not the formal reply mechanism
107
107
 
108
108
  ### Low-Burden Rule
109
109
 
@@ -68,7 +68,7 @@ replyTellask({
68
68
 
69
69
  ### Key Points
70
70
 
71
- - Do not replace this with a plain final message
71
+ - Do not replace this with a plain final message; direct-reply fallback is only a temporary runtime transition safeguard, not a valid planned completion method
72
72
  - Put the final deliverable body directly in `replyContent`
73
73
  - If the header says `replyTellaskSessionless`, use the same shape with that exact function name
74
74
 
@@ -36,7 +36,7 @@ The **tool descriptions themselves** for these functions intentionally stay mini
36
36
  - Call whichever `reply*` runtime currently exposes; do not switch to another reply variant by yourself
37
37
  - If the assignment header explicitly names a reply function, follow that exact name
38
38
  - Put only the final deliverable body in `replyContent`; do not wrap it in meta-explanations like "I am now calling replyTellask"
39
- - If you emit plain text instead of the reply tool, runtime may inject a temporary `role=user` reminder telling you to use the correct reply function
39
+ - If you produce final deliverable content instead of the reply tool, runtime may inject a temporary `role=user` reminder telling you to use the correct reply function; do not rely on direct-reply fallback, which is only a temporary runtime transition safeguard, not the formal reply mechanism
40
40
 
41
41
  ### 1. add_reminder
42
42
 
@@ -103,7 +103,7 @@
103
103
  - 当前支线已经完成,且当前 assignment 明确要求 `replyTellask`:调用 `replyTellask({ replyContent })`
104
104
  - 当前支线已经完成,且当前 assignment 明确要求 `replyTellaskSessionless`:调用 `replyTellaskSessionless({ replyContent })`
105
105
  - 当前是在回复一条诉请者发来的 `tellaskBack` 续诉请,且 runtime 暴露了 `replyTellaskBack`:调用 `replyTellaskBack({ replyContent })`
106
- - 普通文本不是跨对话完成交付通道;如果你直接输出正文而没调 reply 工具,runtime 可能临时插入一条 `role=user` 的提醒要求你改用正确的 reply 函数
106
+ - 普通文本不是跨对话完成交付通道;如果你直接产出可作为最终回贴的内容而没调 reply 工具,runtime 可能临时插入一条 `role=user` 的提醒要求你改用正确的 reply 函数。不要依赖 direct-reply fallback;它只是运行时临时过渡兜底,不是正式回复机制
107
107
 
108
108
  ### 心智负担最小化规则
109
109
 
@@ -67,7 +67,7 @@ replyTellask({
67
67
 
68
68
  ### 关键点
69
69
 
70
- - 不要再发一条普通最终消息代替
70
+ - 不要再发一条普通最终消息代替;direct-reply fallback 只是运行时临时过渡兜底,不是可规划使用的收口方式
71
71
  - `replyContent` 直接放最终交付正文
72
72
  - 若 assignment 头部写的是 `replyTellaskSessionless`,则同结构替换函数名
73
73
 
@@ -36,7 +36,7 @@
36
36
  - 看见哪一个 `reply*` 被 runtime 暴露,就调用哪一个;不要自行改选别的 reply 变体
37
37
  - assignment 头部若已点名 reply 函数名,以那个名字为准
38
38
  - `replyContent` 只放最终交付正文,不要再包一层“我现在调用了 replyXXX”
39
- - 如果你直接输出普通文本而没调 reply 工具,runtime 可能插入一条 `role=user` 的 runtime reminder 纠正你
39
+ - 如果你直接产出最终回贴内容而没调 reply 工具,runtime 可能插入一条 `role=user` 的 runtime reminder 纠正你;不要依赖 direct-reply fallback,它只是运行时临时过渡兜底,不是正式回复机制
40
40
 
41
41
  ### 1. add_reminder
42
42