dominds 1.16.1 → 1.16.2

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 (175) hide show
  1. package/dist/dialog-display-state.d.ts +4 -0
  2. package/dist/dialog-display-state.js +138 -19
  3. package/dist/dialog-fork.js +13 -5
  4. package/dist/dialog-interruption.d.ts +18 -0
  5. package/dist/dialog-interruption.js +34 -0
  6. package/dist/docs/dominds-agent-collaboration.zh.md +401 -0
  7. package/dist/llm/api-quirks.d.ts +11 -0
  8. package/dist/llm/api-quirks.js +322 -24
  9. package/dist/llm/defaults.yaml +2 -1
  10. package/dist/llm/gen/mock.d.ts +1 -1
  11. package/dist/llm/gen/mock.js +12 -0
  12. package/dist/llm/gen/openai-compatible.d.ts +1 -1
  13. package/dist/llm/gen/openai-compatible.js +9 -1
  14. package/dist/llm/gen/openai.d.ts +1 -1
  15. package/dist/llm/gen/openai.js +16 -2
  16. package/dist/llm/gen.d.ts +10 -0
  17. package/dist/llm/gen.js +10 -0
  18. package/dist/llm/kernel-driver/drive.js +994 -866
  19. package/dist/llm/kernel-driver/runtime.d.ts +16 -2
  20. package/dist/llm/kernel-driver/runtime.js +75 -10
  21. package/dist/llm/stop-reason-i18n.d.ts +9 -0
  22. package/dist/llm/stop-reason-i18n.js +114 -0
  23. package/dist/persistence-errors.d.ts +26 -0
  24. package/dist/persistence-errors.js +68 -0
  25. package/dist/persistence.d.ts +119 -82
  26. package/dist/persistence.js +1055 -426
  27. package/dist/server/api-routes.js +174 -58
  28. package/dist/server/websocket-handler.js +125 -21
  29. package/dist/tools/team_mgmt.js +2 -2
  30. package/package.json +3 -3
  31. package/webapp/dist/assets/{_basePickBy-C0MXVhMo.js → _basePickBy-BJWCmtUm.js} +3 -3
  32. package/webapp/dist/assets/_basePickBy-BJWCmtUm.js.map +1 -0
  33. package/webapp/dist/assets/{_baseUniq-CRygl41M.js → _baseUniq-D6k_lGfs.js} +2 -2
  34. package/webapp/dist/assets/_baseUniq-D6k_lGfs.js.map +1 -0
  35. package/webapp/dist/assets/{arc-BeHJtnt2.js → arc-DYDaESgj.js} +2 -2
  36. package/webapp/dist/assets/arc-DYDaESgj.js.map +1 -0
  37. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-CGdo5TVi.js → architectureDiagram-VXUJARFQ-DZMT7dqc.js} +8 -26
  38. package/webapp/dist/assets/architectureDiagram-VXUJARFQ-DZMT7dqc.js.map +1 -0
  39. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-C59VaSGw.js → blockDiagram-VD42YOAC-CABpgVAa.js} +170 -187
  40. package/webapp/dist/assets/blockDiagram-VD42YOAC-CABpgVAa.js.map +1 -0
  41. package/webapp/dist/assets/{c4Diagram-IC4MRINW-B3YCBmTx.js → c4Diagram-YG6GDRKO-COLZS8Ul.js} +4 -4
  42. package/webapp/dist/assets/c4Diagram-YG6GDRKO-COLZS8Ul.js.map +1 -0
  43. package/webapp/dist/assets/{channel-Dn4i8EAY.js → channel-CYFm9Cri.js} +2 -2
  44. package/webapp/dist/assets/channel-CYFm9Cri.js.map +1 -0
  45. package/webapp/dist/assets/{chunk-4BX2VUAB-CQbB3HlW.js → chunk-4BX2VUAB-CX_-XbaN.js} +2 -2
  46. package/webapp/dist/assets/chunk-4BX2VUAB-CX_-XbaN.js.map +1 -0
  47. package/webapp/dist/assets/{chunk-55IACEB6-CkQR_Qu9.js → chunk-55IACEB6-ByD-NdBC.js} +2 -2
  48. package/webapp/dist/assets/chunk-55IACEB6-ByD-NdBC.js.map +1 -0
  49. package/webapp/dist/assets/{chunk-WL4C6EOR-Bjl-j3bf.js → chunk-B4BG7PRW-BANemsDD.js} +121 -171
  50. package/webapp/dist/assets/chunk-B4BG7PRW-BANemsDD.js.map +1 -0
  51. package/webapp/dist/assets/{chunk-NQ4KR5QH-DBxY8LL2.js → chunk-DI55MBZ5-DV7sdJmQ.js} +7 -9
  52. package/webapp/dist/assets/chunk-DI55MBZ5-DV7sdJmQ.js.map +1 -0
  53. package/webapp/dist/assets/{chunk-FMBD7UC4-YSIOuaST.js → chunk-FMBD7UC4-DYGviJnf.js} +2 -2
  54. package/webapp/dist/assets/chunk-FMBD7UC4-DYGviJnf.js.map +1 -0
  55. package/webapp/dist/assets/{chunk-KX2RTZJC-DFiwgnFf.js → chunk-QN33PNHL-B8DWRL9f.js} +2 -2
  56. package/webapp/dist/assets/chunk-QN33PNHL-B8DWRL9f.js.map +1 -0
  57. package/webapp/dist/assets/{chunk-QZHKN3VN-BAdR6lLc.js → chunk-QZHKN3VN-B1aYIzMR.js} +2 -2
  58. package/webapp/dist/assets/chunk-QZHKN3VN-B1aYIzMR.js.map +1 -0
  59. package/webapp/dist/assets/{chunk-JSJVCQXG-CWDLRawz.js → chunk-TZMSLE5B-_HISzxl3.js} +6 -14
  60. package/webapp/dist/assets/chunk-TZMSLE5B-_HISzxl3.js.map +1 -0
  61. package/webapp/dist/assets/{classDiagram-VBA2DB6C-KBeS6WXD.js → classDiagram-2ON5EDUG-CpyYj1Rc.js} +6 -7
  62. package/webapp/dist/assets/classDiagram-2ON5EDUG-CpyYj1Rc.js.map +1 -0
  63. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-KBeS6WXD.js → classDiagram-v2-WZHVMYZB-CpyYj1Rc.js} +6 -7
  64. package/webapp/dist/assets/classDiagram-v2-WZHVMYZB-CpyYj1Rc.js.map +1 -0
  65. package/webapp/dist/assets/{clone-C7-aS2vm.js → clone-B_9AxWIU.js} +2 -2
  66. package/webapp/dist/assets/clone-B_9AxWIU.js.map +1 -0
  67. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-Bo5IsL-5.js → cose-bilkent-S5V4N54A-17ECLfPR.js} +2 -2
  68. package/webapp/dist/assets/cose-bilkent-S5V4N54A-17ECLfPR.js.map +1 -0
  69. package/webapp/dist/assets/cytoscape.esm-Bm8DJGmZ.js.map +1 -1
  70. package/webapp/dist/assets/{dagre-KLK3FWXG-C3FeHj7D.js → dagre-6UL2VRFP-DhRqcF1o.js} +7 -7
  71. package/webapp/dist/assets/dagre-6UL2VRFP-DhRqcF1o.js.map +1 -0
  72. package/webapp/dist/assets/defaultLocale-B2RvLBDe.js.map +1 -1
  73. package/webapp/dist/assets/{diagram-E7M64L7V-0HfiRW0u.js → diagram-PSM6KHXK-lpDT6Wwb.js} +10 -10
  74. package/webapp/dist/assets/diagram-PSM6KHXK-lpDT6Wwb.js.map +1 -0
  75. package/webapp/dist/assets/{diagram-IFDJBPK2-DJwh7SMO.js → diagram-QEK2KX5R-ohxbPpDH.js} +8 -9
  76. package/webapp/dist/assets/diagram-QEK2KX5R-ohxbPpDH.js.map +1 -0
  77. package/webapp/dist/assets/{diagram-P4PSJMXO-Y0RbvUZX.js → diagram-S2PKOQOG-DAFFGfch.js} +8 -8
  78. package/webapp/dist/assets/diagram-S2PKOQOG-DAFFGfch.js.map +1 -0
  79. package/webapp/dist/assets/{erDiagram-INFDFZHY-B183xyLa.js → erDiagram-Q2GNP2WA-BH-7rI43.js} +75 -96
  80. package/webapp/dist/assets/erDiagram-Q2GNP2WA-BH-7rI43.js.map +1 -0
  81. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-C_BR3Y3J.js → flowDiagram-NV44I4VS-CdEjFbz4.js} +81 -98
  82. package/webapp/dist/assets/flowDiagram-NV44I4VS-CdEjFbz4.js.map +1 -0
  83. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-CHLgkuJT.js → ganttDiagram-JELNMOA3-Cju2t-lK.js} +3 -28
  84. package/webapp/dist/assets/ganttDiagram-JELNMOA3-Cju2t-lK.js.map +1 -0
  85. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-D6C_HU1q.js → gitGraphDiagram-V2S2FVAM-CUJ5oxCg.js} +46 -38
  86. package/webapp/dist/assets/gitGraphDiagram-V2S2FVAM-CUJ5oxCg.js.map +1 -0
  87. package/webapp/dist/assets/graph-mhcc7ldf.js +425 -0
  88. package/webapp/dist/assets/graph-mhcc7ldf.js.map +1 -0
  89. package/webapp/dist/assets/{index-CLoNHmSQ.js → index-BLnM-uON.js} +2098 -1841
  90. package/webapp/dist/assets/{index-CLoNHmSQ.js.map → index-BLnM-uON.js.map} +1 -1
  91. package/webapp/dist/assets/{index-YaxF76or.css → index-xvYYeHuy.css} +1 -1
  92. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-mnMS61NO.js → infoDiagram-HS3SLOUP-Df8p0okQ.js} +7 -7
  93. package/webapp/dist/assets/infoDiagram-HS3SLOUP-Df8p0okQ.js.map +1 -0
  94. package/webapp/dist/assets/init-ZxktEp_H.js.map +1 -1
  95. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-BdeERn0r.js → journeyDiagram-XKPGCS4Q-BXMl8H-d.js} +5 -5
  96. package/webapp/dist/assets/journeyDiagram-XKPGCS4Q-BXMl8H-d.js.map +1 -0
  97. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-MLeCGcYg.js → kanban-definition-3W4ZIXB7-Cc5RwCEh.js} +3 -5
  98. package/webapp/dist/assets/kanban-definition-3W4ZIXB7-Cc5RwCEh.js.map +1 -0
  99. package/webapp/dist/assets/{layout-DY9VEQ9M.js → layout-BB2SvQcE.js} +5 -5
  100. package/webapp/dist/assets/layout-BB2SvQcE.js.map +1 -0
  101. package/webapp/dist/assets/{linear-D-T1xRsr.js → linear-Cj50lA0a.js} +2 -2
  102. package/webapp/dist/assets/linear-Cj50lA0a.js.map +1 -0
  103. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-CrVm8vj2.js → mindmap-definition-VGOIOE7T-BLBklJVX.js} +5 -7
  104. package/webapp/dist/assets/mindmap-definition-VGOIOE7T-BLBklJVX.js.map +1 -0
  105. package/webapp/dist/assets/ordinal-CxptdPJm.js.map +1 -1
  106. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-GtLLV0r1.js → pieDiagram-ADFJNKIX-BQrOgSc-.js} +8 -8
  107. package/webapp/dist/assets/pieDiagram-ADFJNKIX-BQrOgSc-.js.map +1 -0
  108. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-BCgk_2LP.js → quadrantDiagram-AYHSOK5B-C62TxtsO.js} +3 -3
  109. package/webapp/dist/assets/quadrantDiagram-AYHSOK5B-C62TxtsO.js.map +1 -0
  110. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-DYUKHWKR.js → requirementDiagram-UZGBJVZJ-Is6Q3osM.js} +6 -16
  111. package/webapp/dist/assets/requirementDiagram-UZGBJVZJ-Is6Q3osM.js.map +1 -0
  112. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-CFAH8OaY.js → sankeyDiagram-TZEHDZUN-CCAW8Dr2.js} +2 -2
  113. package/webapp/dist/assets/sankeyDiagram-TZEHDZUN-CCAW8Dr2.js.map +1 -0
  114. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-BTnhbv6w.js → sequenceDiagram-WL72ISMW-DpODpbl6.js} +201 -601
  115. package/webapp/dist/assets/sequenceDiagram-WL72ISMW-DpODpbl6.js.map +1 -0
  116. package/webapp/dist/assets/{stateDiagram-RAJIS63D-DWUnJTwO.js → stateDiagram-FKZM4ZOC-B8LHaf5T.js} +9 -9
  117. package/webapp/dist/assets/stateDiagram-FKZM4ZOC-B8LHaf5T.js.map +1 -0
  118. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-B-fc52bp.js → stateDiagram-v2-4FDKWEC3-4FzTkpgz.js} +5 -5
  119. package/webapp/dist/assets/stateDiagram-v2-4FDKWEC3-4FzTkpgz.js.map +1 -0
  120. package/webapp/dist/assets/{timeline-definition-YZTLITO2-BMZ52QaG.js → timeline-definition-IT6M3QCI-Ckxc4qZe.js} +3 -3
  121. package/webapp/dist/assets/timeline-definition-IT6M3QCI-Ckxc4qZe.js.map +1 -0
  122. package/webapp/dist/assets/{treemap-KZPCXAKY-fgSrze0S.js → treemap-GDKQZRPO-QQa4vKMv.js} +24 -37
  123. package/webapp/dist/assets/treemap-GDKQZRPO-QQa4vKMv.js.map +1 -0
  124. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-D6aviLtC.js → xychartDiagram-PRI3JC2R-Ba2uJcs6.js} +4 -4
  125. package/webapp/dist/assets/xychartDiagram-PRI3JC2R-Ba2uJcs6.js.map +1 -0
  126. package/webapp/dist/index.html +2 -2
  127. package/webapp/dist/assets/_basePickBy-C0MXVhMo.js.map +0 -1
  128. package/webapp/dist/assets/_baseUniq-CRygl41M.js.map +0 -1
  129. package/webapp/dist/assets/arc-BeHJtnt2.js.map +0 -1
  130. package/webapp/dist/assets/architectureDiagram-2XIMDMQ5-CGdo5TVi.js.map +0 -1
  131. package/webapp/dist/assets/blockDiagram-WCTKOSBZ-C59VaSGw.js.map +0 -1
  132. package/webapp/dist/assets/c4Diagram-IC4MRINW-B3YCBmTx.js.map +0 -1
  133. package/webapp/dist/assets/channel-Dn4i8EAY.js.map +0 -1
  134. package/webapp/dist/assets/chunk-4BX2VUAB-CQbB3HlW.js.map +0 -1
  135. package/webapp/dist/assets/chunk-55IACEB6-CkQR_Qu9.js.map +0 -1
  136. package/webapp/dist/assets/chunk-FMBD7UC4-YSIOuaST.js.map +0 -1
  137. package/webapp/dist/assets/chunk-JSJVCQXG-CWDLRawz.js.map +0 -1
  138. package/webapp/dist/assets/chunk-KX2RTZJC-DFiwgnFf.js.map +0 -1
  139. package/webapp/dist/assets/chunk-NQ4KR5QH-DBxY8LL2.js.map +0 -1
  140. package/webapp/dist/assets/chunk-QZHKN3VN-BAdR6lLc.js.map +0 -1
  141. package/webapp/dist/assets/chunk-WL4C6EOR-Bjl-j3bf.js.map +0 -1
  142. package/webapp/dist/assets/classDiagram-VBA2DB6C-KBeS6WXD.js.map +0 -1
  143. package/webapp/dist/assets/classDiagram-v2-RAHNMMFH-KBeS6WXD.js.map +0 -1
  144. package/webapp/dist/assets/clone-C7-aS2vm.js.map +0 -1
  145. package/webapp/dist/assets/cose-bilkent-S5V4N54A-Bo5IsL-5.js.map +0 -1
  146. package/webapp/dist/assets/dagre-KLK3FWXG-C3FeHj7D.js.map +0 -1
  147. package/webapp/dist/assets/diagram-E7M64L7V-0HfiRW0u.js.map +0 -1
  148. package/webapp/dist/assets/diagram-IFDJBPK2-DJwh7SMO.js.map +0 -1
  149. package/webapp/dist/assets/diagram-P4PSJMXO-Y0RbvUZX.js.map +0 -1
  150. package/webapp/dist/assets/erDiagram-INFDFZHY-B183xyLa.js.map +0 -1
  151. package/webapp/dist/assets/flowDiagram-PKNHOUZH-C_BR3Y3J.js.map +0 -1
  152. package/webapp/dist/assets/ganttDiagram-A5KZAMGK-CHLgkuJT.js.map +0 -1
  153. package/webapp/dist/assets/gitGraphDiagram-K3NZZRJ6-D6C_HU1q.js.map +0 -1
  154. package/webapp/dist/assets/graph-D0hgB-X8.js +0 -782
  155. package/webapp/dist/assets/graph-D0hgB-X8.js.map +0 -1
  156. package/webapp/dist/assets/infoDiagram-LFFYTUFH-mnMS61NO.js.map +0 -1
  157. package/webapp/dist/assets/ishikawaDiagram-PHBUUO56-XFIJsnJj.js +0 -966
  158. package/webapp/dist/assets/ishikawaDiagram-PHBUUO56-XFIJsnJj.js.map +0 -1
  159. package/webapp/dist/assets/journeyDiagram-4ABVD52K-BdeERn0r.js.map +0 -1
  160. package/webapp/dist/assets/kanban-definition-K7BYSVSG-MLeCGcYg.js.map +0 -1
  161. package/webapp/dist/assets/layout-DY9VEQ9M.js.map +0 -1
  162. package/webapp/dist/assets/linear-D-T1xRsr.js.map +0 -1
  163. package/webapp/dist/assets/mindmap-definition-YRQLILUH-CrVm8vj2.js.map +0 -1
  164. package/webapp/dist/assets/pieDiagram-SKSYHLDU-GtLLV0r1.js.map +0 -1
  165. package/webapp/dist/assets/quadrantDiagram-337W2JSQ-BCgk_2LP.js.map +0 -1
  166. package/webapp/dist/assets/requirementDiagram-Z7DCOOCP-DYUKHWKR.js.map +0 -1
  167. package/webapp/dist/assets/sankeyDiagram-WA2Y5GQK-CFAH8OaY.js.map +0 -1
  168. package/webapp/dist/assets/sequenceDiagram-2WXFIKYE-BTnhbv6w.js.map +0 -1
  169. package/webapp/dist/assets/stateDiagram-RAJIS63D-DWUnJTwO.js.map +0 -1
  170. package/webapp/dist/assets/stateDiagram-v2-FVOUBMTO-B-fc52bp.js.map +0 -1
  171. package/webapp/dist/assets/timeline-definition-YZTLITO2-BMZ52QaG.js.map +0 -1
  172. package/webapp/dist/assets/treemap-KZPCXAKY-fgSrze0S.js.map +0 -1
  173. package/webapp/dist/assets/vennDiagram-LZ73GAT5-D5DmDY9l.js +0 -2487
  174. package/webapp/dist/assets/vennDiagram-LZ73GAT5-D5DmDY9l.js.map +0 -1
  175. package/webapp/dist/assets/xychartDiagram-JWTSCODW-D6aviLtC.js.map +0 -1
@@ -55,6 +55,7 @@ const dialog_fork_1 = require("../dialog-fork");
55
55
  const dialog_global_registry_1 = require("../dialog-global-registry");
56
56
  const log_1 = require("../log");
57
57
  const persistence_1 = require("../persistence");
58
+ const persistence_errors_1 = require("../persistence-errors");
58
59
  const priming_1 = require("../priming");
59
60
  const work_language_1 = require("../runtime/work-language");
60
61
  const team_1 = require("../team");
@@ -90,24 +91,108 @@ function getErrorCode(error) {
90
91
  function isRecord(value) {
91
92
  return typeof value === 'object' && value !== null && !Array.isArray(value);
92
93
  }
93
- function parseDialogStatusFromUrl(urlObj) {
94
- const raw = urlObj.searchParams.get('status');
95
- if (raw === null)
96
- return 'running';
94
+ function parsePersistableDialogStatus(raw) {
97
95
  if (raw === 'running' || raw === 'completed' || raw === 'archived') {
98
96
  return raw;
99
97
  }
100
98
  return null;
101
99
  }
102
- function parseDialogStatusKind(raw) {
103
- if (raw === 'running' || raw === 'completed' || raw === 'archived') {
104
- return raw;
100
+ function readOptionalPersistableDialogStatusQuery(urlObj) {
101
+ const raw = urlObj.searchParams.get('status');
102
+ if (raw === null) {
103
+ return { kind: 'missing' };
104
+ }
105
+ const status = parsePersistableDialogStatus(raw);
106
+ if (status === null) {
107
+ return { kind: 'invalid' };
108
+ }
109
+ return { kind: 'value', status };
110
+ }
111
+ function readOptionalPersistableDialogStatus(raw) {
112
+ if (raw === undefined) {
113
+ return { kind: 'missing' };
114
+ }
115
+ const status = parsePersistableDialogStatus(raw);
116
+ if (status === null) {
117
+ return { kind: 'invalid' };
118
+ }
119
+ return { kind: 'value', status };
120
+ }
121
+ async function loadDialogMetadataForLookup(dialogId, status, context) {
122
+ try {
123
+ return await persistence_1.DialogPersistence.loadDialogMetadata(dialogId, status);
124
+ }
125
+ catch (error) {
126
+ if (!(0, persistence_errors_1.findDomindsPersistenceFileError)(error)) {
127
+ throw error;
128
+ }
129
+ log.warn(`${context}: dialog metadata quarantined during lazy lookup`, error, {
130
+ dialogId: dialogId.valueOf(),
131
+ status,
132
+ });
133
+ return null;
134
+ }
135
+ }
136
+ async function loadRootDialogMetadataForLookup(dialogId, status, context) {
137
+ try {
138
+ return await persistence_1.DialogPersistence.loadRootDialogMetadata(dialogId, status);
139
+ }
140
+ catch (error) {
141
+ if (!(0, persistence_errors_1.findDomindsPersistenceFileError)(error)) {
142
+ throw error;
143
+ }
144
+ log.warn(`${context}: root dialog metadata quarantined during lazy lookup`, error, {
145
+ dialogId: dialogId.valueOf(),
146
+ status,
147
+ });
148
+ return null;
149
+ }
150
+ }
151
+ async function loadDialogLatestForLookup(dialogId, status, context) {
152
+ try {
153
+ return await persistence_1.DialogPersistence.loadDialogLatest(dialogId, status);
154
+ }
155
+ catch (error) {
156
+ if (!(0, persistence_errors_1.findDomindsPersistenceFileError)(error)) {
157
+ throw error;
158
+ }
159
+ log.warn(`${context}: latest.yaml quarantined during lazy lookup`, error, {
160
+ dialogId: dialogId.valueOf(),
161
+ status,
162
+ });
163
+ return null;
105
164
  }
106
- return null;
107
165
  }
108
166
  async function detectWaitingForFreshBootsReasoning(dialogId, status) {
109
- const pending = await persistence_1.DialogPersistence.loadPendingSubdialogs(dialogId, status);
110
- return pending.some((entry) => entry.callName === 'freshBootsReasoning');
167
+ try {
168
+ // Pending-subdialogs is also lazy-loaded per dialog. A malformed queue should quarantine just
169
+ // that dialog and degrade this derived flag to "unknown/false" instead of taking down the
170
+ // surrounding list or hierarchy response.
171
+ const pending = await persistence_1.DialogPersistence.loadPendingSubdialogs(dialogId, status);
172
+ return pending.some((entry) => entry.callName === 'freshBootsReasoning');
173
+ }
174
+ catch (error) {
175
+ if (!(0, persistence_errors_1.findDomindsPersistenceFileError)(error)) {
176
+ throw error;
177
+ }
178
+ log.warn('detectWaitingForFreshBootsReasoning: pending-subdialogs quarantined during lookup', error, {
179
+ dialogId: dialogId.valueOf(),
180
+ status,
181
+ });
182
+ return false;
183
+ }
184
+ }
185
+ async function pathStillExistsForLookup(targetPath) {
186
+ try {
187
+ await promises_1.default.access(targetPath);
188
+ return true;
189
+ }
190
+ catch (error) {
191
+ if (getErrorCode(error) === 'ENOENT') {
192
+ return false;
193
+ }
194
+ throw error;
195
+ }
111
196
  }
112
197
  function buildPrimingWarningSummary(warnings) {
113
198
  if (warnings.length === 0)
@@ -219,7 +304,17 @@ async function handleSaveCurrentCoursePriming(req, res) {
219
304
  }
220
305
  const rootId = typeof dialogRaw['rootId'] === 'string' ? dialogRaw['rootId'].trim() : '';
221
306
  const selfId = typeof dialogRaw['selfId'] === 'string' ? dialogRaw['selfId'].trim() : '';
222
- const status = parseDialogStatusKind(dialogRaw['status']) ?? 'running';
307
+ const parsedStatus = readOptionalPersistableDialogStatus(dialogRaw['status']);
308
+ if (parsedStatus.kind === 'invalid') {
309
+ const payload = {
310
+ success: false,
311
+ error: 'dialog.status must be running, completed, or archived',
312
+ errorCode: 'INVALID_REQUEST',
313
+ };
314
+ respondJson(res, 400, payload);
315
+ return true;
316
+ }
317
+ const status = parsedStatus.kind === 'missing' ? 'running' : parsedStatus.status;
223
318
  const course = typeof courseRaw === 'number' && Number.isFinite(courseRaw) ? Math.floor(courseRaw) : 0;
224
319
  const slug = typeof slugRaw === 'string' ? slugRaw.trim() : '';
225
320
  const overwrite = overwriteRaw === true;
@@ -1073,11 +1168,12 @@ async function handleApiRoute(req, res, pathname, context) {
1073
1168
  // Dialog list endpoint
1074
1169
  if (pathname === '/api/dialogs' && req.method === 'GET') {
1075
1170
  const urlObj = new URL(req.url ?? '', 'http://127.0.0.1');
1076
- const status = parseDialogStatusFromUrl(urlObj);
1077
- if (!status) {
1171
+ const parsedStatus = readOptionalPersistableDialogStatusQuery(urlObj);
1172
+ if (parsedStatus.kind === 'invalid') {
1078
1173
  respondJson(res, 400, { success: false, error: 'Invalid status' });
1079
1174
  return true;
1080
1175
  }
1176
+ const status = parsedStatus.kind === 'missing' ? 'running' : parsedStatus.status;
1081
1177
  return await handleGetDialogs(res, status);
1082
1178
  }
1083
1179
  if (pathname === '/api/dialogs/run-control-counts' && req.method === 'GET') {
@@ -1137,11 +1233,12 @@ async function handleApiRoute(req, res, pathname, context) {
1137
1233
  const parts = pathname.split('/');
1138
1234
  const rootId = parts[3].replace(/%2F/g, '/');
1139
1235
  const urlObj = new URL(req.url ?? '', 'http://127.0.0.1');
1140
- const status = parseDialogStatusFromUrl(urlObj);
1141
- if (!status) {
1236
+ const parsedStatus = readOptionalPersistableDialogStatusQuery(urlObj);
1237
+ if (parsedStatus.kind === 'invalid') {
1142
1238
  respondJson(res, 400, { success: false, error: 'Invalid status' });
1143
1239
  return true;
1144
1240
  }
1241
+ const status = parsedStatus.kind === 'missing' ? 'running' : parsedStatus.status;
1145
1242
  return await handleGetDialogHierarchy(res, rootId, status);
1146
1243
  }
1147
1244
  // Serve persisted dialog artifacts (binary)
@@ -1164,11 +1261,12 @@ async function handleApiRoute(req, res, pathname, context) {
1164
1261
  return true;
1165
1262
  }
1166
1263
  const urlObj = new URL(req.url ?? '', 'http://127.0.0.1');
1167
- const status = parseDialogStatusFromUrl(urlObj);
1168
- if (!status) {
1264
+ const parsedStatus = readOptionalPersistableDialogStatusQuery(urlObj);
1265
+ if (parsedStatus.kind === 'invalid') {
1169
1266
  respondJson(res, 400, { success: false, error: 'Invalid status' });
1170
1267
  return true;
1171
1268
  }
1269
+ const status = parsedStatus.kind === 'missing' ? 'running' : parsedStatus.status;
1172
1270
  return await handleGetDialogArtifact(req, res, { rootId, selfId }, status);
1173
1271
  }
1174
1272
  // Get specific dialog
@@ -1177,11 +1275,12 @@ async function handleApiRoute(req, res, pathname, context) {
1177
1275
  const selfId = (parts[4] || parts[3]).replace(/%2F/g, '/');
1178
1276
  const rootId = parts[3].replace(/%2F/g, '/');
1179
1277
  const urlObj = new URL(req.url ?? '', 'http://127.0.0.1');
1180
- const status = parseDialogStatusFromUrl(urlObj);
1181
- if (!status) {
1278
+ const parsedStatus = readOptionalPersistableDialogStatusQuery(urlObj);
1279
+ if (parsedStatus.kind === 'invalid') {
1182
1280
  respondJson(res, 400, { success: false, error: 'Invalid status' });
1183
1281
  return true;
1184
1282
  }
1283
+ const status = parsedStatus.kind === 'missing' ? 'running' : parsedStatus.status;
1185
1284
  const dialog = { selfId, rootId, status };
1186
1285
  return await handleGetDialog(res, dialog, status);
1187
1286
  }
@@ -1850,25 +1949,29 @@ async function handleGetToolsRegistry(req, res) {
1850
1949
  const selfId = typeof selfIdRaw === 'string' ? selfIdRaw.trim() : '';
1851
1950
  const requestedAgentId = typeof agentIdRaw === 'string' ? agentIdRaw.trim() : '';
1852
1951
  const requestedTaskDocPath = typeof taskDocPathRaw === 'string' ? taskDocPathRaw.trim() : '';
1952
+ const requestedStatusParam = readOptionalPersistableDialogStatusQuery(urlObj);
1953
+ if (requestedStatusParam.kind === 'invalid') {
1954
+ respondJson(res, 400, { success: false, error: 'Invalid status' });
1955
+ return true;
1956
+ }
1853
1957
  const resolveDialogMetadata = async () => {
1854
1958
  if (rootId === '' || selfId === '') {
1855
1959
  return null;
1856
1960
  }
1857
- const requestedStatus = parseDialogStatusFromUrl(urlObj);
1858
1961
  const statusOrder = [
1859
1962
  'running',
1860
1963
  'completed',
1861
1964
  'archived',
1862
1965
  ];
1863
- const candidateStatuses = requestedStatus === null
1966
+ const candidateStatuses = requestedStatusParam.kind === 'missing'
1864
1967
  ? statusOrder
1865
1968
  : [
1866
- requestedStatus,
1867
- ...statusOrder.filter((status) => status !== requestedStatus),
1969
+ requestedStatusParam.status,
1970
+ ...statusOrder.filter((status) => status !== requestedStatusParam.status),
1868
1971
  ];
1869
1972
  for (const status of candidateStatuses) {
1870
1973
  const dialogId = new dialog_1.DialogID(selfId, rootId);
1871
- const metadata = await persistence_1.DialogPersistence.loadDialogMetadata(dialogId, status);
1974
+ const metadata = await loadDialogMetadataForLookup(dialogId, status, 'resolveDialogMetadata');
1872
1975
  if (metadata) {
1873
1976
  return metadata;
1874
1977
  }
@@ -1992,21 +2095,14 @@ async function handleGetDialogs(res, status) {
1992
2095
  const ids = await persistence_1.DialogPersistence.listDialogs(status);
1993
2096
  for (const id of ids) {
1994
2097
  const dialogId = new dialog_1.DialogID(id);
1995
- const meta = await persistence_1.DialogPersistence.loadRootDialogMetadata(dialogId, status);
2098
+ const meta = await loadRootDialogMetadataForLookup(dialogId, status, 'handleListDialogs');
1996
2099
  if (!meta)
1997
2100
  continue;
1998
- // Load latest.yaml for currentCourse and lastModified timestamp
1999
- const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, status);
2101
+ // listDialogs() only yields candidate IDs. Metadata/latest stay lazy so one malformed dialog
2102
+ // can quarantine itself here without taking down the whole list response.
2103
+ const latest = await loadDialogLatestForLookup(dialogId, status, 'handleListDialogs');
2000
2104
  const rootPath = persistence_1.DialogPersistence.getRootDialogPath(dialogId, status);
2001
- const rootStillExists = await promises_1.default
2002
- .access(rootPath)
2003
- .then(() => true)
2004
- .catch((error) => {
2005
- if (getErrorCode(error) === 'ENOENT') {
2006
- return false;
2007
- }
2008
- throw error;
2009
- });
2105
+ const rootStillExists = await pathStillExistsForLookup(rootPath);
2010
2106
  if (!rootStillExists) {
2011
2107
  continue;
2012
2108
  }
@@ -2014,6 +2110,9 @@ async function handleGetDialogs(res, status) {
2014
2110
  // Count subdialogs for this root dialog
2015
2111
  const subPath = path.join(rootPath, 'subdialogs');
2016
2112
  const subdialogCount = await countSubdialogs(subPath);
2113
+ if (!(await pathStillExistsForLookup(rootPath))) {
2114
+ continue;
2115
+ }
2017
2116
  rootDialogs.push({
2018
2117
  rootId: meta.id,
2019
2118
  agentId: meta.agentId,
@@ -2067,12 +2166,17 @@ async function countSubdialogs(dirPath) {
2067
2166
  const dialogYamlPath = path.join(fullPath, 'dialog.yaml');
2068
2167
  try {
2069
2168
  await promises_1.default.access(dialogYamlPath);
2070
- // This directory contains dialog.yaml - it's a subdialog
2169
+ // This directory contains dialog.yaml - it's a subdialog.
2071
2170
  count++;
2072
2171
  }
2073
- catch {
2074
- // No dialog.yaml - recurse into this directory
2075
- count += await countSubdialogs(fullPath);
2172
+ catch (error) {
2173
+ if (getErrorCode(error) === 'ENOENT') {
2174
+ // Only a missing dialog.yaml means "keep recursing". Permission/I/O failures must stay
2175
+ // loud so operators can distinguish inaccessible paths from ordinary non-dialog dirs.
2176
+ count += await countSubdialogs(fullPath);
2177
+ continue;
2178
+ }
2179
+ throw error;
2076
2180
  }
2077
2181
  }
2078
2182
  }
@@ -2093,7 +2197,7 @@ async function countSubdialogs(dirPath) {
2093
2197
  */
2094
2198
  async function handleGetDialogHierarchy(res, rootId, status) {
2095
2199
  try {
2096
- const rootMeta = await persistence_1.DialogPersistence.loadRootDialogMetadata(new dialog_1.DialogID(rootId), status);
2200
+ const rootMeta = await loadRootDialogMetadataForLookup(new dialog_1.DialogID(rootId), status, 'handleGetDialogHierarchy:root');
2097
2201
  if (!rootMeta) {
2098
2202
  respondJson(res, 404, {
2099
2203
  success: false,
@@ -2102,17 +2206,9 @@ async function handleGetDialogHierarchy(res, rootId, status) {
2102
2206
  return true;
2103
2207
  }
2104
2208
  // Load latest.yaml for root dialog currentCourse and lastModified timestamp
2105
- const rootLatest = await persistence_1.DialogPersistence.loadDialogLatest(new dialog_1.DialogID(rootId), status);
2209
+ const rootLatest = await loadDialogLatestForLookup(new dialog_1.DialogID(rootId), status, 'handleGetDialogHierarchy:root');
2106
2210
  const rootPath = persistence_1.DialogPersistence.getRootDialogPath(new dialog_1.DialogID(rootId), status);
2107
- const rootStillExists = await promises_1.default
2108
- .access(rootPath)
2109
- .then(() => true)
2110
- .catch((error) => {
2111
- if (getErrorCode(error) === 'ENOENT') {
2112
- return false;
2113
- }
2114
- throw error;
2115
- });
2211
+ const rootStillExists = await pathStillExistsForLookup(rootPath);
2116
2212
  if (!rootStillExists) {
2117
2213
  respondJson(res, 404, {
2118
2214
  success: false,
@@ -2131,18 +2227,29 @@ async function handleGetDialogHierarchy(res, rootId, status) {
2131
2227
  displayState: rootLatest?.displayState,
2132
2228
  waitingForFreshBootsReasoning: await detectWaitingForFreshBootsReasoning(new dialog_1.DialogID(rootId), status),
2133
2229
  };
2230
+ if (!(await pathStillExistsForLookup(rootPath))) {
2231
+ respondJson(res, 404, {
2232
+ success: false,
2233
+ error: `Root dialog ${rootId} was quarantined as malformed`,
2234
+ });
2235
+ return true;
2236
+ }
2134
2237
  let subdialogs = [];
2135
2238
  const dialogIds = await persistence_1.DialogPersistence.listAllDialogIds(status);
2136
2239
  for (const dialogId of dialogIds) {
2137
2240
  if (dialogId.rootId !== rootId || dialogId.selfId === rootId) {
2138
2241
  continue;
2139
2242
  }
2140
- const meta = await persistence_1.DialogPersistence.loadDialogMetadata(dialogId, status);
2243
+ const meta = await loadDialogMetadataForLookup(dialogId, status, 'handleGetDialogHierarchy:subdialog');
2141
2244
  if (!meta) {
2142
2245
  continue;
2143
2246
  }
2144
- const subLatest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, status);
2247
+ const subLatest = await loadDialogLatestForLookup(dialogId, status, 'handleGetDialogHierarchy:subdialog');
2145
2248
  const waitingForFreshBootsReasoning = await detectWaitingForFreshBootsReasoning(dialogId, status);
2249
+ const subdialogPath = persistence_1.DialogPersistence.getSubdialogPath(dialogId, status);
2250
+ if (!(await pathStillExistsForLookup(subdialogPath))) {
2251
+ continue;
2252
+ }
2146
2253
  const derivedSupdialogId = meta.assignmentFromSup?.callerDialogId &&
2147
2254
  meta.assignmentFromSup.callerDialogId.trim() !== ''
2148
2255
  ? meta.assignmentFromSup.callerDialogId
@@ -2292,7 +2399,16 @@ async function handleForkDialog(req, res, context, rootIdRaw) {
2292
2399
  const rootId = rootIdRaw.trim();
2293
2400
  const course = typeof courseRaw === 'number' && Number.isFinite(courseRaw) ? Math.floor(courseRaw) : 0;
2294
2401
  const genseq = typeof genseqRaw === 'number' && Number.isFinite(genseqRaw) ? Math.floor(genseqRaw) : 0;
2295
- const status = parseDialogStatusKind(statusRaw) ?? 'running';
2402
+ const parsedStatus = readOptionalPersistableDialogStatus(statusRaw);
2403
+ if (parsedStatus.kind === 'invalid') {
2404
+ const payload = {
2405
+ success: false,
2406
+ error: 'status must be running, completed, or archived',
2407
+ };
2408
+ respondJson(res, 400, payload);
2409
+ return true;
2410
+ }
2411
+ const status = parsedStatus.kind === 'missing' ? 'running' : parsedStatus.status;
2296
2412
  if (rootId === '') {
2297
2413
  const payload = { success: false, error: 'rootId is required' };
2298
2414
  respondJson(res, 400, payload);
@@ -2380,7 +2496,7 @@ async function handleMoveDialogs(req, res, context) {
2380
2496
  respondJson(res, 400, { success: false, error: 'rootId must be a non-empty string' });
2381
2497
  return true;
2382
2498
  }
2383
- const meta = await persistence_1.DialogPersistence.loadRootDialogMetadata(new dialog_1.DialogID(rootId), fromStatus);
2499
+ const meta = await loadRootDialogMetadataForLookup(new dialog_1.DialogID(rootId), fromStatus, 'handleMoveDialogs:root');
2384
2500
  if (!meta) {
2385
2501
  respondJson(res, 404, {
2386
2502
  success: false,
@@ -2417,7 +2533,7 @@ async function handleMoveDialogs(req, res, context) {
2417
2533
  for (const id of ids) {
2418
2534
  if (typeof id !== 'string' || id.trim() === '')
2419
2535
  continue;
2420
- const meta = await persistence_1.DialogPersistence.loadRootDialogMetadata(new dialog_1.DialogID(id), fromStatus);
2536
+ const meta = await loadRootDialogMetadataForLookup(new dialog_1.DialogID(id), fromStatus, 'handleMoveDialogs:task-scan');
2421
2537
  if (!meta)
2422
2538
  continue;
2423
2539
  if (meta.taskDocPath !== taskDocPath)
@@ -2551,7 +2667,7 @@ async function handleDeleteDialog(res, dialog, context) {
2551
2667
  */
2552
2668
  async function handleGetDialog(res, dialog, status) {
2553
2669
  try {
2554
- const metadata = await persistence_1.DialogPersistence.loadDialogMetadata(new dialog_1.DialogID(dialog.selfId, dialog.rootId), status);
2670
+ const metadata = await loadDialogMetadataForLookup(new dialog_1.DialogID(dialog.selfId, dialog.rootId), status, 'handleGetDialog');
2555
2671
  if (!metadata) {
2556
2672
  respondJson(res, 404, { success: false, error: `Dialog not found in ${status}` });
2557
2673
  return true;
@@ -2598,7 +2714,7 @@ async function handleResolveDialogStatus(req, res) {
2598
2714
  const dialogId = new dialog_1.DialogID(selfId, rootId);
2599
2715
  const candidates = ['running', 'completed', 'archived'];
2600
2716
  for (const status of candidates) {
2601
- const metadata = await persistence_1.DialogPersistence.loadDialogMetadata(dialogId, status);
2717
+ const metadata = await loadDialogMetadataForLookup(dialogId, status, 'handleResolveDialogStatus');
2602
2718
  if (!metadata)
2603
2719
  continue;
2604
2720
  respondJson(res, 200, {
@@ -25,6 +25,7 @@ const kernel_driver_1 = require("../llm/kernel-driver");
25
25
  const runtime_2 = require("../llm/kernel-driver/runtime");
26
26
  const log_1 = require("../log");
27
27
  const persistence_1 = require("../persistence");
28
+ const persistence_errors_1 = require("../persistence-errors");
28
29
  const priming_1 = require("../priming");
29
30
  const problems_1 = require("../problems");
30
31
  const reply_special_1 = require("../recovery/reply-special");
@@ -51,12 +52,22 @@ function normalizeDiligencePushMax(value) {
51
52
  return 0;
52
53
  return Math.floor(value);
53
54
  }
54
- function parseDialogStatusKind(raw) {
55
+ function parsePersistableDialogStatus(raw) {
55
56
  if (raw !== 'running' && raw !== 'completed' && raw !== 'archived') {
56
57
  return null;
57
58
  }
58
59
  return raw;
59
60
  }
61
+ function readOptionalPersistableDialogStatus(raw) {
62
+ if (raw === undefined) {
63
+ return { kind: 'missing' };
64
+ }
65
+ const status = parsePersistableDialogStatus(raw);
66
+ if (status === null) {
67
+ return { kind: 'invalid' };
68
+ }
69
+ return { kind: 'value', status };
70
+ }
60
71
  function formatDeclaredDeadSubdialogNotice(language, dialogId, callName) {
61
72
  if (language === 'zh') {
62
73
  switch (callName) {
@@ -318,7 +329,15 @@ async function handleDeclareSubdialogDead(ws, packet) {
318
329
  return;
319
330
  }
320
331
  const dialogIdObj = new dialog_1.DialogID(dialog.selfId, dialog.rootId);
321
- const requestedStatus = parseDialogStatusKind(dialog.status) ?? 'running';
332
+ const requestedStatusInput = readOptionalPersistableDialogStatus(dialog.status);
333
+ if (requestedStatusInput.kind === 'invalid') {
334
+ ws.send(JSON.stringify({
335
+ type: 'error',
336
+ message: 'declare_subdialog_dead requires status running, completed, or archived',
337
+ }));
338
+ return;
339
+ }
340
+ const requestedStatus = requestedStatusInput.kind === 'missing' ? 'running' : requestedStatusInput.status;
322
341
  if (requestedStatus !== 'running') {
323
342
  ws.send(JSON.stringify({
324
343
  type: 'error',
@@ -405,7 +424,15 @@ async function handleSetDiligencePush(ws, packet) {
405
424
  }
406
425
  // Diligence Push is root-dialog state. Even if a subdialog is displayed, always mutate the root.
407
426
  const dialogIdObj = new dialog_1.DialogID(rootId);
408
- const requestedStatus = parseDialogStatusKind(dialog.status) ?? 'running';
427
+ const requestedStatusInput = readOptionalPersistableDialogStatus(dialog.status);
428
+ if (requestedStatusInput.kind === 'invalid') {
429
+ ws.send(JSON.stringify({
430
+ type: 'error',
431
+ message: 'set_diligence_push requires status running, completed, or archived',
432
+ }));
433
+ return;
434
+ }
435
+ const requestedStatus = requestedStatusInput.kind === 'missing' ? 'running' : requestedStatusInput.status;
409
436
  const rootMeta = await persistence_1.DialogPersistence.loadDialogMetadata(dialogIdObj, requestedStatus);
410
437
  if (!rootMeta) {
411
438
  ws.send(JSON.stringify({
@@ -523,7 +550,15 @@ async function handleRefillDiligencePushBudget(ws, packet) {
523
550
  return;
524
551
  }
525
552
  const rootDialogId = new dialog_1.DialogID(rootId);
526
- const requestedStatus = parseDialogStatusKind(dialog.status) ?? 'running';
553
+ const requestedStatusInput = readOptionalPersistableDialogStatus(dialog.status);
554
+ if (requestedStatusInput.kind === 'invalid') {
555
+ ws.send(JSON.stringify({
556
+ type: 'error',
557
+ message: 'refill_diligence_push_budget requires status running, completed, or archived',
558
+ }));
559
+ return;
560
+ }
561
+ const requestedStatus = requestedStatusInput.kind === 'missing' ? 'running' : requestedStatusInput.status;
527
562
  const rootMeta = await persistence_1.DialogPersistence.loadDialogMetadata(rootDialogId, requestedStatus);
528
563
  if (!rootMeta) {
529
564
  ws.send(JSON.stringify({
@@ -742,7 +777,15 @@ async function handleDisplayDialog(ws, packet) {
742
777
  cleanupWsClient(ws);
743
778
  }
744
779
  const dialogIdObj = new dialog_1.DialogID(dialogId, rootDialogId);
745
- const requestedStatus = parseDialogStatusKind(dialogIdent.status) ?? 'running';
780
+ const requestedStatusInput = readOptionalPersistableDialogStatus(dialogIdent.status);
781
+ if (requestedStatusInput.kind === 'invalid') {
782
+ ws.send(JSON.stringify({
783
+ type: 'error',
784
+ message: 'display_dialog requires status running, completed, or archived',
785
+ }));
786
+ return;
787
+ }
788
+ const requestedStatus = requestedStatusInput.kind === 'missing' ? 'running' : requestedStatusInput.status;
746
789
  const dialogState = await persistence_1.DialogPersistence.restoreDialog(dialogIdObj, requestedStatus);
747
790
  const metadata = await persistence_1.DialogPersistence.loadDialogMetadata(dialogIdObj, requestedStatus);
748
791
  if (!dialogState || !metadata) {
@@ -957,7 +1000,15 @@ async function handleDisplayCourse(ws, packet) {
957
1000
  }
958
1001
  const dialogId = new dialog_1.DialogID(dialogIdStr, rootDialogIdStr);
959
1002
  try {
960
- const requestedStatus = parseDialogStatusKind(dialog.status) ?? 'running';
1003
+ const requestedStatusInput = readOptionalPersistableDialogStatus(dialog.status);
1004
+ if (requestedStatusInput.kind === 'invalid') {
1005
+ ws.send(JSON.stringify({
1006
+ type: 'error',
1007
+ message: 'display_course requires status running, completed, or archived',
1008
+ }));
1009
+ return;
1010
+ }
1011
+ const requestedStatus = requestedStatusInput.kind === 'missing' ? 'running' : requestedStatusInput.status;
961
1012
  const metadata = await persistence_1.DialogPersistence.loadDialogMetadata(dialogId, requestedStatus);
962
1013
  if (!metadata) {
963
1014
  log.warn('Metadata not found for display_course', undefined, {
@@ -1190,22 +1241,34 @@ async function handleResumeAll(ws, packet) {
1190
1241
  }
1191
1242
  const dialogIds = await persistence_1.DialogPersistence.listAllDialogIds('running');
1192
1243
  for (const id of dialogIds) {
1193
- const latest = await persistence_1.DialogPersistence.loadDialogLatest(id, 'running');
1194
- if (!(0, dialog_display_state_1.isDialogLatestResumable)(latest))
1195
- continue;
1196
- void (async () => {
1197
- try {
1198
- const dlg = await restoreDialogForDrive(id, 'running');
1199
- await (0, kernel_driver_1.driveDialogStream)(dlg, undefined, true, {
1200
- allowResumeFromInterrupted: true,
1201
- source: 'ws_resume_all',
1202
- reason: 'resume_all',
1203
- });
1204
- }
1205
- catch (err) {
1206
- log.warn('resume_all: failed to resume dialog', err, { dialogId: id.valueOf() });
1244
+ try {
1245
+ // listAllDialogIds() only gives candidate IDs. A malformed dialog can still quarantine itself
1246
+ // during lazy latest lookup here without preventing resume-all for the rest.
1247
+ const latest = await persistence_1.DialogPersistence.loadDialogLatest(id, 'running');
1248
+ if (!(0, dialog_display_state_1.isDialogLatestResumable)(latest))
1249
+ continue;
1250
+ void (async () => {
1251
+ try {
1252
+ const dlg = await restoreDialogForDrive(id, 'running');
1253
+ await (0, kernel_driver_1.driveDialogStream)(dlg, undefined, true, {
1254
+ allowResumeFromInterrupted: true,
1255
+ source: 'ws_resume_all',
1256
+ reason: 'resume_all',
1257
+ });
1258
+ }
1259
+ catch (err) {
1260
+ log.warn('resume_all: failed to resume dialog', err, { dialogId: id.valueOf() });
1261
+ }
1262
+ })();
1263
+ }
1264
+ catch (error) {
1265
+ if (!(0, persistence_errors_1.findDomindsPersistenceFileError)(error)) {
1266
+ throw error;
1207
1267
  }
1208
- })();
1268
+ log.warn('resume_all: skipping malformed dialog during latest lookup', error, {
1269
+ dialogId: id.valueOf(),
1270
+ });
1271
+ }
1209
1272
  }
1210
1273
  emitRunControlRefresh('resume_all');
1211
1274
  }
@@ -1390,6 +1453,44 @@ function setupWebSocketServer(httpServer, clients, auth, serverWorkLanguage, mod
1390
1453
  broadcastDialogsIndexMessage = (msg) => {
1391
1454
  broadcastToClients(msg);
1392
1455
  };
1456
+ (0, persistence_1.setPrepareDialogQuarantineHook)(async ({ rootDialogId, status }) => {
1457
+ if (status !== 'running') {
1458
+ return;
1459
+ }
1460
+ (0, dialog_display_state_1.markRootDialogQuarantining)(rootDialogId);
1461
+ await (0, dialog_display_state_1.forceStopActiveRunsForRootDialog)(rootDialogId);
1462
+ });
1463
+ (0, persistence_1.setFinalizeDialogQuarantineHook)(({ rootDialogId, status, quarantined }) => {
1464
+ if (status !== 'running') {
1465
+ return;
1466
+ }
1467
+ if (quarantined) {
1468
+ (0, dialog_display_state_1.clearRootDialogQuarantiningIfIdle)(rootDialogId);
1469
+ return;
1470
+ }
1471
+ (0, dialog_display_state_1.clearRootDialogQuarantining)(rootDialogId);
1472
+ });
1473
+ (0, persistence_1.setDialogsQuarantinedBroadcaster)((msg) => {
1474
+ broadcastToClients(msg);
1475
+ if (msg.fromStatus !== 'running') {
1476
+ return;
1477
+ }
1478
+ void (0, dialog_display_state_1.getRunControlCountsSnapshot)()
1479
+ .then((counts) => {
1480
+ broadcastToClients({
1481
+ type: 'run_control_counts_evt',
1482
+ proceeding: counts.proceeding,
1483
+ resumable: counts.resumable,
1484
+ timestamp: (0, time_1.formatUnifiedTimestamp)(new Date()),
1485
+ });
1486
+ })
1487
+ .catch((error) => {
1488
+ log.warn('Failed to broadcast run-control counts after dialog quarantine', error, {
1489
+ rootId: msg.rootId,
1490
+ dialogId: msg.dialogId,
1491
+ });
1492
+ });
1493
+ });
1393
1494
  // Broadcast global run-control refresh hints so all clients converge from persisted dialog index.
1394
1495
  broadcastRunControlRefreshMessage = (msg) => {
1395
1496
  broadcastToClients(msg);
@@ -1418,6 +1519,9 @@ function setupWebSocketServer(httpServer, clients, auth, serverWorkLanguage, mod
1418
1519
  (0, team_config_updates_1.stopTeamConfigWatcher)();
1419
1520
  (0, team_config_updates_1.clearTeamConfigBroadcaster)();
1420
1521
  (0, dominds_self_update_1.setDomindsSelfUpdateBroadcaster)(null);
1522
+ (0, persistence_1.setFinalizeDialogQuarantineHook)(null);
1523
+ (0, persistence_1.setPrepareDialogQuarantineHook)(null);
1524
+ (0, persistence_1.setDialogsQuarantinedBroadcaster)(null);
1421
1525
  void wss.close();
1422
1526
  });
1423
1527
  wss.on('connection', (ws, req) => {
@@ -5333,7 +5333,7 @@ async function renderTeamMgmtGuideContent(language, topicsRaw = []) {
5333
5333
  '`model_param_options` 可选:用于记录该 provider 支持的 `.minds/team.yaml model_params` 选项(文档用途)。',
5334
5334
  '`apiQuirks` 可选:写在 `providers.<providerKey>.apiQuirks`,类型是 `string|string[]`。它是 provider 级 transport / 网关兼容开关,用来描述“这个供应商/网关的 API 有非标准行为”,不是 `.minds/team.yaml` 的成员参数,也不是 `model_params`。',
5335
5335
  '使用原则:只有在你确认某个上游网关确实偏离了标准协议,而且 Dominds 已为这个偏差实现了命名 quirk 时才配置;不要把它当作随意调参入口。当前实现里,未知 quirk 值通常不会报错,但也不会带来任何效果。',
5336
- '当前已知示例:OpenAI Responses 包装层支持 `apiQuirks: xcode.best`(或数组里包含它)。它一方面会把供应商额外发出的 `keepalive` 流事件识别为 heartbeat,而不是当作异常事件处理;另一方面也会对该网关特有的“同一对话上下文连续返回 empty response”做 provider-specific failure handling:先做少量临时重试,连续达到阈值后直接放弃,并明确提示应新开对话而不是继续调大普通重试参数。最小示例:\n```yaml\nproviders:\n my_gateway:\n apiType: openai\n baseUrl: https://example.invalid/v1\n apiKeyEnvVar: MY_GATEWAY_API_KEY\n apiQuirks: xcode.best\n models:\n my_model: { name: \"upstream-model-id\" }\n```',
5336
+ '当前已知示例:OpenAI Responses 包装层支持 `apiQuirks: xcode.best`(或数组里包含它)。它一方面会把供应商额外发出的 `keepalive` 流事件识别为 heartbeat,而不是当作异常事件处理;另一方面也会对该网关特有的失败模式做 provider-specific failure handling,包括“同一对话上下文连续返回 empty response”时先做少量临时重试、连续达到阈值后停止自动重试,并明确提示直接继续大概率仍无真实进展,更建议结合真实情况灵活尝试多种新的指令;这类 provider/API retry 状态在同一次 driver 自动续跑链里会继续沿用,不因中途换 course 而自动清零;若当前对话启用了鞭策,driver 也会优先判断是否能按鞭策逻辑直接续跑一次,而不是先落入 stopped,并且一旦 driver 接受这次自动恢复资格,就会立刻记作已消费;以及把网关返回的 HTML 版 502 Bad Gateway 错误页归类为 conservative 策略重试。最小示例:\n```yaml\nproviders:\n my_gateway:\n apiType: openai\n baseUrl: https://example.invalid/v1\n apiKeyEnvVar: MY_GATEWAY_API_KEY\n apiQuirks: xcode.best\n models:\n my_model: { name: \"upstream-model-id\" }\n```',
5337
5337
  '边界提醒:`apiQuirks` 只影响实现里显式消费它的 provider/generator。就当前实现看,至少 OpenAI Responses 路径会读取它;不要假设所有 `apiType` 都支持或需要它。若配置后行为仍异常,应继续检查上游网关文档、抓流事件类型,并结合 `team_mgmt_check_provider(...)` / 运行日志排查。',
5338
5338
  ])
5339
5339
  : fmtHeader('.minds/llm.yaml') +
@@ -5347,7 +5347,7 @@ async function renderTeamMgmtGuideContent(language, topicsRaw = []) {
5347
5347
  'Optional: `model_param_options` documents `.minds/team.yaml model_params` knobs (documentation only).',
5348
5348
  '`apiQuirks` is optional under `providers.<providerKey>.apiQuirks`, with type `string|string[]`. It is a provider-level transport / gateway compatibility switch for non-standard upstream API behavior. It is not a `.minds/team.yaml` member field and not part of `model_params`.',
5349
5349
  'Use it only when you have confirmed that an upstream gateway deviates from the expected protocol and Dominds has an explicitly named quirk for that deviation. Do not treat it as a generic tuning field. In the current implementation, unknown quirk values are usually ignored rather than rejected, so a typo may silently do nothing.',
5350
- 'Known current example: the OpenAI Responses wrapper supports `apiQuirks: xcode.best` (or an array containing it). It not only treats vendor-emitted `keepalive` stream events as heartbeat events instead of unexpected protocol noise, but also applies provider-specific failure handling for repeated empty responses in the same dialog context: a few temporary retries first, then an explicit give-up that tells the user to start a new dialog rather than merely increasing generic retry knobs. Minimal example:\n```yaml\nproviders:\n my_gateway:\n apiType: openai\n baseUrl: https://example.invalid/v1\n apiKeyEnvVar: MY_GATEWAY_API_KEY\n apiQuirks: xcode.best\n models:\n my_model: { name: \"upstream-model-id\" }\n```',
5350
+ 'Known current example: the OpenAI Responses wrapper supports `apiQuirks: xcode.best` (or an array containing it). It not only treats vendor-emitted `keepalive` stream events as heartbeat events instead of unexpected protocol noise, but also applies provider-specific failure handling for gateway-specific failures, including repeated empty responses in the same dialog context (a few temporary retries first, then an explicit stop of automatic retries with a warning that simply continuing is still unlikely to make real progress and that the better next step is to try different fresh instructions based on the real situation; this provider/API retry state intentionally continues across course changes within the same driver auto-continue chain; if Diligence Push is enabled for the dialog, the driver will first see whether it can continue once through that path before falling into stopped, and that recovery budget is considered consumed as soon as the driver accepts that path) and gateway-returned HTML 502 Bad Gateway pages classified into conservative retry. Minimal example:\n```yaml\nproviders:\n my_gateway:\n apiType: openai\n baseUrl: https://example.invalid/v1\n apiKeyEnvVar: MY_GATEWAY_API_KEY\n apiQuirks: xcode.best\n models:\n my_model: { name: \"upstream-model-id\" }\n```',
5351
5351
  'Boundary reminder: `apiQuirks` only affects providers/generators that explicitly read it in code. In the current implementation, at least the OpenAI Responses path consumes it; do not assume every `apiType` supports or needs it. If behavior is still wrong after setting it, continue with upstream gateway docs, raw stream event inspection, and `team_mgmt_check_provider(...)` / runtime logs.',
5352
5352
  ]);
5353
5353
  }