dominds 1.23.2 → 1.23.3

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 (133) hide show
  1. package/dist/dialog-display-state.js +112 -9
  2. package/dist/dialog.js +4 -0
  3. package/dist/docs/dialog-system.md +6 -2
  4. package/dist/docs/dialog-system.zh.md +5 -1
  5. package/dist/docs/diligence-push.md +27 -6
  6. package/dist/docs/diligence-push.zh.md +13 -6
  7. package/dist/docs/llm-provider-isolation.md +1 -1
  8. package/dist/docs/llm-provider-isolation.zh.md +1 -1
  9. package/dist/docs/tellask-revive-context-refactor.zh.md +13 -1
  10. package/dist/docs/volcengine-coding-plan-openai-compatible.zh.md +3 -2
  11. package/dist/llm/api-quirks.d.ts +1 -0
  12. package/dist/llm/api-quirks.js +35 -1
  13. package/dist/llm/defaults.yaml +4 -0
  14. package/dist/llm/gen/mock.js +26 -11
  15. package/dist/llm/gen/openai-compatible.js +1 -2
  16. package/dist/llm/kernel-driver/context-health.d.ts +3 -4
  17. package/dist/llm/kernel-driver/context-health.js +5 -2
  18. package/dist/llm/kernel-driver/context.d.ts +4 -8
  19. package/dist/llm/kernel-driver/context.js +5 -25
  20. package/dist/llm/kernel-driver/drive.js +158 -58
  21. package/dist/llm/kernel-driver/engine.d.ts +1 -1
  22. package/dist/llm/kernel-driver/engine.js +5 -3
  23. package/dist/llm/kernel-driver/flow.js +162 -45
  24. package/dist/llm/kernel-driver/runtime.d.ts +12 -11
  25. package/dist/llm/kernel-driver/runtime.js +106 -18
  26. package/dist/llm/kernel-driver/sideDialog.d.ts +3 -0
  27. package/dist/llm/kernel-driver/sideDialog.js +4 -1
  28. package/dist/llm/kernel-driver/tellask-special.d.ts +1 -0
  29. package/dist/llm/kernel-driver/tellask-special.js +30 -30
  30. package/dist/llm/kernel-driver/types.d.ts +7 -3
  31. package/dist/recovery/reply-special.js +1 -1
  32. package/dist/runtime/driver-messages.d.ts +14 -1
  33. package/dist/runtime/driver-messages.js +116 -6
  34. package/dist/runtime/inter-dialog-format.d.ts +1 -0
  35. package/dist/runtime/inter-dialog-format.js +7 -3
  36. package/dist/server/websocket-handler.js +0 -1
  37. package/dist/tools/team_mgmt-manual.js +2 -2
  38. package/package.json +2 -2
  39. package/webapp/dist/assets/{_basePickBy-C3SVVywm.js → _basePickBy-DMD1UhXs.js} +3 -3
  40. package/webapp/dist/assets/{_basePickBy-C3SVVywm.js.map → _basePickBy-DMD1UhXs.js.map} +1 -1
  41. package/webapp/dist/assets/{_baseUniq-egNq6cCa.js → _baseUniq-CsE8Qvwt.js} +2 -2
  42. package/webapp/dist/assets/{_baseUniq-egNq6cCa.js.map → _baseUniq-CsE8Qvwt.js.map} +1 -1
  43. package/webapp/dist/assets/{arc-CASAlRvm.js → arc-0h8sV6e1.js} +2 -2
  44. package/webapp/dist/assets/{arc-CASAlRvm.js.map → arc-0h8sV6e1.js.map} +1 -1
  45. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-B6n5FQmS.js → architectureDiagram-2XIMDMQ5-BbMESECO.js} +7 -7
  46. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-B6n5FQmS.js.map → architectureDiagram-2XIMDMQ5-BbMESECO.js.map} +1 -1
  47. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-D2UGGjic.js → blockDiagram-WCTKOSBZ-DwkN-9a4.js} +7 -7
  48. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-D2UGGjic.js.map → blockDiagram-WCTKOSBZ-DwkN-9a4.js.map} +1 -1
  49. package/webapp/dist/assets/{c4Diagram-IC4MRINW-BOY_bQFP.js → c4Diagram-IC4MRINW-CGYONEh1.js} +3 -3
  50. package/webapp/dist/assets/{c4Diagram-IC4MRINW-BOY_bQFP.js.map → c4Diagram-IC4MRINW-CGYONEh1.js.map} +1 -1
  51. package/webapp/dist/assets/{channel-CZ_X09H1.js → channel-DbSJhm5-.js} +2 -2
  52. package/webapp/dist/assets/{channel-CZ_X09H1.js.map → channel-DbSJhm5-.js.map} +1 -1
  53. package/webapp/dist/assets/{chunk-4BX2VUAB-BIwyAzZQ.js → chunk-4BX2VUAB-D1inRfgf.js} +2 -2
  54. package/webapp/dist/assets/{chunk-4BX2VUAB-BIwyAzZQ.js.map → chunk-4BX2VUAB-D1inRfgf.js.map} +1 -1
  55. package/webapp/dist/assets/{chunk-55IACEB6-zSjTFoCX.js → chunk-55IACEB6-DL1IDg_h.js} +2 -2
  56. package/webapp/dist/assets/{chunk-55IACEB6-zSjTFoCX.js.map → chunk-55IACEB6-DL1IDg_h.js.map} +1 -1
  57. package/webapp/dist/assets/{chunk-FMBD7UC4-CTpuRfdB.js → chunk-FMBD7UC4-CugIlRDV.js} +2 -2
  58. package/webapp/dist/assets/{chunk-FMBD7UC4-CTpuRfdB.js.map → chunk-FMBD7UC4-CugIlRDV.js.map} +1 -1
  59. package/webapp/dist/assets/{chunk-JSJVCQXG-CJ3DM4in.js → chunk-JSJVCQXG-DKHSdeu1.js} +2 -2
  60. package/webapp/dist/assets/{chunk-JSJVCQXG-CJ3DM4in.js.map → chunk-JSJVCQXG-DKHSdeu1.js.map} +1 -1
  61. package/webapp/dist/assets/{chunk-KX2RTZJC-B97EakhO.js → chunk-KX2RTZJC-DCU9tkq6.js} +2 -2
  62. package/webapp/dist/assets/{chunk-KX2RTZJC-B97EakhO.js.map → chunk-KX2RTZJC-DCU9tkq6.js.map} +1 -1
  63. package/webapp/dist/assets/{chunk-NQ4KR5QH-5o1o5x0z.js → chunk-NQ4KR5QH-DN3O2s2M.js} +4 -4
  64. package/webapp/dist/assets/{chunk-NQ4KR5QH-5o1o5x0z.js.map → chunk-NQ4KR5QH-DN3O2s2M.js.map} +1 -1
  65. package/webapp/dist/assets/{chunk-QZHKN3VN-D33FSIEb.js → chunk-QZHKN3VN-e3ztIJg0.js} +2 -2
  66. package/webapp/dist/assets/{chunk-QZHKN3VN-D33FSIEb.js.map → chunk-QZHKN3VN-e3ztIJg0.js.map} +1 -1
  67. package/webapp/dist/assets/{chunk-WL4C6EOR-C2InqFin.js → chunk-WL4C6EOR-Dv907NPM.js} +6 -6
  68. package/webapp/dist/assets/{chunk-WL4C6EOR-C2InqFin.js.map → chunk-WL4C6EOR-Dv907NPM.js.map} +1 -1
  69. package/webapp/dist/assets/{classDiagram-VBA2DB6C-BfLZmK48.js → classDiagram-VBA2DB6C-DOTXtxYZ.js} +7 -7
  70. package/webapp/dist/assets/{classDiagram-VBA2DB6C-BfLZmK48.js.map → classDiagram-VBA2DB6C-DOTXtxYZ.js.map} +1 -1
  71. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-BfLZmK48.js → classDiagram-v2-RAHNMMFH-DOTXtxYZ.js} +7 -7
  72. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-BfLZmK48.js.map → classDiagram-v2-RAHNMMFH-DOTXtxYZ.js.map} +1 -1
  73. package/webapp/dist/assets/{clone-BSCHnHfl.js → clone-6lYQMWpu.js} +2 -2
  74. package/webapp/dist/assets/{clone-BSCHnHfl.js.map → clone-6lYQMWpu.js.map} +1 -1
  75. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-CeQ1jAJJ.js → cose-bilkent-S5V4N54A-DoJeDXV0.js} +2 -2
  76. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-CeQ1jAJJ.js.map → cose-bilkent-S5V4N54A-DoJeDXV0.js.map} +1 -1
  77. package/webapp/dist/assets/{dagre-KLK3FWXG-WUuNYzcK.js → dagre-KLK3FWXG-F_n_vhV9.js} +7 -7
  78. package/webapp/dist/assets/{dagre-KLK3FWXG-WUuNYzcK.js.map → dagre-KLK3FWXG-F_n_vhV9.js.map} +1 -1
  79. package/webapp/dist/assets/{diagram-E7M64L7V-jOVCIExP.js → diagram-E7M64L7V-Crwhgyjv.js} +8 -8
  80. package/webapp/dist/assets/{diagram-E7M64L7V-jOVCIExP.js.map → diagram-E7M64L7V-Crwhgyjv.js.map} +1 -1
  81. package/webapp/dist/assets/{diagram-IFDJBPK2-cCeQqotA.js → diagram-IFDJBPK2-CIt1nnn5.js} +7 -7
  82. package/webapp/dist/assets/{diagram-IFDJBPK2-cCeQqotA.js.map → diagram-IFDJBPK2-CIt1nnn5.js.map} +1 -1
  83. package/webapp/dist/assets/{diagram-P4PSJMXO-DjAYFRLv.js → diagram-P4PSJMXO-qowipEfV.js} +7 -7
  84. package/webapp/dist/assets/{diagram-P4PSJMXO-DjAYFRLv.js.map → diagram-P4PSJMXO-qowipEfV.js.map} +1 -1
  85. package/webapp/dist/assets/{erDiagram-INFDFZHY-Dl_6U5fV.js → erDiagram-INFDFZHY-DV2BcYNa.js} +5 -5
  86. package/webapp/dist/assets/{erDiagram-INFDFZHY-Dl_6U5fV.js.map → erDiagram-INFDFZHY-DV2BcYNa.js.map} +1 -1
  87. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-D80nrZ3S.js → flowDiagram-PKNHOUZH-CAbWV161.js} +7 -7
  88. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-D80nrZ3S.js.map → flowDiagram-PKNHOUZH-CAbWV161.js.map} +1 -1
  89. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-T3WdMrgj.js → ganttDiagram-A5KZAMGK-CfdR7FRr.js} +3 -3
  90. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-T3WdMrgj.js.map → ganttDiagram-A5KZAMGK-CfdR7FRr.js.map} +1 -1
  91. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-ifV-jkKL.js → gitGraphDiagram-K3NZZRJ6-DuJFTELz.js} +8 -8
  92. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-ifV-jkKL.js.map → gitGraphDiagram-K3NZZRJ6-DuJFTELz.js.map} +1 -1
  93. package/webapp/dist/assets/{graph-D5jmnb35.js → graph-cjRyzujT.js} +3 -3
  94. package/webapp/dist/assets/{graph-D5jmnb35.js.map → graph-cjRyzujT.js.map} +1 -1
  95. package/webapp/dist/assets/{index-CGbZlct2.js → index-DgfF56L4.js} +36 -36
  96. package/webapp/dist/assets/{index-CGbZlct2.js.map → index-DgfF56L4.js.map} +1 -1
  97. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-C340CY5x.js → infoDiagram-LFFYTUFH-3wx-7AdD.js} +6 -6
  98. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-C340CY5x.js.map → infoDiagram-LFFYTUFH-3wx-7AdD.js.map} +1 -1
  99. package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56-Bu4RsELK.js → ishikawaDiagram-PHBUUO56-g6CMb1Qc.js} +2 -2
  100. package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56-Bu4RsELK.js.map → ishikawaDiagram-PHBUUO56-g6CMb1Qc.js.map} +1 -1
  101. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-Dvv3ypKA.js → journeyDiagram-4ABVD52K-DdCcmOBO.js} +5 -5
  102. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-Dvv3ypKA.js.map → journeyDiagram-4ABVD52K-DdCcmOBO.js.map} +1 -1
  103. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-D_rsetjW.js → kanban-definition-K7BYSVSG-BFw2emGl.js} +3 -3
  104. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-D_rsetjW.js.map → kanban-definition-K7BYSVSG-BFw2emGl.js.map} +1 -1
  105. package/webapp/dist/assets/{layout-CLmOfwnS.js → layout-Clazq06r.js} +5 -5
  106. package/webapp/dist/assets/{layout-CLmOfwnS.js.map → layout-Clazq06r.js.map} +1 -1
  107. package/webapp/dist/assets/{linear-DFAmViqi.js → linear-jdsBGgvD.js} +2 -2
  108. package/webapp/dist/assets/{linear-DFAmViqi.js.map → linear-jdsBGgvD.js.map} +1 -1
  109. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-Ce3_czeS.js → mindmap-definition-YRQLILUH-DLSZrW6l.js} +4 -4
  110. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-Ce3_czeS.js.map → mindmap-definition-YRQLILUH-DLSZrW6l.js.map} +1 -1
  111. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-U6_un5Sc.js → pieDiagram-SKSYHLDU-Uj-Zpci6.js} +8 -8
  112. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-U6_un5Sc.js.map → pieDiagram-SKSYHLDU-Uj-Zpci6.js.map} +1 -1
  113. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-DmVJQItS.js → quadrantDiagram-337W2JSQ-DO7Sl1nV.js} +3 -3
  114. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-DmVJQItS.js.map → quadrantDiagram-337W2JSQ-DO7Sl1nV.js.map} +1 -1
  115. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-BkNhSY-g.js → requirementDiagram-Z7DCOOCP-WrurrDKQ.js} +4 -4
  116. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-BkNhSY-g.js.map → requirementDiagram-Z7DCOOCP-WrurrDKQ.js.map} +1 -1
  117. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-E3UEy5SX.js → sankeyDiagram-WA2Y5GQK-gcxbxuZB.js} +2 -2
  118. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-E3UEy5SX.js.map → sankeyDiagram-WA2Y5GQK-gcxbxuZB.js.map} +1 -1
  119. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-k0w9WKAO.js → sequenceDiagram-2WXFIKYE-B98U2Npa.js} +4 -4
  120. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-k0w9WKAO.js.map → sequenceDiagram-2WXFIKYE-B98U2Npa.js.map} +1 -1
  121. package/webapp/dist/assets/{stateDiagram-RAJIS63D-DRgdJrlx.js → stateDiagram-RAJIS63D-BUgfHMbd.js} +9 -9
  122. package/webapp/dist/assets/{stateDiagram-RAJIS63D-DRgdJrlx.js.map → stateDiagram-RAJIS63D-BUgfHMbd.js.map} +1 -1
  123. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-edtkX9x6.js → stateDiagram-v2-FVOUBMTO-C8gH0rSW.js} +5 -5
  124. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-edtkX9x6.js.map → stateDiagram-v2-FVOUBMTO-C8gH0rSW.js.map} +1 -1
  125. package/webapp/dist/assets/{timeline-definition-YZTLITO2-D0zyXyNL.js → timeline-definition-YZTLITO2-DnVikX3B.js} +3 -3
  126. package/webapp/dist/assets/{timeline-definition-YZTLITO2-D0zyXyNL.js.map → timeline-definition-YZTLITO2-DnVikX3B.js.map} +1 -1
  127. package/webapp/dist/assets/{treemap-KZPCXAKY-CglsYqbQ.js → treemap-KZPCXAKY-BjhjT1IM.js} +5 -5
  128. package/webapp/dist/assets/{treemap-KZPCXAKY-CglsYqbQ.js.map → treemap-KZPCXAKY-BjhjT1IM.js.map} +1 -1
  129. package/webapp/dist/assets/{vennDiagram-LZ73GAT5-DsUizzvt.js → vennDiagram-LZ73GAT5-CXjPMxrl.js} +2 -2
  130. package/webapp/dist/assets/{vennDiagram-LZ73GAT5-DsUizzvt.js.map → vennDiagram-LZ73GAT5-CXjPMxrl.js.map} +1 -1
  131. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-DvYQ4zKY.js → xychartDiagram-JWTSCODW-ByKmk3Cb.js} +3 -3
  132. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-DvYQ4zKY.js.map → xychartDiagram-JWTSCODW-ByKmk3Cb.js.map} +1 -1
  133. package/webapp/dist/index.html +1 -1
@@ -90,6 +90,27 @@ function isDialogLatestResumable(latest) {
90
90
  function isSameDisplayState(left, right) {
91
91
  return JSON.stringify(left) === JSON.stringify(right);
92
92
  }
93
+ function isSideDialogResponseAnchor(record) {
94
+ return record?.type === 'tellask_anchor_record' && record.anchorRole === 'response';
95
+ }
96
+ function isNonIdleDisplayProjection(state) {
97
+ return state !== undefined && state.kind !== 'idle_waiting_user';
98
+ }
99
+ async function hasSideDialogFinalResponseAnchor(dialogId, latest) {
100
+ if (dialogId.selfId === dialogId.rootId) {
101
+ return false;
102
+ }
103
+ const rawCourse = latest.currentCourse;
104
+ const currentCourse = Number.isFinite(rawCourse) && rawCourse > 0 ? Math.floor(rawCourse) : 1;
105
+ const courseEvents = await persistence_1.DialogPersistence.loadCourseEvents(dialogId, currentCourse, 'running');
106
+ for (let index = courseEvents.length - 1; index >= 0; index -= 1) {
107
+ const event = courseEvents[index];
108
+ if (event.type === 'tellask_anchor_record') {
109
+ return isSideDialogResponseAnchor(event);
110
+ }
111
+ }
112
+ return false;
113
+ }
93
114
  function classifyRunControlBucket(state) {
94
115
  if (!state)
95
116
  return 'none';
@@ -124,7 +145,16 @@ async function getRunControlCountsSnapshot() {
124
145
  proceeding++;
125
146
  continue;
126
147
  }
127
- const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
148
+ let latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
149
+ if (!latest) {
150
+ continue;
151
+ }
152
+ const healedLatest = await healStaleSideDialogRunControlAfterFinalResponse({
153
+ dialogId,
154
+ latest,
155
+ trigger: 'run_control_snapshot',
156
+ });
157
+ latest = healedLatest;
128
158
  if (latest?.generating === true) {
129
159
  proceeding++;
130
160
  }
@@ -464,6 +494,9 @@ async function computeIdleDisplayStateFromPersistence(dialogId) {
464
494
  continueEnabled: true,
465
495
  };
466
496
  }
497
+ if (latest && (await hasSideDialogFinalResponseAnchor(dialogId, latest))) {
498
+ return { kind: 'idle_waiting_user' };
499
+ }
467
500
  const q4h = await persistence_1.DialogPersistence.loadQuestions4HumanState(dialogId, 'running');
468
501
  const pendingSideDialogs = await persistence_1.DialogPersistence.loadPendingSideDialogs(dialogId, 'running');
469
502
  const hasQ4H = q4h.length > 0;
@@ -479,15 +512,60 @@ async function computeIdleDisplayStateFromPersistence(dialogId) {
479
512
  }
480
513
  return { kind: 'idle_waiting_user' };
481
514
  }
515
+ async function healStaleSideDialogRunControlAfterFinalResponse(args) {
516
+ if (args.dialogId.selfId === args.dialogId.rootId ||
517
+ (args.latest.needsDrive !== true &&
518
+ args.latest.generating !== true &&
519
+ args.latest.executionMarker?.kind !== 'interrupted' &&
520
+ !isNonIdleDisplayProjection(args.latest.displayState))) {
521
+ return args.latest;
522
+ }
523
+ if (args.latest.executionMarker?.kind === 'dead') {
524
+ return args.latest;
525
+ }
526
+ if (args.latest.pendingCourseStartPrompt) {
527
+ return args.latest;
528
+ }
529
+ if (!(await hasSideDialogFinalResponseAnchor(args.dialogId, args.latest))) {
530
+ return args.latest;
531
+ }
532
+ log.warn('Healing stale sideDialog run-control flags after final response anchor', undefined, {
533
+ dialogId: args.dialogId.valueOf(),
534
+ trigger: args.trigger,
535
+ previousGenerating: args.latest.generating ?? null,
536
+ previousNeedsDrive: args.latest.needsDrive ?? null,
537
+ previousDisplayState: args.latest.displayState ?? null,
538
+ previousExecutionMarker: args.latest.executionMarker ?? null,
539
+ });
540
+ await persistence_1.DialogPersistence.mutateDialogLatest(args.dialogId, () => ({
541
+ kind: 'patch',
542
+ patch: {
543
+ generating: false,
544
+ needsDrive: false,
545
+ displayState: { kind: 'idle_waiting_user' },
546
+ executionMarker: undefined,
547
+ },
548
+ }));
549
+ return await persistence_1.DialogPersistence.loadDialogLatest(args.dialogId, 'running');
550
+ }
482
551
  async function refreshRunControlProjectionFromPersistenceFacts(dialogId, trigger) {
483
- const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
552
+ let latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
484
553
  if (!latest) {
485
554
  return null;
486
555
  }
487
- if (latest.generating === true) {
556
+ if (hasActiveRun(dialogId)) {
488
557
  return latest;
489
558
  }
490
- if (hasActiveRun(dialogId)) {
559
+ const healedStaleSideDialogRunControl = await healStaleSideDialogRunControlAfterFinalResponse({
560
+ dialogId,
561
+ latest,
562
+ trigger,
563
+ });
564
+ if (!healedStaleSideDialogRunControl) {
565
+ return null;
566
+ }
567
+ latest = healedStaleSideDialogRunControl;
568
+ if (latest.generating === true) {
491
569
  return latest;
492
570
  }
493
571
  const desired = await (async () => {
@@ -524,6 +602,9 @@ async function refreshRunControlProjectionFromPersistenceFacts(dialogId, trigger
524
602
  continueEnabled: true,
525
603
  };
526
604
  }
605
+ if (await hasSideDialogFinalResponseAnchor(dialogId, latest)) {
606
+ return { kind: 'idle_waiting_user' };
607
+ }
527
608
  const q4h = await persistence_1.DialogPersistence.loadQuestions4HumanState(dialogId, 'running');
528
609
  const pendingSideDialogs = await persistence_1.DialogPersistence.loadPendingSideDialogs(dialogId, 'running');
529
610
  const hasQ4H = q4h.length > 0;
@@ -610,16 +691,20 @@ async function reconcileDisplayStatesAfterRestart() {
610
691
  });
611
692
  continue;
612
693
  }
613
- const existing = latest?.displayState;
614
- const existingMarker = latest?.executionMarker;
694
+ if (!latest) {
695
+ continue;
696
+ }
697
+ const existing = latest.displayState;
698
+ const existingMarker = latest.executionMarker;
615
699
  if (existingMarker && existingMarker.kind === 'dead' && dialogId.selfId !== dialogId.rootId) {
616
- if (latest?.generating === true) {
700
+ if (latest.generating === true) {
701
+ const displayState = latest.displayState ?? { kind: 'dead', reason: existingMarker.reason };
617
702
  try {
618
703
  await persistence_1.DialogPersistence.mutateDialogLatest(dialogId, () => ({
619
704
  kind: 'patch',
620
705
  patch: {
621
706
  generating: false,
622
- displayState: latest.displayState ?? { kind: 'dead', reason: existingMarker.reason },
707
+ displayState,
623
708
  executionMarker: existingMarker,
624
709
  },
625
710
  }));
@@ -632,6 +717,24 @@ async function reconcileDisplayStatesAfterRestart() {
632
717
  }
633
718
  continue;
634
719
  }
720
+ if (dialogId.selfId !== dialogId.rootId &&
721
+ (latest.generating === true ||
722
+ latest.needsDrive === true ||
723
+ latest.executionMarker?.kind === 'interrupted' ||
724
+ isNonIdleDisplayProjection(latest.displayState))) {
725
+ const healedStaleSideDialogRunControl = await healStaleSideDialogRunControlAfterFinalResponse({
726
+ dialogId,
727
+ latest,
728
+ trigger: 'restart_reconciliation',
729
+ });
730
+ if (!healedStaleSideDialogRunControl) {
731
+ continue;
732
+ }
733
+ latest = healedStaleSideDialogRunControl;
734
+ if (latest.generating !== true && latest.needsDrive !== true) {
735
+ continue;
736
+ }
737
+ }
635
738
  if (isRecoverableGeneratingLatest(latest)) {
636
739
  try {
637
740
  await persistence_1.DialogPersistence.mutateDialogLatest(dialogId, () => ({
@@ -653,7 +756,7 @@ async function reconcileDisplayStatesAfterRestart() {
653
756
  }
654
757
  continue;
655
758
  }
656
- if (latest?.generating === true || latest?.needsDrive === true) {
759
+ if (latest.generating === true || latest.needsDrive === true) {
657
760
  const nextIdle = await computeIdleDisplayStateForReconciliation(dialogId);
658
761
  if (!nextIdle) {
659
762
  continue;
package/dist/dialog.js CHANGED
@@ -983,6 +983,10 @@ class Dialog {
983
983
  if (!trimmed) {
984
984
  throw new Error('Prompt is required to queue registered assignment update');
985
985
  }
986
+ // This is runtime scheduling state, not a durable redo log. Persisted business facts already
987
+ // record the latest assignment in the asker stack and pending records; if the process dies
988
+ // before this prompt is consumed, recovery should surface those facts loudly for the LLM to
989
+ // decide the next action. Do not add persistence here just to make the queued bubble replayable.
986
990
  if (existing?.kind !== 'registered_assignment_update') {
987
991
  const created = {
988
992
  kind: 'registered_assignment_update',
@@ -327,9 +327,11 @@ This makes Type B sideDialogs reusable across multiple Tellask sites without los
327
327
  **Updated Tellask While an Earlier Round Is Still Waiting (normative)**:
328
328
 
329
329
  - For a registered Side Dialog (`same agentId!sessionSlug`), runtime maintains one current waiting tellasker round.
330
- - If a newer TYPE B Tellask arrives before the earlier round replies, runtime immediately closes the earlier waiting round with a system-generated failed Tellask result. The wording must describe the conversation fact in business terms, not protocol jargon.
330
+ - If a newer TYPE B Tellask arrives before the earlier round replies, runtime updates pending state and the sideDialog asker stack to the newer round. The earlier call-site still needs a same-`callId` correspondence point, but UI/LLM wording must not claim the target Side Dialog has already received the update until the Side Dialog actually consumes the update prompt.
331
331
  - The tellaskee is not force-stopped. Instead, its next runtime prompt explains that the work request has been updated, explicitly says not to send a standalone acknowledgement, and includes the latest full assignment.
332
332
  - Delivery of that updated assignment prompt is queued in-order at the next safe turn boundary. Runtime must not reject the update merely because another normal queued prompt already exists; queued prompts are ordered work, not a single overwrite slot.
333
+ - The `registered_assignment_update` up-next item is runtime scheduling state, not a durable redo log. Do not make this queue a new persistence protocol merely to make the update bubble immediately replayable.
334
+ - Business self-healing after a backend restart comes from loud facts and sufficient context: pending reminders, asker-stack assignment state, missing old-call terminal facts, and missing assignment anchors should let the LLM decide whether to resend, correct, or wait. It is not a goal to guarantee that every in-memory queued update survives process death.
333
335
  - A Side Dialog reply produced before that updated assignment prompt is rendered locally MUST NOT be delivered to the tellasker as the newer round's result.
334
336
 
335
337
  **Key Characteristics**:
@@ -1291,8 +1293,10 @@ sequenceDiagram
1291
1293
  alt registry hit
1292
1294
  Reg-->>Driver: existing sideDialog selfId
1293
1295
  opt earlier round still waiting
1294
- Driver-->>Tellasker: close earlier waiting round with system-generated business notice
1295
1296
  Driver->>Side: queue update notice + latest full assignment
1297
+ Driver-->>Tellasker: report earlier round superseded and update registered
1298
+ Side-->>Driver: later consumes update prompt and writes assignment anchor
1299
+ Driver-->>Tellasker: upgrade call-site link to the concrete assignment bubble
1296
1300
  end
1297
1301
  Driver->>Side: restore + drive
1298
1302
  else registry miss
@@ -452,6 +452,8 @@ flowchart TD
452
452
 
453
453
  5. **支线对话响应供应**:支线对话通过持久化将响应写入*当前诉请者*的上下文(不是回调)。对于 TYPE B,每次诉请都会用最新的诉请者 + tellaskInfo 更新支线对话的 `assignmentFromAsker`,因此响应被路由到最近的诉请者(主线或支线对话)。这支持分离操作、复用和崩溃恢复。
454
454
 
455
+ TYPE B 长线诉请更新的确认边界必须谨慎区分:pending 状态与 `asker-stack.jsonl` 更新,只表示运行时已接受/登记新要求;只有支线 driver 实际消费 `registered_assignment_update` up-next、写入可见更新气泡和 assignment anchor 后,才表示目标支线已收到并开始处理更新。`registered_assignment_update` up-next 是运行态调度状态,不是持久化 redo log;不要为了“更新气泡”即时可重放而新增严格持久队列。后端进程重启后的业务自愈依赖 loud facts 与足够上下文(pending reminder、asker stack 最新 assignment、旧 call 是否缺少终态、目标支线是否缺少 assignment anchor),让 LLM 自主决定重发、修正或等待,而不是追求技术层面绝不丢内存队列。
456
+
455
457
  6. **支线对话注册表**:已注册的支线对话(TYPE B 长线诉请)在主线对话作用域的注册表中跟踪。注册表在 `clear_mind` 操作中持续存在,并在主线加载时重建。
456
458
 
457
459
  7. **状态保留契约**:
@@ -1290,7 +1292,9 @@ sequenceDiagram
1290
1292
 
1291
1293
  call-site 呼应不变量:每个 `tellask` 发起点的 `callId`,后续都必须在诉请者对话历史里留下同
1292
1294
  `callId` 的呼应点。正常支线回复、跨程 carryover、已注册会话被更新后的替代通知都可以结束这次
1293
- call;不能静默删除或覆盖 pending 轮次。
1295
+ call;不能静默删除或覆盖 pending 轮次。已注册会话被更新时,替代通知的文案不能在目标支线实际消费
1296
+ 更新前宣称“更新已送达/目标支线正在按新要求处理”;在此之前只能表达“旧轮已被新要求登记替换,等待
1297
+ 目标支线在安全边界消费更新”。
1294
1298
 
1295
1299
  WebUI 外链不变量:生成 `/dl/*` URL 时必须在业务现场显式写出目标语义,避免复用泛化 deep-link
1296
1300
  helper。`/dl/callsite` 只表示诉请者对话里的发起气泡:`selfId/course` 是诉请者对话坐标,`callId`
@@ -15,6 +15,10 @@ This document specifies two related runtime controls:
15
15
  - **Auto-continue injection**: for **main dialogs only**, whenever the driver would otherwise stop,
16
16
  runtime auto-sends a short diligence prompt (rendered as a normal user bubble) and continues
17
17
  generation, except when the dialog is legitimately suspended (Q4H or pending Side Dialogs).
18
+ - **Side Dialog quirk recovery push**: when a provider/API quirk explicitly requests a one-time
19
+ `diligence_push_once` recovery, a Side Dialog may receive a built-in runtime template, but only
20
+ when it currently has an active reply obligation; the template must name the one `replyTellask*`
21
+ tool confirmed by runtime.
18
22
  - **Required tool-use control**: for ordinary main and side dialog rounds, the Diligence Push
19
23
  checkbox controls whether the provider request must end through a Dominds tool call. When checked,
20
24
  the model is expected to call a tool such as `askHuman`, `tellask*`, `replyTellask*`, or another
@@ -32,8 +36,9 @@ This document specifies two related runtime controls:
32
36
  ## Non-goals
33
37
 
34
38
  - Auto-completing / auto-marking a dialog as done.
35
- - Auto-injecting Diligence Push prompts into Side Dialogs (Side Dialogs remain scoped and should
36
- report back to their tellasker).
39
+ - Auto-injecting ordinary Diligence Push prompts into Side Dialogs (Side Dialogs remain scoped and
40
+ should report back to their tellasker; only the provider-quirk recovery exception may use the Side
41
+ Dialog template).
37
42
 
38
43
  ## Definitions
39
44
 
@@ -57,7 +62,8 @@ This is the "controlled convergence" path. The diligence-push mechanism should *
57
62
 
58
63
  ### Trigger conditions (must all hold)
59
64
 
60
- - Dialog is the **Main Dialog** (never for Side Dialogs).
65
+ - Dialog is the **Main Dialog**; Side Dialogs only use the provider deadlock recovery exception
66
+ below.
61
67
  - Dialog is **not suspended**:
62
68
  - no pending Q4H, and
63
69
  - no pending Side Dialogs (waiting for backfill).
@@ -69,7 +75,15 @@ Some provider/API quirk handlers may request a one-time Diligence Push recovery
69
75
  same-context retries for a known deadlock pattern. This is not the ordinary "dialog is about to go
70
76
  idle" path. In that recovery-only case, pending sideDialogs do not veto the single Diligence Push
71
77
  injection, because the deadlock may happen in a function-result-driven generation round right after
72
- the main dialog has already registered an in-flight tellask/sideDialog. Q4H remains a hard blocker.
78
+ the main dialog has already registered an in-flight tellask/sideDialog.
79
+
80
+ Side Dialogs may also use this recovery path, but only when an active reply obligation still exists.
81
+ If no active reply obligation exists, runtime does not inject a push and leaves the retry-stopped
82
+ state for a human to handle. The Side Dialog recovery template does not read rtws diligence files;
83
+ it uses a built-in bilingual template containing the current time, the current Tellask goal, and the
84
+ single runtime-confirmed `replyTellask*` tool (for example
85
+ `replyTellaskSessionless({ replyContent })`). The LLM must not guess the reply variant. Q4H remains
86
+ a hard blocker.
73
87
 
74
88
  ### Action
75
89
 
@@ -164,7 +178,10 @@ post-iteration check:
164
178
  special case described above where one recovery-only Diligence Push may ignore pending
165
179
  sideDialogs.
166
180
  2. If there is any tool feedback, continue normally.
167
- 3. Otherwise (root only), attempt diligence-push auto-continue:
181
+ 3. Otherwise, attempt diligence-push auto-continue:
182
+ - Main Dialog: resolve the rtws diligence file or built-in fallback text.
183
+ - Side Dialog: only for provider-quirk recovery with an active reply obligation; otherwise do
184
+ not inject.
168
185
  - If disabled → stop normally.
169
186
  - If budget exhausted → emit an informational UI notice and stop further automatic Diligence
170
187
  Pushes for the current budget.
@@ -198,7 +215,11 @@ Regression tests should cover:
198
215
 
199
216
  - Main dialog: tool-only output → diligence injection → continued response
200
217
  - Main dialog: empty assistant output → diligence injection → continued response
201
- - SideDialog: no diligence injection
218
+ - SideDialog: ordinary idle path has no diligence injection
219
+ - SideDialog: provider quirk recovery + active reply obligation → inject the Side Dialog template in
220
+ the work language, include the current Tellask goal, and name the exact active `replyTellask*`
221
+ - SideDialog: provider quirk recovery + no active reply obligation → no injection; leave the
222
+ stopped/give-up state for human handling
202
223
  - rtws config:
203
224
  - `.minds/diligence.md` is honored when lang-specific file is absent
204
225
  - empty diligence file disables diligence-push
@@ -12,6 +12,7 @@ Dominds 主线对话旨在长期运行。主线对话"停止"(变为空闲)
12
12
  本文档指定两个相关但不同的运行时控制:
13
13
 
14
14
  - **自动续推注入**:仅针对**主线对话**,当驱动程序即将停止时,运行时会自动发送一个简短的鞭策语(渲染为正常的用户气泡)并继续生成,除非对话处于合法暂停状态(Q4H 或待处理的支线对话)。
15
+ - **支线 quirk 恢复续推**:当 provider/API quirk 明确给出一次性 `diligence_push_once` 恢复动作时,支线对话可使用运行时内置模板续推,但仅限当前存在活跃回贴义务(active reply obligation)的场景;模板必须点名运行时确认的唯一 `replyTellask*` 工具。
15
16
  - **强制工具调用控制**:对于普通的主线/支线对话轮次,`鞭策` 勾选项控制本轮 provider 请求是否必须通过 Dominds 工具结束。勾选时,模型应通过 `askHuman`、`tellask*`、`replyTellask*` 或其他运行时函数完成这一轮,而不是用普通文本直接收尾。FBR 中间轮是刻意例外:它们可以处于无可调用工具状态;FBR 收口阶段则必须调用两个结论工具之一。
16
17
 
17
18
  ## 目标
@@ -24,7 +25,7 @@ Dominds 主线对话旨在长期运行。主线对话"停止"(变为空闲)
24
25
  ## 非目标
25
26
 
26
27
  - 自动完成/自动将对话标记为完成。
27
- - 将自动鞭策语注入应用于支线对话(支线对话保持范围,应向其诉请者报告)。
28
+ - 将普通自动鞭策语注入应用于支线对话(支线对话保持范围,应向其诉请者报告;只有 provider quirk 恢复特例可使用支线模板)。
28
29
 
29
30
  ## 定义
30
31
 
@@ -48,18 +49,20 @@ Dominds 主线对话旨在长期运行。主线对话"停止"(变为空闲)
48
49
 
49
50
  ### 触发条件(必须全部满足)
50
51
 
51
- - 对话是**主线对话**(绝不会是支线对话)。
52
+ - 对话是**主线对话**;支线对话只适用下文 provider 死锁恢复特例。
52
53
  - 对话**未暂停**:
53
54
  - 没有待处理的 Q4H,并且
54
55
  - 没有待处理的支线对话(等待回填)。
55
56
  - 驱动程序即将停止生成循环(即没有工具/函数输出需要另一次迭代)。
56
57
 
57
- ### 例外:provider deadlock recovery
58
+ ### 例外:provider 死锁恢复
58
59
 
59
60
  某些 provider/API quirk 在识别到已知的 same-context deadlock,并停止沿用同一上下文自动重试后,
60
61
  会请求一次性的鞭策恢复。这不是普通的“对话即将空转停止”路径。在这个仅用于恢复的特例里,
61
62
  即使主线对话已经登记了在途诉请/支线对话,pending sideDialog 也不应否决这一次鞭策注入;因为卡死
62
- 可能正发生在函数结果驱动的生成轮次里。Q4H 仍然是硬阻塞条件。
63
+ 可能正发生在函数结果驱动的生成轮次里。
64
+
65
+ 支线对话也可使用这条恢复路径,但必须满足当前仍有活跃回贴义务(active reply obligation);否则不注入鞭策语,保留 retry stopped 状态交给人类处理。支线恢复模板不读取 rtws diligence 文件,而是使用内置双语模板,包含当前时间、当前诉请目标,并点名运行时已确认的唯一 `replyTellask*` 工具(例如 `replyTellaskSessionless({ replyContent })`),禁止让 LLM 自行猜测 reply 变体。Q4H 仍然是硬阻塞条件。
63
66
 
64
67
  ### 操作
65
68
 
@@ -138,7 +141,9 @@ members:
138
141
  1. 如果对话处于暂停状态,则停止(Q4H / 支线对话待处理);但上文所述 deadlock-recovery 特例除外,
139
142
  即那一次恢复专用的鞭策可忽略 pending sideDialog。
140
143
  2. 如果有任何工具反馈,则正常继续。
141
- 3. 否则(仅限主线对话),尝试 diligence-push 自动继续:
144
+ 3. 否则尝试 diligence-push 自动继续:
145
+ - 主线对话:按 rtws diligence 文件 / 内置回退文本解析并续推。
146
+ - 支线对话:仅在 provider quirk 恢复且存在活跃回贴义务(active reply obligation)时,注入支线内置模板;没有活跃回贴义务时不注入。
142
147
  - 如果禁用 → 正常停止。
143
148
  - 如果预算耗尽 → 发出一条仅用于提示的 UI 信息,并停止继续自动鞭策当前预算。
144
149
  - 否则 → 自动发送鞭策语并继续。
@@ -171,7 +176,9 @@ members:
171
176
 
172
177
  - 主线对话:仅工具输出 → 鞭策语注入 → 继续响应
173
178
  - 主线对话:空助手输出 → 鞭策语注入 → 继续响应
174
- - 支线对话:无鞭策语注入
179
+ - 支线对话:普通 idle 无鞭策语注入
180
+ - 支线对话:provider quirk recovery + 活跃回贴义务(active reply obligation)→ 注入支线模板,文案使用工作语言,包含当前诉请目标,并准确点名运行时确认的 `replyTellask*`
181
+ - 支线对话:provider quirk recovery + 无活跃回贴义务 → 不注入,保留 stopped/giveup 给人类处理
175
182
  - rtws 配置:
176
183
  - 当语言特定文件缺失时,`.minds/diligence.md` 被遵守
177
184
  - 空的鞭策语文件禁用 diligence-push
@@ -12,7 +12,7 @@ This means:
12
12
  - `apiType: anthropic` owns official Anthropic Messages semantics, including object-shaped `model_params.anthropic.thinking`.
13
13
  - `apiType: anthropic-compatible` owns Anthropic-compatible gateway semantics, including boolean `model_params.anthropic-compatible.thinking` mapped to provider `enabled` / `disabled` request objects.
14
14
 
15
- Some providers expose OpenAI-compatible or Anthropic-compatible endpoints while still requiring explicit provider quirks. Volcano Engine Ark Coding Plan now uses the OpenAI-compatible Chat Completions shape with its dedicated `/api/coding/v3` endpoint; the historical Anthropic-compatible Volcano tool-call quirks have been removed. Kimi Code also uses the OpenAI-compatible Chat Completions shape, but must use the built-in `kimi-code` provider's dedicated `/coding/v1` endpoint, real Dominds User-Agent, `prompt_cache_key`, and Kimi-specific `thinking`/`reasoning_effort` request shaping. See [`volcengine-coding-plan-openai-compatible.zh.md`](./volcengine-coding-plan-openai-compatible.zh.md) for the Volcano migration design record.
15
+ Some providers expose OpenAI-compatible or Anthropic-compatible endpoints while still requiring explicit provider quirks. Volcano Engine Ark Coding Plan now uses the OpenAI-compatible Chat Completions shape with its dedicated `/api/coding/v3` endpoint; the historical Anthropic-compatible Volcano tool-call quirks have been removed. Kimi Code also uses the OpenAI-compatible Chat Completions shape, but must use the built-in `kimi-code` provider's dedicated `/coding/v1` endpoint, versioned `KimiCLI/Dominds/<version>` User-Agent, `prompt_cache_key`, and Kimi-specific `thinking`/`reasoning_effort` request shaping. See [`volcengine-coding-plan-openai-compatible.zh.md`](./volcengine-coding-plan-openai-compatible.zh.md) for the Volcano migration design record.
16
16
 
17
17
  Similar field names across wrappers do not imply compatibility. For example, `reasoning_effort`, `verbosity`, `parallel_tool_calls`, and web search controls may look similar but can still differ in accepted values, payload shape, lifecycle events, validation rules, and runtime meaning.
18
18
 
@@ -12,7 +12,7 @@ Dominds 把每个 LLM provider wrapper 视为独立的协议适配器,而不
12
12
  - `apiType: anthropic` 只负责 Anthropic 官方 Messages 语义,包括 object 形态的 `model_params.anthropic.thinking`。
13
13
  - `apiType: anthropic-compatible` 负责 Anthropic 兼容网关语义,包括 boolean 形态的 `model_params.anthropic-compatible.thinking`,并映射为 provider 请求里的 `enabled` / `disabled` object。
14
14
 
15
- 某些 provider 虽然暴露 OpenAI-compatible 或 Anthropic-compatible endpoint,但仍可能需要明确的 provider quirk profile。火山方舟 Coding Plan 现在走 OpenAI-compatible Chat Completions 形态,并使用专属 `/api/coding/v3` endpoint;历史 Anthropic-compatible 火山工具调用 quirk 已取消。Kimi Code 也走 OpenAI-compatible Chat Completions 形态,但必须使用内置 `kimi-code` provider 的专属 `/coding/v1` endpoint、真实 Dominds User-Agent、`prompt_cache_key` 和 Kimi 专用 `thinking`/`reasoning_effort` 请求整形。火山迁移设计记录见 [`volcengine-coding-plan-openai-compatible.zh.md`](./volcengine-coding-plan-openai-compatible.zh.md)。
15
+ 某些 provider 虽然暴露 OpenAI-compatible 或 Anthropic-compatible endpoint,但仍可能需要明确的 provider quirk profile。火山方舟 Coding Plan 现在走 OpenAI-compatible Chat Completions 形态,并使用专属 `/api/coding/v3` endpoint;历史 Anthropic-compatible 火山工具调用 quirk 已取消。Kimi Code 也走 OpenAI-compatible Chat Completions 形态,但必须使用内置 `kimi-code` provider 的专属 `/coding/v1` endpoint、带 Dominds 版本的 `KimiCLI/Dominds/<version>` User-Agent、`prompt_cache_key` 和 Kimi 专用 `thinking`/`reasoning_effort` 请求整形。火山迁移设计记录见 [`volcengine-coding-plan-openai-compatible.zh.md`](./volcengine-coding-plan-openai-compatible.zh.md)。
16
16
 
17
17
  不同 wrapper 下看起来同名的字段,不代表它们可以互相兼容。比如 `reasoning_effort`、`verbosity`、`parallel_tool_calls`、web search 相关开关,名字可能相似,但可接受值、请求载荷形状、流事件生命周期、校验规则和运行时含义都可能不同。
18
18
 
@@ -418,6 +418,16 @@ replace pending 不是 silent overwrite,也不是 failed-result fallback。它
418
418
 
419
419
  这样既保留“抽掉旧义务”的业务语义,也让持久层维持 append/truncate-only,不做 YAML 数组整体覆盖。
420
420
 
421
+ **更新投递确认边界(已定案)**:
422
+
423
+ - `registered_assignment_update` 的 up-next 队列是运行时调度状态,不是严格持久化 redo log。不要为了让“更新气泡”即时可重放,把这条队列升级成新的持久事务日志。
424
+ - 诉请者侧反馈必须区分两种事实:
425
+ - **更新已登记 / 已排队**:pending record 与 asker stack 已按新诉请更新,支线后续应按最新 assignment 推进;这只说明运行时接受了更新。
426
+ - **目标支线已实际收到并开始处理更新**:支线 driver 已消费 up-next,写入可见的更新 assignment 气泡与 assignment anchor;只有这个事实才能支撑“目标支线已经收到/正在按新要求处理”的主线反馈。
427
+ - 如果支线正忙,更新 prompt 可以留在内存 up-next 中等待安全边界异步消费。主线不应在消费前渲染“已送达目标支线”语义的气泡;最多表达“旧轮已被新诉请登记替换,目标支线尚待消费更新”。
428
+ - 后端进程意外退出时,不追求技术角度严格自愈不丢 up-next。业务自愈边界是:LLM 能看到足够事实(pending reminder、asker stack 最新 assignment、旧 call 是否缺少终态、目标支线是否缺少 assignment anchor),从而自主判断是否需要重发/修正/继续等待。
429
+ - 因此,维护者修复这类时序问题时,优先做真实状态反馈与 loud diagnostics,不要把 up-next 队列泛化成持久化 redo log。
430
+
421
431
  ### 7.3 Reply tool 与 direct fallback
422
432
 
423
433
  `replyTellask*` 是精确回贴路径;direct fallback 是过渡兼容路径。
@@ -576,6 +586,7 @@ Dominds 不做同 course 内 context window 裁剪。上下文规则是:
576
586
  - replace pending 从 stack 中抽调旧 frame,再把新 obligation 压栈顶;
577
587
  - 原 call site 只出现 pointer,不出现真实回复正文;
578
588
  - Type B registered update 压栈、先回复新诉请、再恢复旧诉请;
589
+ - Type B registered update 在支线消费更新前,主线不得宣称目标支线已收到/正在处理更新;
579
590
  - 重启恢复后 pending 与 arrival fact 一致。
580
591
 
581
592
  ## 9. 已定案问题
@@ -589,4 +600,5 @@ Dominds 不做同 course 内 context window 裁剪。上下文规则是:
589
600
  5. 旧 pending record 缺少 `callSiteCourse` / `callSiteGenseq` 不迁移、不隔离兼容、不 fallback。旧 `.dialogs/` 可丢弃;新 validator 直接要求必填,缺失即 loud fail / quarantine。
590
601
  6. reply obligation 是栈,不是槽。root 与 side dialog 统一使用 `asker-stack.jsonl` 持久化 `AskerDialogStackFrame`,文件只允许 append/truncate。LLM context 从栈顶注入当前义务,而不是扫描历史 JSONL 对话。
591
602
  7. replace pending 是特殊栈操作:抽调旧 frame,再把新 obligation 压到栈顶;找不到旧 frame 必须 loud fail,不能静默 fallback 成普通 push。
592
- 8. 实现术语升级为 `MainDialog` / `SideDialog` / `askerDialog` / `assignmentFromAsker` / `askerStack`。旧 `supdialog` 术语退出实现代码与文档;用户可见文案继续使用“主线对话 / 支线对话、诉请者 / 被诉请者”。
603
+ 8. Type B registered update up-next 队列保持运行态内存调度,不升级为持久化 redo log;主线反馈必须等目标支线实际消费更新并写入 assignment anchor 后,才表达“目标支线已收到/开始处理更新”。
604
+ 9. 实现术语升级为 `MainDialog` / `SideDialog` / `askerDialog` / `assignmentFromAsker` / `askerStack`。旧 `supdialog` 术语退出实现代码与文档;用户可见文案继续使用“主线对话 / 支线对话、诉请者 / 被诉请者”。
@@ -61,15 +61,16 @@ Dominds 现有火山方舟 Coding Plan 接入主要围绕 Anthropic-compatible
61
61
  ```yaml
62
62
  providers:
63
63
  volcano-engine-coding-plan:
64
- name: Volcano Engine Coding Plan
64
+ name: Volcano Ark Coding Plan
65
65
  apiType: openai-compatible
66
66
  apiQuirks:
67
67
  - same-context-empty-response
68
+ - volcengine-invalid-parameter-aggressive-retry
68
69
  baseUrl: https://ark.cn-beijing.volces.com/api/coding/v3
69
70
  apiKeyEnvVar: ARK_API_KEY
70
71
  ```
71
72
 
72
- `apiType: openai-compatible` 表示底层 HTTP endpoint、Chat Completions 请求形态和 SSE 基础形态接近 OpenAI Chat Completions。历史上用于火山 Anthropic-compatible 文本 tool_use / 空对象参数拼接的专项 quirks 已删除;当前内置只保留 `same-context-empty-response` 这类通用 provider 重试/诊断 quirk
73
+ `apiType: openai-compatible` 表示底层 HTTP endpoint、Chat Completions 请求形态和 SSE 基础形态接近 OpenAI Chat Completions。历史上用于火山 Anthropic-compatible 文本 tool_use / 空对象参数拼接的专项 quirks 已删除;当前内置保留 `same-context-empty-response` 这类通用 provider 重试/诊断 quirk,并增加 `volcengine-invalid-parameter-aggressive-retry`,用于火山方舟 Coding Plan 偶发 `400 InvalidParameter` 但同 payload 可重放成功的场景,将其归类为 aggressive 策略重试。
73
74
 
74
75
  ### 具体模型优先
75
76
 
@@ -4,6 +4,7 @@ import type { ProviderConfig } from './client';
4
4
  import type { LlmRetryStrategy } from './gen';
5
5
  export type LlmFailureKind = 'retriable' | 'rejected' | 'fatal';
6
6
  export declare const SAME_CONTEXT_EMPTY_RESPONSE_API_QUIRK = "same-context-empty-response";
7
+ export declare const VOLCENGINE_INVALID_PARAMETER_AGGRESSIVE_RETRY_API_QUIRK = "volcengine-invalid-parameter-aggressive-retry";
7
8
  export type LlmFailureSummary = {
8
9
  kind: LlmFailureKind;
9
10
  message: string;
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.XCODE_BEST_STREAM_INTERNAL_ERROR_CODE = exports.SAME_CONTEXT_EMPTY_RESPONSE_API_QUIRK = void 0;
3
+ exports.XCODE_BEST_STREAM_INTERNAL_ERROR_CODE = exports.VOLCENGINE_INVALID_PARAMETER_AGGRESSIVE_RETRY_API_QUIRK = exports.SAME_CONTEXT_EMPTY_RESPONSE_API_QUIRK = void 0;
4
4
  exports.normalizeProviderApiQuirks = normalizeProviderApiQuirks;
5
5
  exports.createLlmFailureQuirkHandlerSession = createLlmFailureQuirkHandlerSession;
6
6
  const persistence_errors_1 = require("../persistence-errors");
7
7
  exports.SAME_CONTEXT_EMPTY_RESPONSE_API_QUIRK = 'same-context-empty-response';
8
+ exports.VOLCENGINE_INVALID_PARAMETER_AGGRESSIVE_RETRY_API_QUIRK = 'volcengine-invalid-parameter-aggressive-retry';
8
9
  const DOMINDS_LLM_EMPTY_RESPONSE_ERROR_CODE = 'DOMINDS_LLM_EMPTY_RESPONSE';
9
10
  const XCODE_BEST_EMPTY_RESPONSE_SINGLE_RETRY_DELAY_MS = 3000;
10
11
  const XCODE_BEST_EMPTY_RESPONSE_GIVE_UP_THRESHOLD = 5;
@@ -16,6 +17,8 @@ const XCODE_BEST_UNEXPECTED_EOF_RETRY_MESSAGE = 'xcode.best upstream stream ende
16
17
  const XCODE_BEST_MISREPORTED_403_RETRY_MESSAGE = 'xcode.best returned 403 for a transient upstream failure; retrying aggressively.';
17
18
  exports.XCODE_BEST_STREAM_INTERNAL_ERROR_CODE = 'XCODE_BEST_STREAM_INTERNAL_ERROR';
18
19
  const XCODE_BEST_STREAM_INTERNAL_RETRY_MESSAGE = 'xcode.best upstream stream reported internal_error from peer; retrying aggressively.';
20
+ const VOLCENGINE_INVALID_PARAMETER_MESSAGE_FRAGMENT = 'a parameter specified in the request is not valid';
21
+ const VOLCENGINE_INVALID_PARAMETER_AGGRESSIVE_RETRY_MESSAGE = 'Volcano Ark Coding Plan returned transient 400 InvalidParameter; retrying aggressively.';
19
22
  const LOCAL_FILE_IO_ERROR_CODES = new Set(['ENOENT', 'ENOTDIR', 'EISDIR', 'EACCES', 'EPERM']);
20
23
  const LOCAL_FILE_IO_SYSCALLS = new Set([
21
24
  'open',
@@ -69,6 +72,18 @@ function isXcodeBestStreamInternalFailure(failure, error) {
69
72
  const code = failure.code ?? readErrorCode(error);
70
73
  return code === exports.XCODE_BEST_STREAM_INTERNAL_ERROR_CODE;
71
74
  }
75
+ function isVolcengineTransientInvalidParameterFailure(args) {
76
+ const statuses = readFailureAndErrorStatuses(args);
77
+ if (!statuses.includes(400) || statuses.includes(429)) {
78
+ return false;
79
+ }
80
+ const code = (args.failure.code ?? readErrorCode(args.error))?.trim();
81
+ if (code !== 'InvalidParameter') {
82
+ return false;
83
+ }
84
+ return (args.failure.message.toLowerCase().includes(VOLCENGINE_INVALID_PARAMETER_MESSAGE_FRAGMENT) ||
85
+ errorChainIncludesMessageFragment(args.error, VOLCENGINE_INVALID_PARAMETER_MESSAGE_FRAGMENT));
86
+ }
72
87
  const XCODE_BEST_RETRY_QUIRK_RULES = [
73
88
  {
74
89
  statusPolicy: { kind: 'only_status', status: 403 },
@@ -494,9 +509,28 @@ function createSameContextEmptyResponseFailureQuirkHandlerSession(providerConfig
494
509
  },
495
510
  };
496
511
  }
512
+ function createVolcengineInvalidParameterAggressiveRetryQuirkHandlerSession() {
513
+ return {
514
+ quirkName: exports.VOLCENGINE_INVALID_PARAMETER_AGGRESSIVE_RETRY_API_QUIRK,
515
+ onFailure(args) {
516
+ if (!isVolcengineTransientInvalidParameterFailure({
517
+ failure: args.failure,
518
+ error: args.error,
519
+ })) {
520
+ return { kind: 'default' };
521
+ }
522
+ return {
523
+ kind: 'retry_strategy',
524
+ retryStrategy: 'aggressive',
525
+ message: VOLCENGINE_INVALID_PARAMETER_AGGRESSIVE_RETRY_MESSAGE,
526
+ };
527
+ },
528
+ };
529
+ }
497
530
  const FAILURE_QUIRK_HANDLER_FACTORIES = {
498
531
  'xcode.best': createXcodeBestFailureQuirkHandlerSession,
499
532
  [exports.SAME_CONTEXT_EMPTY_RESPONSE_API_QUIRK]: createSameContextEmptyResponseFailureQuirkHandlerSession,
533
+ [exports.VOLCENGINE_INVALID_PARAMETER_AGGRESSIVE_RETRY_API_QUIRK]: createVolcengineInvalidParameterAggressiveRetryQuirkHandlerSession,
500
534
  };
501
535
  function normalizeProviderApiQuirks(providerConfig) {
502
536
  const raw = providerConfig.apiQuirks;
@@ -14,6 +14,9 @@
14
14
  # `500 auth_unavailable: no auth available` infrastructure failures.
15
15
  # `apiQuirks: same-context-empty-response` detects repeated empty responses in the same
16
16
  # dialog generation context and stops same-context retry loops with a deadlock-break prompt.
17
+ # `apiQuirks: volcengine-invalid-parameter-aggressive-retry` classifies Volcano Ark Coding Plan
18
+ # transient `400 InvalidParameter` failures as aggressive retry when the shape matches that
19
+ # provider's replay-successful instability.
17
20
  # - tool_result_max_chars: optional transport-level cap for a single tool-result text payload
18
21
  # before Dominds projects it into the provider request. Use this when a provider/gateway enforces
19
22
  # a stricter per-item string limit than Dominds' built-in defaults.
@@ -498,6 +501,7 @@ providers:
498
501
  apiType: openai-compatible
499
502
  apiQuirks:
500
503
  - same-context-empty-response
504
+ - volcengine-invalid-parameter-aggressive-retry
501
505
  baseUrl: https://ark.cn-beijing.volces.com/api/coding/v3
502
506
  apiKeyEnvVar: ARK_API_KEY
503
507
  tech_spec_url: https://www.volcengine.com/docs/82379/1928261
@@ -379,10 +379,14 @@ responses:
379
379
  const matched = this.findMatchingResponse(db, content, role, context) ??
380
380
  this.buildReplyToolReminderAutoResponse(content, role, context);
381
381
  await delayWithAbort(matched?.delayMs ?? 0, abortSignal);
382
- await receiver.thinkingStart();
383
- await receiver.thinkingChunk(`[${modelName}] `);
384
- await receiver.thinkingChunk(content.substring(0, 50) || '(empty)');
385
- await receiver.thinkingFinish();
382
+ const thinkingText = matched?.omitDefaultThinking === true
383
+ ? (matched.thinkingResponse ?? '')
384
+ : (matched?.thinkingResponse ?? `[${modelName}] ${content.substring(0, 50) || '(empty)'}`);
385
+ if (thinkingText !== '') {
386
+ await receiver.thinkingStart();
387
+ await receiver.thinkingChunk(thinkingText);
388
+ await receiver.thinkingFinish();
389
+ }
386
390
  if (matched?.streamError) {
387
391
  if (matched.emitStreamErrorBeforeThrow && receiver.streamError) {
388
392
  await receiver.streamError(matched.streamError);
@@ -488,12 +492,17 @@ responses:
488
492
  this.buildReplyToolReminderAutoResponse(content, role, context);
489
493
  await delayWithAbort(matched?.delayMs ?? 0, abortSignal);
490
494
  const responseText = matched?.response ?? this.makeFallbackResponse(dbPath, content, role, modelName);
491
- const thinking = {
492
- type: 'thinking_msg',
493
- role: 'assistant',
494
- genseq,
495
- content: `[${modelName}] ${content.substring(0, 100)}`,
496
- };
495
+ const thinkingText = matched?.omitDefaultThinking === true
496
+ ? (matched.thinkingResponse ?? '')
497
+ : (matched?.thinkingResponse ?? `[${modelName}] ${content.substring(0, 100)}`);
498
+ const thinking = thinkingText === ''
499
+ ? undefined
500
+ : {
501
+ type: 'thinking_msg',
502
+ role: 'assistant',
503
+ genseq,
504
+ content: thinkingText,
505
+ };
497
506
  const saying = matched?.streamError || responseText !== ''
498
507
  ? {
499
508
  type: 'saying_msg',
@@ -567,7 +576,13 @@ responses:
567
576
  };
568
577
  }) ?? [];
569
578
  return {
570
- messages: saying ? [thinking, saying, ...funcMsgs] : [thinking, ...funcMsgs],
579
+ messages: thinking !== undefined
580
+ ? saying
581
+ ? [thinking, saying, ...funcMsgs]
582
+ : [thinking, ...funcMsgs]
583
+ : saying
584
+ ? [saying, ...funcMsgs]
585
+ : [...funcMsgs],
571
586
  usage,
572
587
  llmGenModel: modelName,
573
588
  };
@@ -48,7 +48,6 @@ const OPENAI_COMPATIBLE_REJECTED_REQUEST_ERROR_CODE = 'OPENAI_COMPATIBLE_REJECTE
48
48
  const KIMI_CODE_API_QUIRK = 'kimi-code';
49
49
  const KIMI_CODE_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
50
50
  const KIMI_CLI_CLOAK_API_QUIRK = 'kimi-cli-cloak';
51
- const KIMI_CLI_USER_AGENT = 'KimiCLI/1.41.0';
52
51
  const DISABLE_ASSISTANT_TOOL_CALL_REASONING_CONTENT_API_QUIRK = 'disable-assistant-tool-call-reasoning-content';
53
52
  const JSON_SCHEMA_COMBINATOR_KEYS = new Set([
54
53
  'anyOf',
@@ -578,7 +577,7 @@ function createOpenAiCompatibleClient(args) {
578
577
  };
579
578
  if (isKimiCliCloakProvider(args.providerConfig)) {
580
579
  options.defaultHeaders = {
581
- 'User-Agent': KIMI_CLI_USER_AGENT,
580
+ 'User-Agent': `KimiCLI/Dominds/${dominds_running_version_1.DOMINDS_RUNNING_VERSION}`,
582
581
  };
583
582
  }
584
583
  else if (isKimiCodeProvider(args.providerConfig)) {