dominds 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/agent-priming.js +2051 -0
  2. package/dist/apps/app-lock-file.js +228 -0
  3. package/dist/apps/assigned-port.js +124 -0
  4. package/dist/apps/enabled-apps.js +472 -7
  5. package/dist/apps/manifest.js +37 -0
  6. package/dist/apps/override-paths.js +19 -6
  7. package/dist/apps/problems.js +43 -0
  8. package/dist/apps/resolution-file.js +370 -0
  9. package/dist/apps/runtime.js +5 -17
  10. package/dist/apps/teammates.js +102 -1
  11. package/dist/cli/disable.js +10 -6
  12. package/dist/cli/enable.js +21 -19
  13. package/dist/cli/install.js +40 -18
  14. package/dist/cli/uninstall.js +6 -6
  15. package/dist/cli/update.js +38 -13
  16. package/dist/dialog.js +5 -0
  17. package/dist/docs/app-constitution.md +85 -18
  18. package/dist/docs/app-constitution.zh.md +86 -21
  19. package/dist/docs/dialog-system.md +1 -1
  20. package/dist/docs/dialog-system.zh.md +1 -1
  21. package/dist/docs/dominds-agent-priming.md +218 -0
  22. package/dist/docs/dominds-agent-priming.zh.md +196 -0
  23. package/dist/docs/drive-logic-context-refactor-plan.zh.md +338 -0
  24. package/dist/docs/keep-going.md +176 -0
  25. package/dist/docs/keep-going.zh.md +162 -0
  26. package/dist/docs/showing-by-doing.md +208 -0
  27. package/dist/docs/showing-by-doing.zh.md +177 -0
  28. package/dist/docs/team-mgmt-toolset.md +482 -0
  29. package/dist/docs/team-mgmt-toolset.zh.md +426 -0
  30. package/dist/llm/defaults.yaml +1 -1
  31. package/dist/llm/driver.js +4093 -0
  32. package/dist/llm/kernel-driver/drive.js +5 -2
  33. package/dist/llm/kernel-driver/flow.js +3 -0
  34. package/dist/minds/promptdocs.js +263 -0
  35. package/dist/problems.js +67 -16
  36. package/dist/server/api-routes.js +333 -0
  37. package/dist/server/prompts-routes.js +545 -0
  38. package/dist/server/server-core.js +4 -0
  39. package/dist/server/websocket-handler.js +17 -0
  40. package/dist/shared/team-mgmt-manual.js +120 -0
  41. package/dist/shared/types/prompts.js +2 -0
  42. package/dist/shared/types/tellask.js +8 -0
  43. package/dist/showing-by-doing.js +1091 -0
  44. package/dist/snippets/README.en.md +3 -0
  45. package/dist/snippets/README.md +4 -0
  46. package/dist/static/assets/{_basePickBy-CF9r08iy.js → _basePickBy-BMCtwrV7.js} +3 -3
  47. package/dist/static/assets/{_basePickBy-CF9r08iy.js.map → _basePickBy-BMCtwrV7.js.map} +1 -1
  48. package/dist/static/assets/{_baseUniq-CxKv0cd4.js → _baseUniq-BuyCgJiA.js} +2 -2
  49. package/dist/static/assets/{_baseUniq-CxKv0cd4.js.map → _baseUniq-BuyCgJiA.js.map} +1 -1
  50. package/dist/static/assets/{arc-C9JyvnlB.js → arc-BDuN8lwA.js} +2 -2
  51. package/dist/static/assets/{arc-C9JyvnlB.js.map → arc-BDuN8lwA.js.map} +1 -1
  52. package/dist/static/assets/{architectureDiagram-VXUJARFQ-CpcUgjHf.js → architectureDiagram-VXUJARFQ-C-ekqGAD.js} +7 -7
  53. package/dist/static/assets/{architectureDiagram-VXUJARFQ-CpcUgjHf.js.map → architectureDiagram-VXUJARFQ-C-ekqGAD.js.map} +1 -1
  54. package/dist/static/assets/{blockDiagram-VD42YOAC-BA9vtmm7.js → blockDiagram-VD42YOAC-CgQiNuuQ.js} +7 -7
  55. package/dist/static/assets/{blockDiagram-VD42YOAC-BA9vtmm7.js.map → blockDiagram-VD42YOAC-CgQiNuuQ.js.map} +1 -1
  56. package/dist/static/assets/{c4Diagram-YG6GDRKO-D49MGNdF.js → c4Diagram-YG6GDRKO-DONC39q-.js} +3 -3
  57. package/dist/static/assets/{c4Diagram-YG6GDRKO-D49MGNdF.js.map → c4Diagram-YG6GDRKO-DONC39q-.js.map} +1 -1
  58. package/dist/static/assets/{channel-B4KzL0Kg.js → channel-CJTFwXIG.js} +2 -2
  59. package/dist/static/assets/{channel-B4KzL0Kg.js.map → channel-CJTFwXIG.js.map} +1 -1
  60. package/dist/static/assets/{chunk-4BX2VUAB-0F-1ayl0.js → chunk-4BX2VUAB-NaIy4uLJ.js} +2 -2
  61. package/dist/static/assets/{chunk-4BX2VUAB-0F-1ayl0.js.map → chunk-4BX2VUAB-NaIy4uLJ.js.map} +1 -1
  62. package/dist/static/assets/{chunk-55IACEB6-Dnl2HDTZ.js → chunk-55IACEB6-JUKI_Ayx.js} +2 -2
  63. package/dist/static/assets/{chunk-55IACEB6-Dnl2HDTZ.js.map → chunk-55IACEB6-JUKI_Ayx.js.map} +1 -1
  64. package/dist/static/assets/{chunk-B4BG7PRW-Bhx5RbkQ.js → chunk-B4BG7PRW-dIswFJDn.js} +5 -5
  65. package/dist/static/assets/{chunk-B4BG7PRW-Bhx5RbkQ.js.map → chunk-B4BG7PRW-dIswFJDn.js.map} +1 -1
  66. package/dist/static/assets/{chunk-DI55MBZ5-EYd1wL3E.js → chunk-DI55MBZ5-DU2b_N30.js} +4 -4
  67. package/dist/static/assets/{chunk-DI55MBZ5-EYd1wL3E.js.map → chunk-DI55MBZ5-DU2b_N30.js.map} +1 -1
  68. package/dist/static/assets/{chunk-FMBD7UC4-DAjkhhUU.js → chunk-FMBD7UC4-BgExcScw.js} +2 -2
  69. package/dist/static/assets/{chunk-FMBD7UC4-DAjkhhUU.js.map → chunk-FMBD7UC4-BgExcScw.js.map} +1 -1
  70. package/dist/static/assets/{chunk-QN33PNHL-CK6TY7IE.js → chunk-QN33PNHL-bitxyqh7.js} +2 -2
  71. package/dist/static/assets/{chunk-QN33PNHL-CK6TY7IE.js.map → chunk-QN33PNHL-bitxyqh7.js.map} +1 -1
  72. package/dist/static/assets/{chunk-QZHKN3VN-CketngiE.js → chunk-QZHKN3VN-Cor8u7DT.js} +2 -2
  73. package/dist/static/assets/{chunk-QZHKN3VN-CketngiE.js.map → chunk-QZHKN3VN-Cor8u7DT.js.map} +1 -1
  74. package/dist/static/assets/{chunk-TZMSLE5B-Bcuvqo45.js → chunk-TZMSLE5B-Aceoxav_.js} +2 -2
  75. package/dist/static/assets/{chunk-TZMSLE5B-Bcuvqo45.js.map → chunk-TZMSLE5B-Aceoxav_.js.map} +1 -1
  76. package/dist/static/assets/{classDiagram-2ON5EDUG-CaP4T3r4.js → classDiagram-2ON5EDUG-D1Q6a8Hg.js} +6 -6
  77. package/dist/static/assets/{classDiagram-2ON5EDUG-CaP4T3r4.js.map → classDiagram-2ON5EDUG-D1Q6a8Hg.js.map} +1 -1
  78. package/dist/static/assets/{classDiagram-v2-WZHVMYZB-CaP4T3r4.js → classDiagram-v2-WZHVMYZB-D1Q6a8Hg.js} +6 -6
  79. package/dist/static/assets/{classDiagram-v2-WZHVMYZB-CaP4T3r4.js.map → classDiagram-v2-WZHVMYZB-D1Q6a8Hg.js.map} +1 -1
  80. package/dist/static/assets/{clone-C-JULvnG.js → clone-MlWbv1V0.js} +2 -2
  81. package/dist/static/assets/{clone-C-JULvnG.js.map → clone-MlWbv1V0.js.map} +1 -1
  82. package/dist/static/assets/{cose-bilkent-S5V4N54A-vXCmi_eC.js → cose-bilkent-S5V4N54A-DWPCXSrn.js} +2 -2
  83. package/dist/static/assets/{cose-bilkent-S5V4N54A-vXCmi_eC.js.map → cose-bilkent-S5V4N54A-DWPCXSrn.js.map} +1 -1
  84. package/dist/static/assets/{dagre-6UL2VRFP-bhGzX6kO.js → dagre-6UL2VRFP-C8ptQ9V3.js} +7 -7
  85. package/dist/static/assets/{dagre-6UL2VRFP-bhGzX6kO.js.map → dagre-6UL2VRFP-C8ptQ9V3.js.map} +1 -1
  86. package/dist/static/assets/{diagram-PSM6KHXK-BUKfmfGk.js → diagram-PSM6KHXK-Bgf1FqkE.js} +8 -8
  87. package/dist/static/assets/{diagram-PSM6KHXK-BUKfmfGk.js.map → diagram-PSM6KHXK-Bgf1FqkE.js.map} +1 -1
  88. package/dist/static/assets/{diagram-QEK2KX5R-DYlq3uFq.js → diagram-QEK2KX5R-BZ5xzofU.js} +7 -7
  89. package/dist/static/assets/{diagram-QEK2KX5R-DYlq3uFq.js.map → diagram-QEK2KX5R-BZ5xzofU.js.map} +1 -1
  90. package/dist/static/assets/{diagram-S2PKOQOG-CjxkLHWG.js → diagram-S2PKOQOG-Dwp47T9I.js} +7 -7
  91. package/dist/static/assets/{diagram-S2PKOQOG-CjxkLHWG.js.map → diagram-S2PKOQOG-Dwp47T9I.js.map} +1 -1
  92. package/dist/static/assets/{erDiagram-Q2GNP2WA-S3hR85On.js → erDiagram-Q2GNP2WA-Cx4weIHl.js} +5 -5
  93. package/dist/static/assets/{erDiagram-Q2GNP2WA-S3hR85On.js.map → erDiagram-Q2GNP2WA-Cx4weIHl.js.map} +1 -1
  94. package/dist/static/assets/{flowDiagram-NV44I4VS-aBmNMuQ0.js → flowDiagram-NV44I4VS-vNUuIeRk.js} +6 -6
  95. package/dist/static/assets/{flowDiagram-NV44I4VS-aBmNMuQ0.js.map → flowDiagram-NV44I4VS-vNUuIeRk.js.map} +1 -1
  96. package/dist/static/assets/{ganttDiagram-JELNMOA3-DJxXaiW1.js → ganttDiagram-JELNMOA3-BEfozJAr.js} +3 -3
  97. package/dist/static/assets/{ganttDiagram-JELNMOA3-DJxXaiW1.js.map → ganttDiagram-JELNMOA3-BEfozJAr.js.map} +1 -1
  98. package/dist/static/assets/{gitGraphDiagram-V2S2FVAM-DEOBCM0G.js → gitGraphDiagram-V2S2FVAM-eHxwc3d9.js} +8 -8
  99. package/dist/static/assets/{gitGraphDiagram-V2S2FVAM-DEOBCM0G.js.map → gitGraphDiagram-V2S2FVAM-eHxwc3d9.js.map} +1 -1
  100. package/dist/static/assets/{graph-DwrKSIE7.js → graph-C6a6uAok.js} +3 -3
  101. package/dist/static/assets/{graph-DwrKSIE7.js.map → graph-C6a6uAok.js.map} +1 -1
  102. package/dist/static/assets/{index-HWTRvE2k.js → index-D3TQbAKh.js} +383 -59
  103. package/dist/static/assets/index-D3TQbAKh.js.map +1 -0
  104. package/dist/static/assets/{infoDiagram-HS3SLOUP-BH9kVuYd.js → infoDiagram-HS3SLOUP-CX0NiId3.js} +6 -6
  105. package/dist/static/assets/{infoDiagram-HS3SLOUP-BH9kVuYd.js.map → infoDiagram-HS3SLOUP-CX0NiId3.js.map} +1 -1
  106. package/dist/static/assets/{journeyDiagram-XKPGCS4Q-Dap7AcjR.js → journeyDiagram-XKPGCS4Q-C1IepPZ-.js} +5 -5
  107. package/dist/static/assets/{journeyDiagram-XKPGCS4Q-Dap7AcjR.js.map → journeyDiagram-XKPGCS4Q-C1IepPZ-.js.map} +1 -1
  108. package/dist/static/assets/{kanban-definition-3W4ZIXB7-4NOl8MEj.js → kanban-definition-3W4ZIXB7-uMNX4Z1W.js} +3 -3
  109. package/dist/static/assets/{kanban-definition-3W4ZIXB7-4NOl8MEj.js.map → kanban-definition-3W4ZIXB7-uMNX4Z1W.js.map} +1 -1
  110. package/dist/static/assets/{layout-D6uIxu1E.js → layout-CpE3kk5z.js} +5 -5
  111. package/dist/static/assets/{layout-D6uIxu1E.js.map → layout-CpE3kk5z.js.map} +1 -1
  112. package/dist/static/assets/{linear-CvBOGQA2.js → linear-DV8laXr9.js} +2 -2
  113. package/dist/static/assets/{linear-CvBOGQA2.js.map → linear-DV8laXr9.js.map} +1 -1
  114. package/dist/static/assets/{mindmap-definition-VGOIOE7T-ugsrLNY5.js → mindmap-definition-VGOIOE7T-CKjgVM9S.js} +4 -4
  115. package/dist/static/assets/{mindmap-definition-VGOIOE7T-ugsrLNY5.js.map → mindmap-definition-VGOIOE7T-CKjgVM9S.js.map} +1 -1
  116. package/dist/static/assets/{pieDiagram-ADFJNKIX-CdVZjM8g.js → pieDiagram-ADFJNKIX-BBonlNyT.js} +8 -8
  117. package/dist/static/assets/{pieDiagram-ADFJNKIX-CdVZjM8g.js.map → pieDiagram-ADFJNKIX-BBonlNyT.js.map} +1 -1
  118. package/dist/static/assets/{quadrantDiagram-AYHSOK5B-A6m5lZKd.js → quadrantDiagram-AYHSOK5B-BTI8HbBu.js} +3 -3
  119. package/dist/static/assets/{quadrantDiagram-AYHSOK5B-A6m5lZKd.js.map → quadrantDiagram-AYHSOK5B-BTI8HbBu.js.map} +1 -1
  120. package/dist/static/assets/{requirementDiagram-UZGBJVZJ-Cac3zSJH.js → requirementDiagram-UZGBJVZJ-ZtSr9Q5R.js} +4 -4
  121. package/dist/static/assets/{requirementDiagram-UZGBJVZJ-Cac3zSJH.js.map → requirementDiagram-UZGBJVZJ-ZtSr9Q5R.js.map} +1 -1
  122. package/dist/static/assets/{sankeyDiagram-TZEHDZUN-DXDdUUl1.js → sankeyDiagram-TZEHDZUN-DibLVGzg.js} +2 -2
  123. package/dist/static/assets/{sankeyDiagram-TZEHDZUN-DXDdUUl1.js.map → sankeyDiagram-TZEHDZUN-DibLVGzg.js.map} +1 -1
  124. package/dist/static/assets/{sequenceDiagram-WL72ISMW-Domsjl5Y.js → sequenceDiagram-WL72ISMW-qXatfzVt.js} +4 -4
  125. package/dist/static/assets/{sequenceDiagram-WL72ISMW-Domsjl5Y.js.map → sequenceDiagram-WL72ISMW-qXatfzVt.js.map} +1 -1
  126. package/dist/static/assets/{stateDiagram-FKZM4ZOC-Bu0lRQK1.js → stateDiagram-FKZM4ZOC-7fgxCQHo.js} +9 -9
  127. package/dist/static/assets/{stateDiagram-FKZM4ZOC-Bu0lRQK1.js.map → stateDiagram-FKZM4ZOC-7fgxCQHo.js.map} +1 -1
  128. package/dist/static/assets/{stateDiagram-v2-4FDKWEC3-D0K-n3ic.js → stateDiagram-v2-4FDKWEC3-DcWlOAnF.js} +5 -5
  129. package/dist/static/assets/{stateDiagram-v2-4FDKWEC3-D0K-n3ic.js.map → stateDiagram-v2-4FDKWEC3-DcWlOAnF.js.map} +1 -1
  130. package/dist/static/assets/{timeline-definition-IT6M3QCI-BGvpddwR.js → timeline-definition-IT6M3QCI-iX2MRdpY.js} +3 -3
  131. package/dist/static/assets/{timeline-definition-IT6M3QCI-BGvpddwR.js.map → timeline-definition-IT6M3QCI-iX2MRdpY.js.map} +1 -1
  132. package/dist/static/assets/{treemap-GDKQZRPO-BoOzOm2j.js → treemap-GDKQZRPO-AVRnyXu1.js} +5 -5
  133. package/dist/static/assets/{treemap-GDKQZRPO-BoOzOm2j.js.map → treemap-GDKQZRPO-AVRnyXu1.js.map} +1 -1
  134. package/dist/static/assets/{xychartDiagram-PRI3JC2R-C_h3_ICR.js → xychartDiagram-PRI3JC2R-DVYEo5aJ.js} +3 -3
  135. package/dist/static/assets/{xychartDiagram-PRI3JC2R-C_h3_ICR.js.map → xychartDiagram-PRI3JC2R-DVYEo5aJ.js.map} +1 -1
  136. package/dist/static/index.html +1 -1
  137. package/dist/team.js +52 -48
  138. package/dist/tellask.js +439 -0
  139. package/dist/tools/context-health.js +177 -0
  140. package/dist/tools/diag.js +583 -0
  141. package/dist/tools/fs.js +194 -68
  142. package/dist/tools/prompts/memory/en/principles.md +13 -5
  143. package/dist/tools/prompts/memory/en/tools.md +11 -36
  144. package/dist/tools/prompts/memory/zh/principles.md +18 -8
  145. package/dist/tools/prompts/memory/zh/tools.md +11 -36
  146. package/dist/tools/team-mgmt.js +3487 -0
  147. package/dist/utils/task-doc.js +236 -0
  148. package/package.json +1 -1
  149. package/dist/static/assets/index-HWTRvE2k.js.map +0 -1
@@ -0,0 +1,4093 @@
1
+ "use strict";
2
+ /**
3
+ * Module: llm/driver
4
+ *
5
+ * Drives dialog streaming end-to-end:
6
+ * - Loads minds/tools, selects generator, streams outputs
7
+ * - Parses tellask blocks (teammate tellasks), handles human prompts
8
+ * - Supports autonomous teammate tellasks: when an agent mentions a teammate (e.g., @teammate), a subdialog is created and driven; the parent logs the initiating assistant bubble and system creation/result, while subdialog conversation stays in the subdialog
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.createSayingEventsReceiver = createSayingEventsReceiver;
45
+ exports.emitThinkingEvents = emitThinkingEvents;
46
+ exports.emitSayingEvents = emitSayingEvents;
47
+ exports.driveDialogStream = driveDialogStream;
48
+ exports.driveDialogStreamCoreV1 = driveDialogStreamCoreV1;
49
+ exports.runBackendDriver = runBackendDriver;
50
+ exports.checkAndReviveSuspendedDialogs = checkAndReviveSuspendedDialogs;
51
+ exports.restoreDialogHierarchy = restoreDialogHierarchy;
52
+ exports.parseTeammateTellask = parseTeammateTellask;
53
+ exports.continueDialogWithHumanResponse = continueDialogWithHumanResponse;
54
+ exports.continueRootDialog = continueRootDialog;
55
+ exports.createSubdialogForSupdialog = createSubdialogForSupdialog;
56
+ exports.supplyResponseToSupdialog = supplyResponseToSupdialog;
57
+ exports.areAllSubdialogsSatisfied = areAllSubdialogsSatisfied;
58
+ exports.incorporateSubdialogResponses = incorporateSubdialogResponses;
59
+ const fs = __importStar(require("fs"));
60
+ const path = __importStar(require("path"));
61
+ const dialog_1 = require("../dialog");
62
+ const util_1 = require("util");
63
+ const dialog_global_registry_1 = require("../dialog-global-registry");
64
+ const dialog_instance_registry_1 = require("../dialog-instance-registry");
65
+ const dialog_run_state_1 = require("../dialog-run-state");
66
+ const evt_registry_1 = require("../evt-registry");
67
+ const log_1 = require("../log");
68
+ const load_1 = require("../minds/load");
69
+ const system_prompt_parts_1 = require("../minds/system-prompt-parts");
70
+ const persistence_1 = require("../persistence");
71
+ const problems_1 = require("../problems");
72
+ const async_fifo_mutex_1 = require("../shared/async-fifo-mutex");
73
+ const diligence_1 = require("../shared/diligence");
74
+ const driver_messages_1 = require("../shared/i18n/driver-messages");
75
+ const runtime_language_1 = require("../shared/runtime-language");
76
+ const id_1 = require("../shared/utils/id");
77
+ const inter_dialog_format_1 = require("../shared/utils/inter-dialog-format");
78
+ const time_1 = require("../shared/utils/time");
79
+ const team_1 = require("../team");
80
+ const tellask_1 = require("../tellask");
81
+ const tool_1 = require("../tool");
82
+ const pending_tellask_reminder_1 = require("../tools/pending-tellask-reminder");
83
+ const id_2 = require("../utils/id");
84
+ const taskdoc_1 = require("../utils/taskdoc");
85
+ const client_1 = require("./client");
86
+ const context_1 = require("./driver-v2/context");
87
+ const registry_1 = require("./gen/registry");
88
+ const tools_projection_1 = require("./tools-projection");
89
+ const DEFAULT_KEEP_GOING_MAX_NUM_PROMPTS = diligence_1.DEFAULT_DILIGENCE_PUSH_MAX;
90
+ function isNodeErrorWithCode(error) {
91
+ return error instanceof Error && 'code' in error;
92
+ }
93
+ function resolveMemberDiligencePushMax(team, agentId) {
94
+ const member = team.getMember(agentId);
95
+ if (member && member.diligence_push_max !== undefined) {
96
+ return member.diligence_push_max;
97
+ }
98
+ return DEFAULT_KEEP_GOING_MAX_NUM_PROMPTS;
99
+ }
100
+ async function syncPendingTellaskReminderBestEffort(dlg, where) {
101
+ try {
102
+ const changed = await (0, pending_tellask_reminder_1.syncPendingTellaskReminderState)(dlg);
103
+ if (!changed)
104
+ return;
105
+ await dlg.processReminderUpdates();
106
+ }
107
+ catch (err) {
108
+ log_1.log.warn('Failed to sync pending tellask reminder', {
109
+ where,
110
+ dialogId: dlg.id.selfId,
111
+ rootId: dlg.id.rootId,
112
+ error: err instanceof Error ? err.message : String(err),
113
+ });
114
+ }
115
+ }
116
+ function stripMarkdownFrontmatter(raw) {
117
+ // We no longer honor frontmatter config in diligence files.
118
+ // If frontmatter exists, strip it to preserve backward compatibility with old files.
119
+ const match = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
120
+ return match ? (match[1] ?? '') : raw;
121
+ }
122
+ async function resolveRtwsDiligenceConfig(workLanguage) {
123
+ const langSpecificPath = path.resolve(process.cwd(), '.minds', `diligence.${workLanguage}.md`);
124
+ const genericPath = path.resolve(process.cwd(), '.minds', 'diligence.md');
125
+ async function resolveFromFile(filePath) {
126
+ let raw;
127
+ try {
128
+ raw = await fs.promises.readFile(filePath, 'utf-8');
129
+ }
130
+ catch (error) {
131
+ if (isNodeErrorWithCode(error) && error.code === 'ENOENT') {
132
+ return null;
133
+ }
134
+ log_1.log.warn('Failed to read rtws diligence file; falling back to built-in defaults', error, {
135
+ filePath,
136
+ });
137
+ return {
138
+ kind: 'enabled',
139
+ diligenceText: diligence_1.DILIGENCE_FALLBACK_TEXT[workLanguage],
140
+ };
141
+ }
142
+ const trimmed = raw.trim();
143
+ // Existing empty file explicitly disables Diligence Push.
144
+ if (trimmed === '') {
145
+ return { kind: 'disabled', reason: 'empty_file' };
146
+ }
147
+ const bodyTrimmed = stripMarkdownFrontmatter(raw).trim();
148
+ if (bodyTrimmed === '') {
149
+ return { kind: 'disabled', reason: 'empty_body' };
150
+ }
151
+ return { kind: 'enabled', diligenceText: bodyTrimmed };
152
+ }
153
+ const langSpecific = await resolveFromFile(langSpecificPath);
154
+ if (langSpecific) {
155
+ return langSpecific;
156
+ }
157
+ const generic = await resolveFromFile(genericPath);
158
+ if (generic) {
159
+ return generic;
160
+ }
161
+ // No diligence file found: use built-in prompt + default max.
162
+ return {
163
+ kind: 'enabled',
164
+ diligenceText: diligence_1.DILIGENCE_FALLBACK_TEXT[workLanguage],
165
+ };
166
+ }
167
+ async function maybePrepareDiligenceAutoContinuePrompt(options) {
168
+ if (!options.isRootDialog) {
169
+ return { kind: 'disabled', nextRemainingBudget: options.remainingBudget };
170
+ }
171
+ if (options.dlg.disableDiligencePush || options.suppressDiligencePush === true) {
172
+ const normalizedRemaining = typeof options.remainingBudget === 'number' && Number.isFinite(options.remainingBudget)
173
+ ? Math.max(0, Math.floor(options.remainingBudget))
174
+ : 0;
175
+ return { kind: 'disabled', nextRemainingBudget: normalizedRemaining };
176
+ }
177
+ const resolved = await resolveRtwsDiligenceConfig((0, runtime_language_1.getWorkLanguage)());
178
+ if (resolved.kind === 'disabled') {
179
+ return { kind: 'disabled', nextRemainingBudget: options.remainingBudget };
180
+ }
181
+ const maxInjectCount = typeof options.diligencePushMax === 'number' && Number.isFinite(options.diligencePushMax)
182
+ ? Math.floor(options.diligencePushMax)
183
+ : 0;
184
+ const normalizedRemaining = typeof options.remainingBudget === 'number' && Number.isFinite(options.remainingBudget)
185
+ ? Math.max(0, Math.floor(options.remainingBudget))
186
+ : 0;
187
+ // When max <= 0, Diligence Push is disabled by config, but can be manually refilled via UI.
188
+ if (maxInjectCount < 1) {
189
+ if (normalizedRemaining < 1) {
190
+ return { kind: 'disabled', nextRemainingBudget: 0 };
191
+ }
192
+ const prompt = {
193
+ content: resolved.diligenceText,
194
+ msgId: (0, id_1.generateShortId)(),
195
+ grammar: 'markdown',
196
+ origin: 'diligence_push',
197
+ persistMode: 'persist',
198
+ };
199
+ return {
200
+ kind: 'prompt',
201
+ prompt,
202
+ maxInjectCount: 0,
203
+ nextRemainingBudget: normalizedRemaining - 1,
204
+ };
205
+ }
206
+ const currentRemaining = Math.min(normalizedRemaining, maxInjectCount);
207
+ if (currentRemaining < 1) {
208
+ return { kind: 'budget_exhausted', maxInjectCount, nextRemainingBudget: 0 };
209
+ }
210
+ const prompt = {
211
+ content: resolved.diligenceText,
212
+ msgId: (0, id_1.generateShortId)(),
213
+ grammar: 'markdown',
214
+ origin: 'diligence_push',
215
+ persistMode: 'persist',
216
+ };
217
+ return {
218
+ kind: 'prompt',
219
+ prompt,
220
+ maxInjectCount,
221
+ nextRemainingBudget: currentRemaining - 1,
222
+ };
223
+ }
224
+ async function suspendForKeepGoingBudgetExhausted(options) {
225
+ const { dlg, maxInjectCount } = options;
226
+ const questionId = `q4h-${(0, id_2.generateDialogID)()}`;
227
+ const language = dlg.getLastUserLanguageCode();
228
+ const question = {
229
+ id: questionId,
230
+ tellaskHead: '@human',
231
+ bodyContent: (0, driver_messages_1.formatQ4HDiligencePushBudgetExhausted)(language, { maxInjectCount }),
232
+ askedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
233
+ callSiteRef: {
234
+ course: dlg.currentCourse,
235
+ messageIndex: dlg.msgs.length,
236
+ },
237
+ };
238
+ await persistence_1.DialogPersistence.appendQuestion4HumanState(dlg.id, question);
239
+ const newQuestionEvent = {
240
+ type: 'new_q4h_asked',
241
+ question: {
242
+ id: question.id,
243
+ selfId: dlg.id.selfId,
244
+ tellaskHead: question.tellaskHead,
245
+ bodyContent: question.bodyContent,
246
+ askedAt: question.askedAt,
247
+ callId: question.callId,
248
+ callSiteRef: question.callSiteRef,
249
+ rootId: dlg.id.rootId,
250
+ agentId: dlg.agentId,
251
+ taskDocPath: dlg.taskDocPath,
252
+ },
253
+ };
254
+ (0, evt_registry_1.postDialogEvent)(dlg, newQuestionEvent);
255
+ }
256
+ function showErrorToAi(err) {
257
+ try {
258
+ if (err instanceof Error) {
259
+ return `${err.name}: ${err.message}${err.stack ? `\n${err.stack}` : ''}`;
260
+ }
261
+ if (typeof err === 'string') {
262
+ const s = err.trim();
263
+ return s.length > 500 ? s.slice(0, 497) + '...' : s;
264
+ }
265
+ return (0, util_1.inspect)(err, { depth: 5, breakLength: 120, compact: false, sorted: true });
266
+ }
267
+ catch (fallbackErr) {
268
+ return `Unknown error of type ${typeof err}`;
269
+ }
270
+ }
271
+ class DialogInterruptedError extends Error {
272
+ constructor(reason) {
273
+ super('Dialog interrupted');
274
+ this.reason = reason;
275
+ }
276
+ }
277
+ function throwIfAborted(abortSignal, dlgId) {
278
+ if (!abortSignal?.aborted)
279
+ return;
280
+ const stopRequested = (0, dialog_run_state_1.getStopRequestedReason)(dlgId);
281
+ if (stopRequested === 'emergency_stop') {
282
+ throw new DialogInterruptedError({ kind: 'emergency_stop' });
283
+ }
284
+ if (stopRequested === 'user_stop') {
285
+ throw new DialogInterruptedError({ kind: 'user_stop' });
286
+ }
287
+ throw new DialogInterruptedError({ kind: 'system_stop', detail: 'Aborted.' });
288
+ }
289
+ /**
290
+ * Validate streaming configuration for a team member.
291
+ * Streaming supports function tools; no restrictions to enforce here.
292
+ */
293
+ function validateStreamingConfiguration(_agent, _agentTools) { }
294
+ function isPlainObject(value) {
295
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
296
+ }
297
+ function validateFuncToolArguments(tool, rawArgs) {
298
+ if (!isPlainObject(rawArgs)) {
299
+ return { ok: false, error: 'Arguments must be an object' };
300
+ }
301
+ if (tool.argsValidation === 'passthrough') {
302
+ return { ok: true, args: rawArgs };
303
+ }
304
+ const validation = (0, tool_1.validateArgs)(tool.parameters, rawArgs);
305
+ return validation.ok
306
+ ? { ok: true, args: rawArgs }
307
+ : { ok: false, error: validation.error };
308
+ }
309
+ function classifyLlmFailure(err) {
310
+ const fallbackMessage = err instanceof Error
311
+ ? err.message || err.name
312
+ : typeof err === 'string'
313
+ ? err
314
+ : JSON.stringify(err);
315
+ if (err instanceof Error && err.message === 'AbortError') {
316
+ return { kind: 'fatal', message: 'Aborted.' };
317
+ }
318
+ {
319
+ const msg = err instanceof Error
320
+ ? err.message && err.message.length > 0
321
+ ? err.message
322
+ : err.name
323
+ : typeof err === 'string'
324
+ ? err
325
+ : undefined;
326
+ if (typeof msg === 'string' && msg.length > 0) {
327
+ const lower = msg.toLowerCase();
328
+ if (lower.includes('fetch failed') || lower.includes('socket hang up')) {
329
+ return { kind: 'retriable', message: msg };
330
+ }
331
+ if (lower.includes('terminated')) {
332
+ return { kind: 'retriable', message: msg };
333
+ }
334
+ if (lower.includes('timeout') ||
335
+ lower.includes('timed out') ||
336
+ lower.includes('rate limit')) {
337
+ return { kind: 'retriable', message: msg };
338
+ }
339
+ }
340
+ }
341
+ if (isPlainObject(err)) {
342
+ const status = 'status' in err && typeof err.status === 'number'
343
+ ? err.status
344
+ : 'statusCode' in err && typeof err.statusCode === 'number'
345
+ ? err.statusCode
346
+ : undefined;
347
+ const code = 'code' in err && typeof err.code === 'string'
348
+ ? err.code
349
+ : 'errno' in err && typeof err.errno === 'string'
350
+ ? err.errno
351
+ : undefined;
352
+ const msg = 'message' in err && typeof err.message === 'string' && err.message.length > 0
353
+ ? err.message
354
+ : fallbackMessage;
355
+ if (typeof status === 'number') {
356
+ if (status === 408 || status === 429 || status >= 500) {
357
+ return { kind: 'retriable', status, message: msg };
358
+ }
359
+ if (status >= 400 && status < 500) {
360
+ return { kind: 'rejected', status, message: msg };
361
+ }
362
+ }
363
+ if (typeof code === 'string') {
364
+ const retriableCodes = new Set([
365
+ 'ETIMEDOUT',
366
+ 'ECONNRESET',
367
+ 'ECONNREFUSED',
368
+ 'EAI_AGAIN',
369
+ 'ENOTFOUND',
370
+ 'ENETUNREACH',
371
+ 'EHOSTUNREACH',
372
+ // undici / Node.js fetch
373
+ 'UND_ERR_CONNECT_TIMEOUT',
374
+ 'UND_ERR_HEADERS_TIMEOUT',
375
+ 'UND_ERR_BODY_TIMEOUT',
376
+ 'UND_ERR_SOCKET',
377
+ ]);
378
+ if (retriableCodes.has(code)) {
379
+ return { kind: 'retriable', code, message: msg };
380
+ }
381
+ }
382
+ const lower = msg.toLowerCase();
383
+ if (lower.includes('fetch failed') || lower.includes('socket hang up')) {
384
+ return { kind: 'retriable', message: msg };
385
+ }
386
+ if (lower.includes('terminated')) {
387
+ return { kind: 'retriable', message: msg };
388
+ }
389
+ if (lower.includes('timeout') || lower.includes('timed out') || lower.includes('rate limit')) {
390
+ return { kind: 'retriable', message: msg };
391
+ }
392
+ }
393
+ return { kind: 'fatal', message: fallbackMessage };
394
+ }
395
+ async function sleepWithAbort(ms, abortSignal) {
396
+ if (abortSignal?.aborted) {
397
+ throw new Error('AbortError');
398
+ }
399
+ await new Promise((resolve) => setTimeout(resolve, ms));
400
+ if (abortSignal?.aborted) {
401
+ throw new Error('AbortError');
402
+ }
403
+ }
404
+ async function runLlmRequestWithRetry(params) {
405
+ const providerProblemId = `llm/provider_rejected/${params.dlg.id.valueOf()}`;
406
+ for (let attempt = 0; attempt <= params.maxRetries; attempt++) {
407
+ try {
408
+ const res = await params.doRequest();
409
+ (0, problems_1.removeProblem)(providerProblemId);
410
+ return res;
411
+ }
412
+ catch (err) {
413
+ if (params.abortSignal?.aborted) {
414
+ throw err;
415
+ }
416
+ const failure = classifyLlmFailure(err);
417
+ const detail = (0, log_1.extractErrorDetails)(err).message;
418
+ if (failure.kind === 'rejected') {
419
+ (0, problems_1.upsertProblem)({
420
+ kind: 'llm_provider_rejected_request',
421
+ source: 'llm',
422
+ id: providerProblemId,
423
+ severity: 'error',
424
+ timestamp: (0, time_1.formatUnifiedTimestamp)(new Date()),
425
+ message: `LLM provider rejected the request`,
426
+ detail: {
427
+ dialogId: params.dlg.id.valueOf(),
428
+ provider: params.provider,
429
+ errorText: detail,
430
+ },
431
+ });
432
+ try {
433
+ await params.dlg.streamError(detail);
434
+ }
435
+ catch (_emitErr) {
436
+ // best-effort
437
+ }
438
+ throw new DialogInterruptedError({
439
+ kind: 'system_stop',
440
+ detail: `Provider '${params.provider}' rejected the request: ${failure.message}`,
441
+ });
442
+ }
443
+ const canRetry = failure.kind === 'retriable' && params.canRetry();
444
+ const isLastAttempt = attempt >= params.maxRetries;
445
+ if (!canRetry || isLastAttempt) {
446
+ try {
447
+ await params.dlg.streamError(detail);
448
+ }
449
+ catch (_emitErr) {
450
+ // best-effort
451
+ }
452
+ throw new DialogInterruptedError({
453
+ kind: 'system_stop',
454
+ detail: canRetry
455
+ ? `LLM failed after retries: ${failure.message}`
456
+ : `LLM failed: ${failure.message}`,
457
+ });
458
+ }
459
+ // Exponential backoff with cap. (No jitter for determinism.)
460
+ // We intentionally use a larger cap because transient provider termination errors often
461
+ // recover only after a few seconds.
462
+ const backoffMs = Math.min(30000, 1000 * 2 ** attempt);
463
+ log_1.log.warn(`Retrying LLM request after retriable error`, {
464
+ provider: params.provider,
465
+ attempt: attempt + 1,
466
+ backoffMs,
467
+ failure,
468
+ });
469
+ await sleepWithAbort(backoffMs, params.abortSignal);
470
+ continue;
471
+ }
472
+ }
473
+ throw new DialogInterruptedError({ kind: 'system_stop', detail: 'LLM failed.' });
474
+ }
475
+ function resolveModelContextLimitTokens(modelInfo) {
476
+ if (modelInfo &&
477
+ typeof modelInfo.context_length === 'number' &&
478
+ Number.isFinite(modelInfo.context_length)) {
479
+ const n = Math.floor(modelInfo.context_length);
480
+ return n > 0 ? n : null;
481
+ }
482
+ if (modelInfo &&
483
+ typeof modelInfo.input_length === 'number' &&
484
+ Number.isFinite(modelInfo.input_length)) {
485
+ const n = Math.floor(modelInfo.input_length);
486
+ return n > 0 ? n : null;
487
+ }
488
+ return null;
489
+ }
490
+ function resolveEffectiveOptimalMaxTokens(args) {
491
+ const configuredOptimal = args.modelInfo &&
492
+ typeof args.modelInfo.optimal_max_tokens === 'number' &&
493
+ Number.isFinite(args.modelInfo.optimal_max_tokens)
494
+ ? Math.floor(args.modelInfo.optimal_max_tokens)
495
+ : undefined;
496
+ const optimalMaxTokensConfigured = configuredOptimal !== undefined && configuredOptimal > 0 ? configuredOptimal : undefined;
497
+ const configuredCritical = args.modelInfo &&
498
+ typeof args.modelInfo.critical_max_tokens === 'number' &&
499
+ Number.isFinite(args.modelInfo.critical_max_tokens)
500
+ ? Math.floor(args.modelInfo.critical_max_tokens)
501
+ : undefined;
502
+ const criticalMaxTokensConfigured = configuredCritical !== undefined && configuredCritical > 0 ? configuredCritical : undefined;
503
+ // Default threshold (when not configured): 100K.
504
+ const defaultOptimal = 100000;
505
+ const effectiveOptimalMaxTokens = optimalMaxTokensConfigured !== undefined ? optimalMaxTokensConfigured : defaultOptimal;
506
+ // Default threshold (when not configured): 90% of hard max.
507
+ const defaultCritical = Math.max(1, Math.floor(args.modelContextLimitTokens * 0.9));
508
+ const effectiveCriticalMaxTokens = criticalMaxTokensConfigured !== undefined ? criticalMaxTokensConfigured : defaultCritical;
509
+ return {
510
+ effectiveOptimalMaxTokens,
511
+ optimalMaxTokensConfigured,
512
+ effectiveCriticalMaxTokens,
513
+ criticalMaxTokensConfigured,
514
+ };
515
+ }
516
+ function computeContextHealthSnapshot(args) {
517
+ const modelInfo = args.providerCfg.models[args.model];
518
+ const modelContextWindowText = modelInfo && typeof modelInfo.context_window === 'string'
519
+ ? modelInfo.context_window
520
+ : undefined;
521
+ const modelContextLimitTokens = resolveModelContextLimitTokens(modelInfo);
522
+ if (modelContextLimitTokens === null) {
523
+ return { kind: 'unavailable', reason: 'model_limit_unavailable', modelContextWindowText };
524
+ }
525
+ const { effectiveOptimalMaxTokens, optimalMaxTokensConfigured, effectiveCriticalMaxTokens, criticalMaxTokensConfigured, } = resolveEffectiveOptimalMaxTokens({
526
+ modelInfo,
527
+ modelContextLimitTokens,
528
+ });
529
+ if (args.usage.kind !== 'available') {
530
+ return {
531
+ kind: 'unavailable',
532
+ reason: 'usage_unavailable',
533
+ modelContextWindowText,
534
+ modelContextLimitTokens,
535
+ effectiveOptimalMaxTokens,
536
+ optimalMaxTokensConfigured,
537
+ effectiveCriticalMaxTokens,
538
+ criticalMaxTokensConfigured,
539
+ };
540
+ }
541
+ const hardUtil = args.usage.promptTokens / modelContextLimitTokens;
542
+ const optimalUtil = args.usage.promptTokens / effectiveOptimalMaxTokens;
543
+ const level = args.usage.promptTokens > effectiveCriticalMaxTokens
544
+ ? 'critical'
545
+ : args.usage.promptTokens > effectiveOptimalMaxTokens
546
+ ? 'caution'
547
+ : 'healthy';
548
+ return {
549
+ kind: 'available',
550
+ promptTokens: args.usage.promptTokens,
551
+ completionTokens: args.usage.completionTokens,
552
+ totalTokens: args.usage.totalTokens,
553
+ modelContextWindowText,
554
+ modelContextLimitTokens,
555
+ effectiveOptimalMaxTokens,
556
+ optimalMaxTokensConfigured,
557
+ effectiveCriticalMaxTokens,
558
+ criticalMaxTokensConfigured,
559
+ hardUtil,
560
+ optimalUtil,
561
+ level,
562
+ };
563
+ }
564
+ const contextHealthV3StateByDialogKey = new Map();
565
+ function getContextHealthV3State(dlg) {
566
+ const key = dlg.id.key();
567
+ const existing = contextHealthV3StateByDialogKey.get(key);
568
+ if (existing)
569
+ return existing;
570
+ const created = {};
571
+ contextHealthV3StateByDialogKey.set(key, created);
572
+ return created;
573
+ }
574
+ function resetContextHealthV3State(dlg) {
575
+ contextHealthV3StateByDialogKey.delete(dlg.id.key());
576
+ }
577
+ const defaultCautionHardCadenceGenerations = 10;
578
+ const defaultCriticalCountdownGenerations = 5;
579
+ function shouldInjectCautionRemediationGuide(args) {
580
+ const { dlg } = args;
581
+ const state = getContextHealthV3State(dlg);
582
+ const modelInfo = args.providerCfg.models[args.model];
583
+ const cadence = modelInfo &&
584
+ typeof modelInfo.caution_remediation_cadence_generations === 'number' &&
585
+ Number.isFinite(modelInfo.caution_remediation_cadence_generations)
586
+ ? Math.floor(modelInfo.caution_remediation_cadence_generations)
587
+ : undefined;
588
+ const effectiveCadence = typeof cadence === 'number' && Number.isFinite(cadence) && cadence > 0
589
+ ? Math.floor(cadence)
590
+ : defaultCautionHardCadenceGenerations;
591
+ const genSeq = dlg.activeGenSeq;
592
+ if (genSeq === undefined)
593
+ return true;
594
+ const lastInjected = state.lastCautionGuideInjectedAtGenSeq;
595
+ if (lastInjected === undefined)
596
+ return true;
597
+ return genSeq - lastInjected >= effectiveCadence;
598
+ }
599
+ async function applyContextHealthV3Remediation(args) {
600
+ const { dlg } = args;
601
+ const snapshot = dlg.getLastContextHealth();
602
+ if (!snapshot || snapshot.kind !== 'available') {
603
+ resetContextHealthV3State(dlg);
604
+ return { kind: 'proceed', ctxMsgs: args.ctxMsgs };
605
+ }
606
+ if (snapshot.level === 'healthy') {
607
+ resetContextHealthV3State(dlg);
608
+ return { kind: 'proceed', ctxMsgs: args.ctxMsgs };
609
+ }
610
+ if (snapshot.level === 'caution') {
611
+ const state = getContextHealthV3State(dlg);
612
+ if (state.lastSeenLevel !== 'caution') {
613
+ state.lastSeenLevel = 'caution';
614
+ state.criticalCountdownRemaining = undefined;
615
+ }
616
+ if (!shouldInjectCautionRemediationGuide({
617
+ dlg,
618
+ providerCfg: args.providerCfg,
619
+ model: args.model,
620
+ })) {
621
+ return { kind: 'proceed', ctxMsgs: args.ctxMsgs };
622
+ }
623
+ // Caution remediation at cadence (including the entry injection): require reminder curation
624
+ // (no forced clear_mind).
625
+ const activeGenSeq = dlg.activeGenSeqOrUndefined;
626
+ if (activeGenSeq === undefined) {
627
+ return { kind: 'proceed', ctxMsgs: args.ctxMsgs };
628
+ }
629
+ const guideText = (0, driver_messages_1.formatAgentFacingContextHealthV3RemediationGuide)((0, runtime_language_1.getWorkLanguage)(), {
630
+ kind: 'caution',
631
+ mode: 'soft',
632
+ });
633
+ state.lastCautionGuideInjectedAtGenSeq = activeGenSeq;
634
+ // Prefer a recorded user prompt (visible in UI) when there isn't already a user prompt
635
+ // in this generation.
636
+ if (!args.hadUserPromptThisGen) {
637
+ const msgId = (0, id_1.generateShortId)();
638
+ const userLanguageCode = (0, runtime_language_1.getWorkLanguage)();
639
+ const promptMsg = {
640
+ type: 'prompting_msg',
641
+ role: 'user',
642
+ genseq: activeGenSeq,
643
+ msgId,
644
+ grammar: 'markdown',
645
+ content: guideText,
646
+ };
647
+ await dlg.addChatMessages(promptMsg);
648
+ await dlg.persistUserMessage(guideText, msgId, 'markdown', userLanguageCode);
649
+ await emitUserMarkdown(dlg, guideText);
650
+ try {
651
+ (0, evt_registry_1.postDialogEvent)(dlg, {
652
+ type: 'end_of_user_saying_evt',
653
+ course: dlg.currentCourse,
654
+ genseq: activeGenSeq,
655
+ msgId,
656
+ content: guideText,
657
+ grammar: 'markdown',
658
+ userLanguageCode,
659
+ });
660
+ }
661
+ catch (err) {
662
+ log_1.log.warn('Failed to emit end_of_user_saying_evt for caution guide prompt', err);
663
+ }
664
+ return { kind: 'proceed', ctxMsgs: [...args.ctxMsgs, promptMsg] };
665
+ }
666
+ // Fallback: still guide the LLM even if we cannot safely emit a second user prompt for UI.
667
+ const guide = { type: 'environment_msg', role: 'user', content: guideText };
668
+ return { kind: 'proceed', ctxMsgs: [...args.ctxMsgs, guide] };
669
+ }
670
+ if (snapshot.level === 'critical') {
671
+ const state = getContextHealthV3State(dlg);
672
+ if (state.lastSeenLevel !== 'critical') {
673
+ state.lastSeenLevel = 'critical';
674
+ state.criticalCountdownRemaining = defaultCriticalCountdownGenerations;
675
+ }
676
+ const promptsBeforeAutoClear = typeof state.criticalCountdownRemaining === 'number' &&
677
+ Number.isFinite(state.criticalCountdownRemaining)
678
+ ? Math.floor(state.criticalCountdownRemaining)
679
+ : defaultCriticalCountdownGenerations;
680
+ if (promptsBeforeAutoClear <= 0) {
681
+ // Countdown exhausted: force-start a new course directly. Do not simulate an LLM tool call
682
+ // because this is system remediation, not agent action.
683
+ const language = (0, runtime_language_1.getWorkLanguage)();
684
+ const newCoursePrompt = language === 'zh'
685
+ ? '系统因上下文已告急(critical)而自动开启新一程对话,请继续推进任务。'
686
+ : 'System auto-started a new dialog course because context health is critical. Please continue the task.';
687
+ await dlg.startNewCourse(newCoursePrompt);
688
+ // Context health snapshot is inherently tied to the previous prompt/context. After clearing,
689
+ // invalidate it so the next generation can recompute without stale remediation.
690
+ dlg.setLastContextHealth({ kind: 'unavailable', reason: 'usage_unavailable' });
691
+ resetContextHealthV3State(dlg);
692
+ const nextPrompt = resolveUpNextPrompt(dlg);
693
+ return { kind: 'continue', nextPrompt };
694
+ }
695
+ const guideText = (0, driver_messages_1.formatAgentFacingContextHealthV3RemediationGuide)((0, runtime_language_1.getWorkLanguage)(), {
696
+ kind: 'critical',
697
+ mode: 'countdown',
698
+ promptsRemainingAfterThis: promptsBeforeAutoClear - 1,
699
+ promptsTotal: defaultCriticalCountdownGenerations,
700
+ });
701
+ state.criticalCountdownRemaining = promptsBeforeAutoClear - 1;
702
+ // Prefer a recorded prompt (visible in UI as user prompt) when there isn't already a
703
+ // user prompt in this generation.
704
+ if (!args.hadUserPromptThisGen) {
705
+ const msgId = (0, id_1.generateShortId)();
706
+ const userLanguageCode = (0, runtime_language_1.getWorkLanguage)();
707
+ const promptMsg = {
708
+ type: 'prompting_msg',
709
+ role: 'user',
710
+ genseq: dlg.activeGenSeq,
711
+ msgId,
712
+ grammar: 'markdown',
713
+ content: guideText,
714
+ };
715
+ await dlg.addChatMessages(promptMsg);
716
+ await dlg.persistUserMessage(guideText, msgId, 'markdown', userLanguageCode);
717
+ await emitUserMarkdown(dlg, guideText);
718
+ try {
719
+ (0, evt_registry_1.postDialogEvent)(dlg, {
720
+ type: 'end_of_user_saying_evt',
721
+ course: dlg.currentCourse,
722
+ genseq: dlg.activeGenSeq,
723
+ msgId,
724
+ content: guideText,
725
+ grammar: 'markdown',
726
+ userLanguageCode,
727
+ });
728
+ }
729
+ catch (err) {
730
+ log_1.log.warn('Failed to emit end_of_user_saying_evt for critical countdown prompt', err);
731
+ }
732
+ return { kind: 'proceed', ctxMsgs: [...args.ctxMsgs, promptMsg] };
733
+ }
734
+ // Fallback: still guide the LLM even if we cannot safely emit a second user prompt for UI.
735
+ const guide = {
736
+ type: 'environment_msg',
737
+ role: 'user',
738
+ content: guideText,
739
+ };
740
+ return { kind: 'proceed', ctxMsgs: [...args.ctxMsgs, guide] };
741
+ }
742
+ const _exhaustive = snapshot.level;
743
+ return _exhaustive;
744
+ }
745
+ // === UNIFIED STREAMING HANDLERS ===
746
+ /**
747
+ * Create a TellaskEventsReceiver for unified saying event emission.
748
+ * Handles tellask call blocks and markdown using TellaskStreamParser.
749
+ * Used by both streaming and non-streaming modes.
750
+ */
751
+ function createSayingEventsReceiver(dlg) {
752
+ return {
753
+ markdownStart: async () => {
754
+ await dlg.markdownStart();
755
+ },
756
+ markdownChunk: async (chunk) => {
757
+ await dlg.markdownChunk(chunk);
758
+ },
759
+ markdownFinish: async () => {
760
+ await dlg.markdownFinish();
761
+ },
762
+ callStart: async (validation) => {
763
+ await dlg.callingStart(validation);
764
+ },
765
+ callHeadLineChunk: async (chunk) => {
766
+ await dlg.callingHeadlineChunk(chunk);
767
+ },
768
+ callHeadLineFinish: async () => {
769
+ await dlg.callingHeadlineFinish();
770
+ },
771
+ tellaskBodyStart: async () => {
772
+ await dlg.callingBodyStart();
773
+ },
774
+ tellaskBodyChunk: async (chunk) => {
775
+ await dlg.callingBodyChunk(chunk);
776
+ },
777
+ tellaskBodyFinish: async () => {
778
+ await dlg.callingBodyFinish();
779
+ },
780
+ callFinish: async (call, _upstreamEndOffset) => {
781
+ await dlg.callingFinish(call.callId);
782
+ },
783
+ };
784
+ }
785
+ /**
786
+ * Emit thinking events for a thinking message (non-streaming mode).
787
+ * Emits thinkingStart, thinkingChunk with full content, and thinkingFinish.
788
+ * Returns the extracted signature for caller to use.
789
+ */
790
+ async function emitThinkingEvents(dlg, content) {
791
+ if (!content.trim())
792
+ return undefined;
793
+ await dlg.thinkingStart();
794
+ await dlg.thinkingChunk(content);
795
+ await dlg.thinkingFinish();
796
+ // Extract and return signature for caller to use
797
+ const signatureMatch = content.match(/<thinking[^>]*>(.*?)<\/thinking>/s);
798
+ return signatureMatch?.[1]?.trim();
799
+ }
800
+ /**
801
+ * Emit saying events using TellaskStreamParser (non-streaming mode).
802
+ * Processes the entire content at once, handling markdown + tellask calls.
803
+ */
804
+ async function emitSayingEvents(dlg, content) {
805
+ if (!content.trim())
806
+ return [];
807
+ const receiver = createSayingEventsReceiver(dlg);
808
+ const parser = new tellask_1.TellaskStreamParser(receiver);
809
+ await parser.takeUpstreamChunk(content);
810
+ await parser.finalize();
811
+ return parser.getCollectedCalls();
812
+ }
813
+ async function emitUserMarkdown(dlg, content) {
814
+ if (!content.trim()) {
815
+ return;
816
+ }
817
+ await dlg.markdownStart();
818
+ await dlg.markdownChunk(content);
819
+ await dlg.markdownFinish();
820
+ }
821
+ function resolveUpNextPrompt(dlg, humanPrompt) {
822
+ if (humanPrompt) {
823
+ return humanPrompt;
824
+ }
825
+ const upNext = dlg.takeUpNext();
826
+ if (!upNext) {
827
+ return undefined;
828
+ }
829
+ return {
830
+ content: upNext.prompt,
831
+ msgId: upNext.msgId,
832
+ grammar: 'markdown',
833
+ userLanguageCode: upNext.userLanguageCode,
834
+ };
835
+ }
836
+ function scheduleUpNextDrive(dlg, upNext) {
837
+ const prompt = {
838
+ content: upNext.prompt,
839
+ msgId: upNext.msgId,
840
+ grammar: 'markdown',
841
+ userLanguageCode: upNext.userLanguageCode,
842
+ };
843
+ void driveDialogStream(dlg, prompt, true);
844
+ }
845
+ const suspensionStateMutexes = new Map();
846
+ async function withSuspensionStateLock(dialogId, fn) {
847
+ const key = dialogId.key();
848
+ let mutex = suspensionStateMutexes.get(key);
849
+ if (!mutex) {
850
+ mutex = new async_fifo_mutex_1.AsyncFifoMutex();
851
+ suspensionStateMutexes.set(key, mutex);
852
+ }
853
+ const release = await mutex.acquire();
854
+ try {
855
+ return await fn();
856
+ }
857
+ finally {
858
+ release();
859
+ }
860
+ }
861
+ async function hasQueuedSubdialogResponses(dialogId) {
862
+ try {
863
+ const queued = await withSuspensionStateLock(dialogId, async () => {
864
+ return await persistence_1.DialogPersistence.loadSubdialogResponsesQueue(dialogId);
865
+ });
866
+ return queued.length > 0;
867
+ }
868
+ catch (err) {
869
+ log_1.log.warn('Failed to check queued subdialog responses; suppressing diligence as safe default', {
870
+ dialogId: dialogId.valueOf(),
871
+ error: err,
872
+ });
873
+ // Fail safe: if we cannot verify queue emptiness, do not inject diligence prompts.
874
+ return true;
875
+ }
876
+ }
877
+ // TODO: certain scenarios should pass `waitInQue=true`:
878
+ // - supdialog call for clarification
879
+ /**
880
+ * Drive a dialog stream with the following phases:
881
+ *
882
+ * Phase 1 - Lock Acquisition:
883
+ * - Attempt to acquire exclusive lock for the dialog using mutex
884
+ * - If dialog is already being driven, either wait in queue or throw error
885
+ *
886
+ * Phase 2 - Human Prompt Processing (first iteration only):
887
+ * - If humanPrompt is provided, add it as a prompting_msg
888
+ * - Persist user message to storage
889
+ *
890
+ * Phase 3 - User Tellask Call Block Collection & Execution:
891
+ * - Parse user text for tellask blocks using TellaskStreamParser
892
+ * - Execute tellasks (teammate tellasks / Q4H / supdialog)
893
+ * - Handle subdialog creation for @teammate mentions
894
+ *
895
+ * Phase 4 - Context Building:
896
+ * - Load agent minds (team, agent, system prompt, memories, tools)
897
+ * - Build context messages: memories, Taskdoc, assignment from supdialog, dialog msgs
898
+ * - Process and render reminders
899
+ *
900
+ * Phase 5 - LLM Generation:
901
+ * - For streaming=false: Generate all messages at once
902
+ * - For streaming=true: Stream responses with thinking/saying events
903
+ *
904
+ * Phase 6 - Function/Tellask Call Execution:
905
+ * - Execute function calls (non-streaming mode)
906
+ * - Execute tellask calls (streaming mode)
907
+ * - Collect and persist results
908
+ *
909
+ * Phase 7 - Loop or Complete:
910
+ * - Check if more generation iterations are needed
911
+ * - Continue loop if new function calls or tool outputs exist
912
+ * - Break and release lock when complete
913
+ */
914
+ async function driveDialogStream(dlg, humanPrompt, waitInQue = false, driveOptions) {
915
+ if (!waitInQue && dlg.isLocked()) {
916
+ throw new Error(`Dialog busy driven, see how it proceeded and try again.`);
917
+ }
918
+ const allowResumeFromInterrupted = driveOptions?.allowResumeFromInterrupted === true || humanPrompt?.origin === 'user';
919
+ const release = await dlg.acquire();
920
+ let followUp;
921
+ let driveResult;
922
+ let subdialogReplyTarget;
923
+ try {
924
+ // "dead" is an irreversible UI state (primarily for subdialogs). If a dialog is marked dead
925
+ // in latest.yaml, do not proceed with driving. This guards against races/cross-client drive.
926
+ try {
927
+ const latest = await persistence_1.DialogPersistence.loadDialogLatest(dlg.id, 'running');
928
+ if (dlg.id.selfId !== dlg.id.rootId &&
929
+ latest &&
930
+ latest.runState &&
931
+ latest.runState.kind === 'dead') {
932
+ return;
933
+ }
934
+ if (latest && latest.runState && latest.runState.kind === 'proceeding_stop_requested') {
935
+ log_1.log.info('Skip drive while stop request is still being processed', undefined, {
936
+ dialogId: dlg.id.valueOf(),
937
+ reason: latest.runState.reason,
938
+ });
939
+ return;
940
+ }
941
+ if (latest &&
942
+ latest.runState &&
943
+ latest.runState.kind === 'interrupted' &&
944
+ !allowResumeFromInterrupted) {
945
+ log_1.log.info('Skip drive for interrupted dialog without explicit resume/user prompt', undefined, {
946
+ dialogId: dlg.id.valueOf(),
947
+ reason: latest.runState.reason,
948
+ });
949
+ return;
950
+ }
951
+ }
952
+ catch (err) {
953
+ log_1.log.warn('Failed to check runState before drive; proceeding best-effort', err, {
954
+ dialogId: dlg.id.valueOf(),
955
+ });
956
+ }
957
+ const effectivePrompt = resolveUpNextPrompt(dlg, humanPrompt);
958
+ subdialogReplyTarget = effectivePrompt?.subdialogReplyTarget;
959
+ if (effectivePrompt && effectivePrompt.userLanguageCode) {
960
+ dlg.setLastUserLanguageCode(effectivePrompt.userLanguageCode);
961
+ }
962
+ driveResult = await _driveDialogStream(dlg, effectivePrompt, driveOptions);
963
+ // Do not auto-chain upNext when this drive ended in interrupted state
964
+ // (user/emergency/system stop). upNext remains queued for explicit manual resume.
965
+ if (!driveResult.interrupted) {
966
+ followUp = dlg.takeUpNext();
967
+ }
968
+ }
969
+ finally {
970
+ release();
971
+ }
972
+ if (followUp) {
973
+ scheduleUpNextDrive(dlg, followUp);
974
+ }
975
+ else if (dlg instanceof dialog_1.SubDialog &&
976
+ driveResult &&
977
+ !driveResult.interrupted &&
978
+ driveResult.lastAssistantSayingContent !== null) {
979
+ const suspension = await dlg.getSuspensionStatus();
980
+ if (!suspension.canDrive) {
981
+ log_1.log.info('Skip supplying subdialog response because dialog is still suspended', {
982
+ rootId: dlg.id.rootId,
983
+ selfId: dlg.id.selfId,
984
+ waitingQ4H: suspension.q4h,
985
+ waitingSubdialogs: suspension.subdialogs,
986
+ });
987
+ return;
988
+ }
989
+ if (subdialogReplyTarget) {
990
+ await supplySubdialogResponseToSpecificCallerIfPending(dlg, driveResult.lastAssistantSayingContent, subdialogReplyTarget);
991
+ }
992
+ else {
993
+ await supplySubdialogResponseToAssignedCallerIfPending(dlg, driveResult.lastAssistantSayingContent);
994
+ }
995
+ }
996
+ }
997
+ // Transitional bridge for driver-v2 development.
998
+ // Keeps v1 behavior unchanged while allowing v2 to invoke the core generation loop directly.
999
+ async function driveDialogStreamCoreV1(dlg, humanPrompt, driveOptions) {
1000
+ return await _driveDialogStream(dlg, humanPrompt, driveOptions);
1001
+ }
1002
+ /**
1003
+ * Backend coroutine that continuously drives dialogs.
1004
+ * Uses dynamic canDrive() checks instead of stored suspend state.
1005
+ */
1006
+ async function runBackendDriver() {
1007
+ while (true) {
1008
+ try {
1009
+ const dialogsToDrive = dialog_global_registry_1.globalDialogRegistry.getDialogsNeedingDrive();
1010
+ for (const rootDialog of dialogsToDrive) {
1011
+ try {
1012
+ const latest = await persistence_1.DialogPersistence.loadDialogLatest(rootDialog.id, 'running');
1013
+ const runStateKind = latest?.runState?.kind;
1014
+ if (runStateKind === 'interrupted' || runStateKind === 'proceeding_stop_requested') {
1015
+ dialog_global_registry_1.globalDialogRegistry.markNotNeedingDrive(rootDialog.id.rootId);
1016
+ await persistence_1.DialogPersistence.setNeedsDrive(rootDialog.id, false, rootDialog.status);
1017
+ continue;
1018
+ }
1019
+ if (!(await rootDialog.canDrive())) {
1020
+ continue;
1021
+ }
1022
+ const release = await rootDialog.acquire();
1023
+ try {
1024
+ await driveDialogToSuspension(rootDialog);
1025
+ }
1026
+ finally {
1027
+ release();
1028
+ }
1029
+ const status = await rootDialog.getSuspensionStatus();
1030
+ const shouldStayQueued = rootDialog.hasUpNext() || !status.canDrive;
1031
+ if (shouldStayQueued) {
1032
+ dialog_global_registry_1.globalDialogRegistry.markNeedsDrive(rootDialog.id.rootId);
1033
+ await persistence_1.DialogPersistence.setNeedsDrive(rootDialog.id, true, rootDialog.status);
1034
+ }
1035
+ else {
1036
+ dialog_global_registry_1.globalDialogRegistry.markNotNeedingDrive(rootDialog.id.rootId);
1037
+ await persistence_1.DialogPersistence.setNeedsDrive(rootDialog.id, false, rootDialog.status);
1038
+ }
1039
+ if (status.subdialogs) {
1040
+ log_1.log.info(`Dialog ${rootDialog.id.rootId} suspended, waiting for subdialogs`);
1041
+ }
1042
+ if (status.q4h) {
1043
+ log_1.log.info(`Dialog ${rootDialog.id.rootId} awaiting Q4H answer`);
1044
+ }
1045
+ }
1046
+ catch (err) {
1047
+ log_1.log.error(`Error driving dialog ${rootDialog.id.rootId}:`, err, undefined, {
1048
+ dialogId: rootDialog.id.rootId,
1049
+ });
1050
+ }
1051
+ }
1052
+ await new Promise((resolve) => setTimeout(resolve, 100));
1053
+ }
1054
+ catch (loopErr) {
1055
+ log_1.log.error('Error in backend driver loop:', loopErr);
1056
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1057
+ }
1058
+ }
1059
+ }
1060
+ /**
1061
+ * Drive a dialog until it suspends or completes.
1062
+ * Called with mutex already acquired.
1063
+ */
1064
+ async function driveDialogToSuspension(dlg) {
1065
+ try {
1066
+ const effectivePrompt = resolveUpNextPrompt(dlg);
1067
+ if (effectivePrompt && effectivePrompt.userLanguageCode) {
1068
+ dlg.setLastUserLanguageCode(effectivePrompt.userLanguageCode);
1069
+ }
1070
+ await _driveDialogStream(dlg, effectivePrompt);
1071
+ }
1072
+ catch (err) {
1073
+ log_1.log.warn(`Error in driveDialogToSuspension for ${dlg.id.selfId}:`, err);
1074
+ throw err;
1075
+ }
1076
+ }
1077
+ /**
1078
+ * Frontend-triggered revive check (crash-recovery).
1079
+ */
1080
+ async function checkAndReviveSuspendedDialogs() {
1081
+ const allDialogs = dialog_global_registry_1.globalDialogRegistry.getAll();
1082
+ for (const rootDialog of allDialogs) {
1083
+ const pending = await persistence_1.DialogPersistence.loadPendingSubdialogs(rootDialog.id);
1084
+ if (pending.length > 0) {
1085
+ const allSatisfied = await areAllSubdialogsSatisfied(rootDialog.id);
1086
+ if (allSatisfied) {
1087
+ await withSuspensionStateLock(rootDialog.id, async () => {
1088
+ await persistence_1.DialogPersistence.clearPendingSubdialogs(rootDialog.id);
1089
+ await persistence_1.DialogPersistence.setNeedsDrive(rootDialog.id, true, rootDialog.status);
1090
+ });
1091
+ dialog_global_registry_1.globalDialogRegistry.markNeedsDrive(rootDialog.id.rootId);
1092
+ log_1.log.info(`All subdialogs complete for ${rootDialog.id.rootId}, auto-reviving`);
1093
+ }
1094
+ }
1095
+ const subdialogs = rootDialog.getAllDialogs().filter((d) => d !== rootDialog);
1096
+ for (const subdialog of subdialogs) {
1097
+ try {
1098
+ const latest = await persistence_1.DialogPersistence.loadDialogLatest(subdialog.id, 'running');
1099
+ if (latest && latest.runState && latest.runState.kind === 'dead') {
1100
+ continue;
1101
+ }
1102
+ }
1103
+ catch (err) {
1104
+ log_1.log.warn('Failed to check runState for subdialog revival; proceeding best-effort', err, {
1105
+ dialogId: subdialog.id.valueOf(),
1106
+ });
1107
+ }
1108
+ const hasAnswer = await checkQ4HAnswered(subdialog.id);
1109
+ if (hasAnswer && !(await subdialog.hasPendingQ4H())) {
1110
+ void driveDialogStream(subdialog, undefined, true);
1111
+ log_1.log.info(`Q4H answered for dialog ${subdialog.id.selfId}, auto-reviving`);
1112
+ }
1113
+ }
1114
+ }
1115
+ }
1116
+ async function checkQ4HAnswered(dialogId) {
1117
+ try {
1118
+ const questions = await persistence_1.DialogPersistence.loadQuestions4HumanState(dialogId);
1119
+ return questions.length === 0;
1120
+ }
1121
+ catch (err) {
1122
+ log_1.log.warn(`Error checking Q4H state ${dialogId.key()}:`, err);
1123
+ return false;
1124
+ }
1125
+ }
1126
+ async function _driveDialogStream(dlg, humanPrompt, driveOptions) {
1127
+ const suppressDiligencePushForDrive = driveOptions?.suppressDiligencePush === true;
1128
+ const abortSignal = (0, dialog_run_state_1.createActiveRun)(dlg.id);
1129
+ let finalRunState;
1130
+ let shouldEmitResumedMarker = false;
1131
+ if (!humanPrompt) {
1132
+ try {
1133
+ const latest = await persistence_1.DialogPersistence.loadDialogLatest(dlg.id, 'running');
1134
+ shouldEmitResumedMarker =
1135
+ latest !== null &&
1136
+ latest !== undefined &&
1137
+ latest.runState !== undefined &&
1138
+ latest.runState.kind === 'interrupted';
1139
+ }
1140
+ catch (err) {
1141
+ log_1.log.warn('Failed to load latest.yaml for resumption marker', err, {
1142
+ dialogId: dlg.id.valueOf(),
1143
+ });
1144
+ }
1145
+ }
1146
+ if (shouldEmitResumedMarker) {
1147
+ (0, dialog_run_state_1.broadcastRunStateMarker)(dlg.id, { kind: 'resumed' });
1148
+ }
1149
+ await (0, dialog_run_state_1.setDialogRunState)(dlg.id, { kind: 'proceeding' });
1150
+ let pubRemindersVer = dlg.remindersVer;
1151
+ let lastAssistantSayingContent = null;
1152
+ let generationHadError = false;
1153
+ let tookSubdialogResponses = false;
1154
+ let takenSubdialogResponses = [];
1155
+ // Keep injected subdialog replies visible across all gen iterations in the same drive.
1156
+ // Without this, iteration #2 (e.g. after a function call) can lose reply context from iteration #1.
1157
+ let subdialogResponseContextMsgs = [];
1158
+ // Internal prompt is drive-scoped priming context and never persisted.
1159
+ let internalDrivePromptMsg;
1160
+ let committedTakenSubdialogResponses = false;
1161
+ let genIterNo = 0;
1162
+ let pendingPrompt = humanPrompt;
1163
+ let skipTaskdocForThisDrive = humanPrompt?.skipTaskdoc === true;
1164
+ try {
1165
+ while (true) {
1166
+ genIterNo++;
1167
+ throwIfAborted(abortSignal, dlg.id);
1168
+ // reload the agent's minds from disk every course, in case the disk files changed by human or ai meanwhile
1169
+ const minds = await (0, load_1.loadAgentMinds)(dlg.agentId, dlg);
1170
+ const team = minds.team;
1171
+ let agent = minds.agent;
1172
+ let systemPrompt = minds.systemPrompt;
1173
+ const memories = minds.memories;
1174
+ let agentTools = minds.agentTools;
1175
+ const drivePolicy = buildDrivePolicy({
1176
+ dlg,
1177
+ agent,
1178
+ systemPrompt,
1179
+ agentTools,
1180
+ language: (0, runtime_language_1.getWorkLanguage)(),
1181
+ });
1182
+ const drivePolicyValidation = validateDrivePolicyInvariants(drivePolicy, (0, runtime_language_1.getWorkLanguage)());
1183
+ if (!drivePolicyValidation.ok) {
1184
+ throw new Error(`FBR policy isolation violation: ${drivePolicyValidation.detail}`);
1185
+ }
1186
+ agent = drivePolicy.effectiveAgent;
1187
+ systemPrompt = drivePolicy.effectiveSystemPrompt;
1188
+ agentTools = drivePolicy.effectiveAgentTools;
1189
+ // reload cfgs every course, in case it's been updated by human or ai meanwhile
1190
+ // Validate streaming configuration
1191
+ try {
1192
+ validateStreamingConfiguration(agent, agentTools);
1193
+ }
1194
+ catch (error) {
1195
+ log_1.log.warn(`Streaming configuration error for agent ${dlg.agentId}:`, error);
1196
+ throw error;
1197
+ }
1198
+ // Validate that required provider and model are configured
1199
+ // Validate that required provider and model are configured
1200
+ const provider = agent.provider ?? team.memberDefaults.provider;
1201
+ const model = agent.model ?? team.memberDefaults.model;
1202
+ if (!provider) {
1203
+ const error = new Error(`Configuration Error: No provider configured for agent '${dlg.agentId}'. Please specify a provider in the agent's configuration or in member_defaults section of .minds/team.yaml.`);
1204
+ log_1.log.warn(`Provider not configured for agent ${dlg.agentId}`, error);
1205
+ throw error;
1206
+ }
1207
+ if (!model) {
1208
+ const error = new Error(`Configuration Error: No model configured for agent '${dlg.agentId}'. Please specify a model in the agent's configuration or in member_defaults section of .minds/team.yaml.`);
1209
+ log_1.log.warn(`Model not configured for agent ${dlg.agentId}`, error);
1210
+ throw error;
1211
+ }
1212
+ const llmCfg = await client_1.LlmConfig.load();
1213
+ const providerCfg = llmCfg.getProvider(provider);
1214
+ if (!providerCfg) {
1215
+ const error = new Error(`Provider configuration error: Provider '${provider}' not found for agent '${dlg.agentId}'. Please check .minds/llm.yaml and .minds/team.yaml configuration.`);
1216
+ log_1.log.warn(`Provider not found for agent ${dlg.agentId}`, error);
1217
+ throw error;
1218
+ }
1219
+ const modelsUnknown = providerCfg.models;
1220
+ const models = typeof modelsUnknown === 'object' && modelsUnknown !== null && !Array.isArray(modelsUnknown)
1221
+ ? modelsUnknown
1222
+ : undefined;
1223
+ const modelInfo = models ? models[model] : undefined;
1224
+ if (!modelInfo) {
1225
+ const uiLanguage = dlg.getUiLanguage();
1226
+ const msg = uiLanguage === 'zh'
1227
+ ? [
1228
+ '配置错误:当前成员的模型配置无效。',
1229
+ '',
1230
+ `- member: ${agent.name} (${dlg.agentId})`,
1231
+ `- provider: ${provider}`,
1232
+ `- model: ${model}(这是 model key;在该 provider 的 models 列表中不存在,或该 provider 缺少 models 配置)`,
1233
+ '',
1234
+ '请联系团队管理者修复:',
1235
+ `- 在 .minds/team.yaml 中把该成员的 provider/model 改成有效 key;或`,
1236
+ `- 在 .minds/llm.yaml 的 providers.${provider}.models 下补齐该 model key。`,
1237
+ '',
1238
+ '提示:你也可以打开 WebUI 的 `/setup` 查看当前 rtws(运行时工作区)可用的 provider/model 列表。',
1239
+ '',
1240
+ '团队管理者修复后建议运行:`team_mgmt_validate_team_cfg({})`。',
1241
+ ].join('\n')
1242
+ : [
1243
+ 'Configuration error: invalid model selection for this member.',
1244
+ '',
1245
+ `- member: ${agent.name} (${dlg.agentId})`,
1246
+ `- provider: ${provider}`,
1247
+ `- model: ${model} (this is a model key; not found under this provider's models list, or the provider has no models configured)`,
1248
+ '',
1249
+ 'Please contact your team manager to fix:',
1250
+ `- Update the member's provider/model keys in .minds/team.yaml, or`,
1251
+ `- Add the model key under .minds/llm.yaml providers.${provider}.models.`,
1252
+ '',
1253
+ 'Tip: you can also open the WebUI `/setup` page to see available provider/model keys for this rtws (runtime workspace).',
1254
+ '',
1255
+ 'After the fix, the team manager should run: `team_mgmt_validate_team_cfg({})`.',
1256
+ ].join('\n');
1257
+ throw new Error(msg);
1258
+ }
1259
+ const llmGen = (0, registry_1.getLlmGenerator)(providerCfg.apiType);
1260
+ if (!llmGen) {
1261
+ const error = new Error(`LLM generator not found: API type '${providerCfg.apiType}' for provider '${provider}' in agent '${dlg.agentId}'. Please check .minds/llm.yaml configuration.`);
1262
+ log_1.log.warn(`LLM generator not found for agent ${dlg.agentId}`, error);
1263
+ throw error;
1264
+ }
1265
+ const canonicalFuncTools = agentTools.filter((t) => t.type === 'func');
1266
+ const projected = (0, tools_projection_1.projectFuncToolsForProvider)(providerCfg.apiType, canonicalFuncTools);
1267
+ const funcTools = projected.tools;
1268
+ let suspendForHuman = false;
1269
+ let promptContent = '';
1270
+ let contextHealthForGen;
1271
+ let llmGenModelForGen = model;
1272
+ try {
1273
+ throwIfAborted(abortSignal, dlg.id);
1274
+ await dlg.notifyGeneratingStart();
1275
+ const currentPrompt = pendingPrompt;
1276
+ pendingPrompt = undefined;
1277
+ if (currentPrompt) {
1278
+ const promptOrigin = currentPrompt.origin ?? 'user';
1279
+ const isDiligencePrompt = promptOrigin === 'diligence_push';
1280
+ if (isDiligencePrompt && (dlg.disableDiligencePush || suppressDiligencePushForDrive)) {
1281
+ log_1.log.info('Skip diligence prompt after disable toggle', {
1282
+ dialogId: dlg.id.valueOf(),
1283
+ msgId: currentPrompt.msgId,
1284
+ });
1285
+ break;
1286
+ }
1287
+ if (currentPrompt.skipTaskdoc === true) {
1288
+ skipTaskdocForThisDrive = true;
1289
+ }
1290
+ promptContent = currentPrompt.content;
1291
+ const msgId = currentPrompt.msgId;
1292
+ const promptGrammar = currentPrompt.grammar;
1293
+ const persistedUserLanguageCode = currentPrompt.userLanguageCode ?? dlg.getLastUserLanguageCode();
1294
+ const requestedPersistMode = currentPrompt.persistMode ?? 'persist';
1295
+ const persistMode = isDiligencePrompt ? 'persist' : requestedPersistMode;
1296
+ if (isDiligencePrompt && requestedPersistMode !== 'persist') {
1297
+ log_1.log.warn('Diligence prompt must be persisted; forcing persist mode', {
1298
+ dialogId: dlg.id.valueOf(),
1299
+ msgId,
1300
+ requestedPersistMode,
1301
+ });
1302
+ }
1303
+ if (persistMode === 'internal') {
1304
+ const injected = currentPrompt.content.trim();
1305
+ internalDrivePromptMsg = injected
1306
+ ? {
1307
+ type: 'environment_msg',
1308
+ role: 'user',
1309
+ content: injected,
1310
+ }
1311
+ : undefined;
1312
+ }
1313
+ else {
1314
+ await dlg.addChatMessages({
1315
+ type: 'prompting_msg',
1316
+ role: 'user',
1317
+ genseq: dlg.activeGenSeq,
1318
+ content: promptContent,
1319
+ msgId: msgId,
1320
+ grammar: promptGrammar,
1321
+ });
1322
+ // Persist user message to storage FIRST
1323
+ await dlg.persistUserMessage(promptContent, msgId, promptGrammar, persistedUserLanguageCode);
1324
+ }
1325
+ if (persistMode !== 'internal' && promptGrammar === 'tellask') {
1326
+ // Collect and execute tellask calls from user text using streaming parser
1327
+ throwIfAborted(abortSignal, dlg.id);
1328
+ const collectedUserCalls = await emitSayingEvents(dlg, promptContent);
1329
+ throwIfAborted(abortSignal, dlg.id);
1330
+ const userResult = await executeTellaskCalls(dlg, agent, collectedUserCalls);
1331
+ if (dlg.hasUpNext()) {
1332
+ return { lastAssistantSayingContent, interrupted: false };
1333
+ }
1334
+ if (userResult.toolOutputs.length > 0) {
1335
+ await dlg.addChatMessages(...userResult.toolOutputs);
1336
+ }
1337
+ if (userResult.suspend) {
1338
+ suspendForHuman = true;
1339
+ }
1340
+ // No teammate-call fallback here: rely exclusively on TellaskStreamParser.
1341
+ // Pending subdialogs are tracked in persistence (pending-subdialogs.json) as the source of truth.
1342
+ }
1343
+ else if (persistMode !== 'internal') {
1344
+ await emitUserMarkdown(dlg, promptContent);
1345
+ }
1346
+ if (persistMode !== 'internal') {
1347
+ try {
1348
+ (0, evt_registry_1.postDialogEvent)(dlg, {
1349
+ type: 'end_of_user_saying_evt',
1350
+ course: dlg.currentCourse,
1351
+ genseq: dlg.activeGenSeq,
1352
+ msgId,
1353
+ content: promptContent,
1354
+ grammar: promptGrammar,
1355
+ userLanguageCode: persistedUserLanguageCode,
1356
+ });
1357
+ }
1358
+ catch (err) {
1359
+ log_1.log.warn('Failed to emit end_of_user_saying_evt', err);
1360
+ }
1361
+ }
1362
+ }
1363
+ if (suspendForHuman) {
1364
+ break;
1365
+ }
1366
+ // Take any queued subdialog responses (once per drive) and inject them as fresh user context.
1367
+ // This is the core "revival" mechanism: the parent is driven again when all pending subdialogs
1368
+ // are resolved, and the queued responses become the next user-visible input to the model.
1369
+ if (genIterNo === 1 && !tookSubdialogResponses) {
1370
+ tookSubdialogResponses = true;
1371
+ try {
1372
+ takenSubdialogResponses = await withSuspensionStateLock(dlg.id, async () => {
1373
+ return await persistence_1.DialogPersistence.takeSubdialogResponses(dlg.id);
1374
+ });
1375
+ }
1376
+ catch (err) {
1377
+ log_1.log.warn('Failed to take subdialog responses for injection', {
1378
+ dialogId: dlg.id.selfId,
1379
+ error: err,
1380
+ });
1381
+ generationHadError = true;
1382
+ takenSubdialogResponses = [];
1383
+ }
1384
+ }
1385
+ // use fresh memory + updated msgs from dialog object
1386
+ // Build ctxMsgs messages in logical order, then inject reminders as late as possible:
1387
+ // 1) memories
1388
+ // 2) Taskdoc (user)
1389
+ // 3) historical dialog msgs
1390
+ // Finally, render reminders and place them immediately before the last 'user' message
1391
+ // so they are salient for the next response without polluting earlier context.
1392
+ const taskDocMsg = dlg.taskDocPath && !skipTaskdocForThisDrive ? await (0, taskdoc_1.formatTaskDocContent)(dlg) : undefined;
1393
+ const coursePrefixMsgs = (() => {
1394
+ const msgs = dlg.getCoursePrefixMsgs();
1395
+ return msgs.length > 0 ? [...msgs] : [];
1396
+ })();
1397
+ const dialogMsgsForContext = dlg.msgs.filter((m) => {
1398
+ if (!m)
1399
+ return false;
1400
+ if (m.type === 'ui_only_markdown_msg')
1401
+ return false;
1402
+ return true;
1403
+ });
1404
+ if (genIterNo === 1 && takenSubdialogResponses.length > 0) {
1405
+ subdialogResponseContextMsgs = takenSubdialogResponses.map((response) => ({
1406
+ type: 'environment_msg',
1407
+ role: 'user',
1408
+ content: (0, inter_dialog_format_1.formatTeammateResponseContent)({
1409
+ responderId: response.responderId,
1410
+ requesterId: response.originMemberId,
1411
+ originalCallHeadLine: response.tellaskHead,
1412
+ responseBody: response.response,
1413
+ language: (0, runtime_language_1.getWorkLanguage)(),
1414
+ }),
1415
+ }));
1416
+ }
1417
+ await dlg.processReminderUpdates();
1418
+ const renderedReminders = dlg.reminders.length > 0
1419
+ ? await Promise.all(dlg.reminders.map(async (reminder, index) => {
1420
+ if (reminder.owner) {
1421
+ return await reminder.owner.renderReminder(dlg, reminder, index);
1422
+ }
1423
+ return {
1424
+ type: 'environment_msg',
1425
+ role: 'user',
1426
+ content: (0, driver_messages_1.formatReminderItemGuide)((0, runtime_language_1.getWorkLanguage)(), index + 1, reminder.content, { meta: reminder.meta }),
1427
+ };
1428
+ }))
1429
+ : [];
1430
+ const uiLanguage = dlg.getLastUserLanguageCode();
1431
+ const workingLanguage = (0, runtime_language_1.getWorkLanguage)();
1432
+ const guideMsg = {
1433
+ type: 'transient_guide_msg',
1434
+ role: 'assistant',
1435
+ content: (0, driver_messages_1.formatCurrentUserLanguagePreference)(workingLanguage, uiLanguage),
1436
+ };
1437
+ const ctxMsgs = (0, context_1.assembleDriveContextMessages)({
1438
+ base: {
1439
+ prependedContextMessages: drivePolicy.prependedContextMessages,
1440
+ memories,
1441
+ taskDocMsg,
1442
+ coursePrefixMsgs,
1443
+ dialogMsgsForContext,
1444
+ },
1445
+ ephemeral: {
1446
+ subdialogResponseContextMsgs,
1447
+ internalDrivePromptMsg,
1448
+ },
1449
+ tail: {
1450
+ renderedReminders,
1451
+ languageGuideMsg: guideMsg,
1452
+ },
1453
+ });
1454
+ const remediation = await applyContextHealthV3Remediation({
1455
+ dlg,
1456
+ agent,
1457
+ agentTools,
1458
+ providerCfg,
1459
+ provider,
1460
+ systemPrompt,
1461
+ funcTools,
1462
+ ctxMsgs,
1463
+ llmGen,
1464
+ abortSignal,
1465
+ model,
1466
+ hadUserPromptThisGen: currentPrompt !== undefined,
1467
+ });
1468
+ if (remediation.kind === 'continue') {
1469
+ if (remediation.contextHealthForGen) {
1470
+ contextHealthForGen = remediation.contextHealthForGen;
1471
+ }
1472
+ pendingPrompt = remediation.nextPrompt;
1473
+ continue;
1474
+ }
1475
+ if (remediation.kind === 'suspend') {
1476
+ if (remediation.contextHealthForGen) {
1477
+ contextHealthForGen = remediation.contextHealthForGen;
1478
+ }
1479
+ suspendForHuman = true;
1480
+ break;
1481
+ }
1482
+ const ctxMsgsForGen = remediation.ctxMsgs;
1483
+ if (agent.streaming === false) {
1484
+ if (llmGen.apiType === 'codex') {
1485
+ const detail = `Team config error: member '${agent.id}' has streaming=false but provider apiType=codex requires streaming=true (provider='${providerCfg.name}', genseq=${String(dlg.activeGenSeq)}).`;
1486
+ log_1.log.error(detail, new Error('team_config_invalid_streaming'));
1487
+ await dlg.streamError(detail);
1488
+ throw new Error(detail);
1489
+ }
1490
+ let nonStreamResult;
1491
+ try {
1492
+ throwIfAborted(abortSignal, dlg.id);
1493
+ nonStreamResult = await runLlmRequestWithRetry({
1494
+ dlg,
1495
+ provider,
1496
+ abortSignal,
1497
+ maxRetries: 5,
1498
+ canRetry: () => true,
1499
+ doRequest: async () => {
1500
+ return await llmGen.genMoreMessages(providerCfg, agent, systemPrompt, funcTools, ctxMsgsForGen, dlg.activeGenSeq, abortSignal);
1501
+ },
1502
+ });
1503
+ }
1504
+ catch (err) {
1505
+ if (abortSignal.aborted) {
1506
+ throwIfAborted(abortSignal, dlg.id);
1507
+ }
1508
+ generationHadError = true;
1509
+ throw err;
1510
+ }
1511
+ if (typeof nonStreamResult.llmGenModel === 'string' &&
1512
+ nonStreamResult.llmGenModel.trim() !== '') {
1513
+ llmGenModelForGen = nonStreamResult.llmGenModel.trim();
1514
+ }
1515
+ if (!agent.model) {
1516
+ throw new Error(`Internal error: Model is undefined for agent '${agent.id}'`);
1517
+ }
1518
+ contextHealthForGen = computeContextHealthSnapshot({
1519
+ providerCfg,
1520
+ model: agent.model,
1521
+ usage: nonStreamResult.usage,
1522
+ });
1523
+ dlg.setLastContextHealth(contextHealthForGen);
1524
+ const nonStreamMsgs = nonStreamResult.messages;
1525
+ const assistantMsgs = nonStreamMsgs.filter((m) => m.type === 'saying_msg' || m.type === 'thinking_msg');
1526
+ const collectedAssistantCalls = [];
1527
+ if (assistantMsgs.length > 0) {
1528
+ await dlg.addChatMessages(...assistantMsgs);
1529
+ for (const msg of assistantMsgs) {
1530
+ if (msg.role === 'assistant' &&
1531
+ msg.genseq !== undefined &&
1532
+ (msg.type === 'thinking_msg' || msg.type === 'saying_msg')) {
1533
+ // Only persist saying_msg - thinking_msg is persisted via thinkingFinish
1534
+ if (msg.type === 'saying_msg') {
1535
+ lastAssistantSayingContent = msg.content;
1536
+ await dlg.persistAgentMessage(msg.content, msg.genseq, 'saying_msg');
1537
+ }
1538
+ // Emit thinking events using shared handler (non-streaming mode)
1539
+ if (msg.type === 'thinking_msg') {
1540
+ await emitThinkingEvents(dlg, msg.content);
1541
+ }
1542
+ // Emit saying events using shared TellaskStreamParser integration
1543
+ if (msg.type === 'saying_msg') {
1544
+ const calls = await emitSayingEvents(dlg, msg.content);
1545
+ collectedAssistantCalls.push(...calls);
1546
+ }
1547
+ }
1548
+ }
1549
+ }
1550
+ const nonStreamingTellaskViolation = resolveDrivePolicyViolationKind({
1551
+ policy: drivePolicy,
1552
+ tellaskCalls: collectedAssistantCalls,
1553
+ functionCallCount: 0,
1554
+ });
1555
+ if (nonStreamingTellaskViolation === 'tellask') {
1556
+ const violationText = (0, driver_messages_1.formatDomindsNoteFbrToollessViolation)((0, runtime_language_1.getWorkLanguage)(), {
1557
+ kind: 'tellask',
1558
+ });
1559
+ const genseq = dlg.activeGenSeq ?? 0;
1560
+ await dlg.addChatMessages({
1561
+ type: 'saying_msg',
1562
+ role: 'assistant',
1563
+ genseq,
1564
+ content: violationText,
1565
+ });
1566
+ lastAssistantSayingContent = violationText;
1567
+ await dlg.persistAgentMessage(violationText, genseq, 'saying_msg');
1568
+ return { lastAssistantSayingContent, interrupted: false };
1569
+ }
1570
+ if (collectedAssistantCalls.length > 0) {
1571
+ throwIfAborted(abortSignal, dlg.id);
1572
+ const assistantResult = await executeTellaskCalls(dlg, agent, collectedAssistantCalls);
1573
+ if (dlg.hasUpNext()) {
1574
+ return { lastAssistantSayingContent, interrupted: false };
1575
+ }
1576
+ if (assistantResult.toolOutputs.length > 0) {
1577
+ await dlg.addChatMessages(...assistantResult.toolOutputs);
1578
+ }
1579
+ if (assistantResult.suspend) {
1580
+ suspendForHuman = true;
1581
+ }
1582
+ }
1583
+ const funcCalls = nonStreamMsgs.filter((m) => m.type === 'func_call_msg');
1584
+ const nonStreamingToolViolation = resolveDrivePolicyViolationKind({
1585
+ policy: drivePolicy,
1586
+ tellaskCalls: [],
1587
+ functionCallCount: funcCalls.length,
1588
+ });
1589
+ if (nonStreamingToolViolation === 'tool') {
1590
+ const violationText = (0, driver_messages_1.formatDomindsNoteFbrToollessViolation)((0, runtime_language_1.getWorkLanguage)(), {
1591
+ kind: 'tool',
1592
+ });
1593
+ const genseq = dlg.activeGenSeq ?? 0;
1594
+ await dlg.addChatMessages({
1595
+ type: 'saying_msg',
1596
+ role: 'assistant',
1597
+ genseq,
1598
+ content: violationText,
1599
+ });
1600
+ lastAssistantSayingContent = violationText;
1601
+ await dlg.persistAgentMessage(violationText, genseq, 'saying_msg');
1602
+ return { lastAssistantSayingContent, interrupted: false };
1603
+ }
1604
+ const functionPromises = funcCalls.map(async (func) => {
1605
+ throwIfAborted(abortSignal, dlg.id);
1606
+ // Use the genseq from the func_call_msg to ensure tool results share the same generation sequence
1607
+ // This is critical for correct grouping in reconstructAnthropicContext()
1608
+ const callGenseq = func.genseq;
1609
+ // Use the LLM-allocated unique id for tracking
1610
+ // This id comes from func_call_msg and is the proper unique identifier
1611
+ const callId = func.id;
1612
+ // argsStr is still needed for UI event (funcCallRequested)
1613
+ const argsStr = typeof func.arguments === 'string'
1614
+ ? func.arguments
1615
+ : JSON.stringify(func.arguments ?? {});
1616
+ const tool = agentTools.find((t) => t.type === 'func' && t.name === func.name);
1617
+ if (!tool) {
1618
+ const errorResult = {
1619
+ type: 'func_result_msg',
1620
+ id: func.id,
1621
+ name: func.name,
1622
+ content: `Tool '${func.name}' not found`,
1623
+ role: 'tool',
1624
+ genseq: callGenseq,
1625
+ };
1626
+ await dlg.receiveFuncResult(errorResult);
1627
+ return errorResult;
1628
+ }
1629
+ let rawArgs = {};
1630
+ if (typeof func.arguments === 'string' && func.arguments.trim()) {
1631
+ try {
1632
+ rawArgs = JSON.parse(func.arguments);
1633
+ }
1634
+ catch (parseErr) {
1635
+ rawArgs = null;
1636
+ log_1.log.warn('Failed to parse function arguments as JSON', {
1637
+ funcName: func.name,
1638
+ arguments: func.arguments,
1639
+ error: parseErr,
1640
+ });
1641
+ }
1642
+ }
1643
+ let result;
1644
+ const argsValidation = validateFuncToolArguments(tool, rawArgs);
1645
+ if (argsValidation.ok) {
1646
+ const argsObj = argsValidation.args;
1647
+ // Emit func_call_requested event to build the func-call section UI
1648
+ try {
1649
+ await dlg.funcCallRequested(func.id, func.name, argsStr);
1650
+ }
1651
+ catch (err) {
1652
+ log_1.log.warn('Failed to emit func_call_requested event', err);
1653
+ }
1654
+ try {
1655
+ await dlg.persistFunctionCall(func.id, func.name, argsObj, callGenseq);
1656
+ }
1657
+ catch (err) {
1658
+ log_1.log.warn('Failed to persist function call', err);
1659
+ }
1660
+ try {
1661
+ throwIfAborted(abortSignal, dlg.id);
1662
+ const output = await tool.call(dlg, agent, argsObj);
1663
+ const normalized = typeof output === 'string'
1664
+ ? { content: output, contentItems: undefined }
1665
+ : {
1666
+ content: typeof output.content === 'string' ? output.content : String(output),
1667
+ contentItems: Array.isArray(output.contentItems)
1668
+ ? output.contentItems
1669
+ : undefined,
1670
+ };
1671
+ result = {
1672
+ type: 'func_result_msg',
1673
+ id: func.id,
1674
+ name: func.name,
1675
+ content: String(normalized.content),
1676
+ contentItems: normalized.contentItems,
1677
+ role: 'tool',
1678
+ genseq: callGenseq,
1679
+ };
1680
+ }
1681
+ catch (err) {
1682
+ result = {
1683
+ type: 'func_result_msg',
1684
+ id: func.id,
1685
+ name: func.name,
1686
+ content: `Function '${func.name}' execution failed: ${showErrorToAi(err)}`,
1687
+ role: 'tool',
1688
+ genseq: callGenseq,
1689
+ };
1690
+ }
1691
+ }
1692
+ else {
1693
+ result = {
1694
+ type: 'func_result_msg',
1695
+ id: func.id,
1696
+ name: func.name,
1697
+ content: `Invalid arguments: ${argsValidation.error}`,
1698
+ role: 'tool',
1699
+ genseq: callGenseq,
1700
+ };
1701
+ }
1702
+ await dlg.receiveFuncResult(result);
1703
+ return result;
1704
+ });
1705
+ const funcResults = await Promise.all(functionPromises);
1706
+ await Promise.resolve();
1707
+ // Add function calls AND results to dialog messages so LLM sees tool context in next iteration
1708
+ // Both are needed: func_call_msg for the tool definition, func_result_msg for the output
1709
+ if (funcCalls.length > 0) {
1710
+ const paired = [];
1711
+ for (let i = 0; i < funcCalls.length; i++) {
1712
+ paired.push(funcCalls[i]);
1713
+ paired.push(funcResults[i]);
1714
+ }
1715
+ await dlg.addChatMessages(...paired);
1716
+ }
1717
+ if (dlg.hasUpNext()) {
1718
+ pendingPrompt = resolveUpNextPrompt(dlg);
1719
+ continue;
1720
+ }
1721
+ if (suspendForHuman) {
1722
+ try {
1723
+ // Q4H suspension resets Diligence Push budget so post-Q4H continuation gets a fresh counter.
1724
+ if (await dlg.hasPendingQ4H()) {
1725
+ const configuredMax = resolveMemberDiligencePushMax(team, dlg.agentId);
1726
+ if (typeof configuredMax === 'number' && Number.isFinite(configuredMax)) {
1727
+ const next = Math.floor(configuredMax);
1728
+ dlg.diligencePushRemainingBudget =
1729
+ next > 0 ? next : Math.max(0, Math.floor(dlg.diligencePushRemainingBudget));
1730
+ }
1731
+ else {
1732
+ dlg.diligencePushRemainingBudget = Math.max(0, Math.floor(dlg.diligencePushRemainingBudget));
1733
+ }
1734
+ void persistence_1.DialogPersistence.mutateDialogLatest(dlg.id, () => ({
1735
+ kind: 'patch',
1736
+ patch: { diligencePushRemainingBudget: dlg.diligencePushRemainingBudget },
1737
+ }));
1738
+ }
1739
+ }
1740
+ catch (err) {
1741
+ log_1.log.warn('Failed to check Q4H state for Diligence Push reset', err, {
1742
+ dialogId: dlg.id.valueOf(),
1743
+ });
1744
+ }
1745
+ break;
1746
+ }
1747
+ // Continue only when this round executed function tools.
1748
+ // Tellask-only rounds must stop and wait for sideline/backfill feedback.
1749
+ const shouldContinue = funcCalls.length > 0 || (funcResults.length > 0 && funcCalls.length === 0);
1750
+ if (!shouldContinue) {
1751
+ // Diligence Push (root dialog only): prevent ALL stopping except legitimate suspension.
1752
+ // If disabled (empty diligence file) or budget exhausted, we suspend via Q4H.
1753
+ if (dlg instanceof dialog_1.RootDialog) {
1754
+ const suspension = await dlg.getSuspensionStatus();
1755
+ if (!suspension.canDrive) {
1756
+ if (suspension.q4h) {
1757
+ const configuredMax = resolveMemberDiligencePushMax(team, dlg.agentId);
1758
+ if (typeof configuredMax === 'number' && Number.isFinite(configuredMax)) {
1759
+ const next = Math.floor(configuredMax);
1760
+ dlg.diligencePushRemainingBudget =
1761
+ next > 0 ? next : Math.max(0, Math.floor(dlg.diligencePushRemainingBudget));
1762
+ }
1763
+ else {
1764
+ dlg.diligencePushRemainingBudget = Math.max(0, Math.floor(dlg.diligencePushRemainingBudget));
1765
+ }
1766
+ void persistence_1.DialogPersistence.mutateDialogLatest(dlg.id, () => ({
1767
+ kind: 'patch',
1768
+ patch: { diligencePushRemainingBudget: dlg.diligencePushRemainingBudget },
1769
+ }));
1770
+ }
1771
+ break;
1772
+ }
1773
+ const hasQueuedResponses = await hasQueuedSubdialogResponses(dlg.id);
1774
+ if (hasQueuedResponses) {
1775
+ log_1.log.info('Skip diligence prompt while subdialog responses are still queued', {
1776
+ dialogId: dlg.id.valueOf(),
1777
+ });
1778
+ break;
1779
+ }
1780
+ const prepared = await maybePrepareDiligenceAutoContinuePrompt({
1781
+ dlg,
1782
+ isRootDialog: true,
1783
+ remainingBudget: dlg.diligencePushRemainingBudget,
1784
+ diligencePushMax: resolveMemberDiligencePushMax(team, dlg.agentId),
1785
+ suppressDiligencePush: suppressDiligencePushForDrive,
1786
+ });
1787
+ dlg.diligencePushRemainingBudget = prepared.nextRemainingBudget;
1788
+ void persistence_1.DialogPersistence.mutateDialogLatest(dlg.id, () => ({
1789
+ kind: 'patch',
1790
+ patch: { diligencePushRemainingBudget: dlg.diligencePushRemainingBudget },
1791
+ }));
1792
+ if (prepared.kind !== 'disabled') {
1793
+ (0, evt_registry_1.postDialogEvent)(dlg, {
1794
+ type: 'diligence_budget_evt',
1795
+ maxInjectCount: prepared.maxInjectCount,
1796
+ injectedCount: Math.max(0, prepared.maxInjectCount - prepared.nextRemainingBudget),
1797
+ remainingCount: Math.max(0, prepared.nextRemainingBudget),
1798
+ disableDiligencePush: dlg.disableDiligencePush || suppressDiligencePushForDrive,
1799
+ });
1800
+ }
1801
+ if (prepared.kind === 'budget_exhausted') {
1802
+ await suspendForKeepGoingBudgetExhausted({
1803
+ dlg,
1804
+ maxInjectCount: prepared.maxInjectCount,
1805
+ });
1806
+ dlg.diligencePushRemainingBudget = 0;
1807
+ break;
1808
+ }
1809
+ if (prepared.kind === 'prompt') {
1810
+ pendingPrompt = prepared.prompt;
1811
+ continue;
1812
+ }
1813
+ }
1814
+ break;
1815
+ }
1816
+ continue;
1817
+ }
1818
+ else {
1819
+ const newMsgs = [];
1820
+ const streamedFuncCalls = [];
1821
+ // Track thinking content for signature extraction during streaming
1822
+ let currentThinkingContent = '';
1823
+ let currentThinkingSignature = '';
1824
+ let currentSayingContent = '';
1825
+ let sawAnyStreamContent = false;
1826
+ // Create receiver using shared helper (unified TellaskStreamParser integration)
1827
+ const receiver = createSayingEventsReceiver(dlg);
1828
+ // Direct streaming parser that forwards events without state tracking
1829
+ const parser = new tellask_1.TellaskStreamParser(receiver);
1830
+ let streamActive = { kind: 'idle' };
1831
+ let streamResult;
1832
+ try {
1833
+ streamResult = await runLlmRequestWithRetry({
1834
+ dlg,
1835
+ provider,
1836
+ abortSignal,
1837
+ maxRetries: 5,
1838
+ canRetry: () => !sawAnyStreamContent,
1839
+ doRequest: async () => {
1840
+ return await llmGen.genToReceiver(providerCfg, agent, systemPrompt, funcTools, ctxMsgsForGen, {
1841
+ streamError: async (detail) => {
1842
+ await dlg.streamError(detail);
1843
+ },
1844
+ thinkingStart: async () => {
1845
+ throwIfAborted(abortSignal, dlg.id);
1846
+ sawAnyStreamContent = true;
1847
+ if (streamActive.kind !== 'idle') {
1848
+ const detail = `Protocol violation: thinkingStart while ${streamActive.kind} is active (genseq=${String(dlg.activeGenSeq)}, provider=${providerCfg.apiType})`;
1849
+ log_1.log.error(detail, new Error('stream_overlap_violation'));
1850
+ await dlg.streamError(detail);
1851
+ throw new Error(detail);
1852
+ }
1853
+ streamActive = { kind: 'thinking' };
1854
+ currentThinkingContent = '';
1855
+ currentThinkingSignature = '';
1856
+ await dlg.thinkingStart();
1857
+ },
1858
+ thinkingChunk: async (chunk) => {
1859
+ throwIfAborted(abortSignal, dlg.id);
1860
+ sawAnyStreamContent = true;
1861
+ currentThinkingContent += chunk;
1862
+ // Extract Anthropic thinking signature from content
1863
+ const signatureMatch = currentThinkingContent.match(/<thinking[^>]*>(.*?)<\/thinking>/s);
1864
+ if (signatureMatch && signatureMatch[1]) {
1865
+ currentThinkingSignature = signatureMatch[1].trim();
1866
+ }
1867
+ await dlg.thinkingChunk(chunk);
1868
+ },
1869
+ thinkingFinish: async () => {
1870
+ throwIfAborted(abortSignal, dlg.id);
1871
+ if (streamActive.kind !== 'thinking') {
1872
+ const detail = `Protocol violation: thinkingFinish while ${streamActive.kind} is active (genseq=${String(dlg.activeGenSeq)}, provider=${providerCfg.apiType})`;
1873
+ log_1.log.error(detail, new Error('stream_overlap_violation'));
1874
+ await dlg.streamError(detail);
1875
+ throw new Error(detail);
1876
+ }
1877
+ streamActive = { kind: 'idle' };
1878
+ // Create thinking message with genseq and signature
1879
+ const genseq = dlg.activeGenSeq;
1880
+ if (genseq) {
1881
+ const thinkingMessage = {
1882
+ type: 'thinking_msg',
1883
+ role: 'assistant',
1884
+ genseq,
1885
+ content: currentThinkingContent,
1886
+ provider_data: currentThinkingSignature
1887
+ ? { signature: currentThinkingSignature }
1888
+ : undefined,
1889
+ };
1890
+ newMsgs.push(thinkingMessage);
1891
+ }
1892
+ await dlg.thinkingFinish();
1893
+ },
1894
+ sayingStart: async () => {
1895
+ throwIfAborted(abortSignal, dlg.id);
1896
+ sawAnyStreamContent = true;
1897
+ if (streamActive.kind !== 'idle') {
1898
+ const detail = `Protocol violation: sayingStart while ${streamActive.kind} is active (genseq=${String(dlg.activeGenSeq)}, provider=${providerCfg.apiType})`;
1899
+ log_1.log.error(detail, new Error('stream_overlap_violation'));
1900
+ await dlg.streamError(detail);
1901
+ throw new Error(detail);
1902
+ }
1903
+ streamActive = { kind: 'saying' };
1904
+ currentSayingContent = '';
1905
+ await dlg.sayingStart();
1906
+ },
1907
+ sayingChunk: async (chunk) => {
1908
+ throwIfAborted(abortSignal, dlg.id);
1909
+ sawAnyStreamContent = true;
1910
+ currentSayingContent += chunk;
1911
+ await parser.takeUpstreamChunk(chunk);
1912
+ // Dialog store handles persistence - maintain ordering guarantee
1913
+ await dlg.sayingChunk(chunk);
1914
+ },
1915
+ sayingFinish: async () => {
1916
+ throwIfAborted(abortSignal, dlg.id);
1917
+ if (streamActive.kind !== 'saying') {
1918
+ const detail = `Protocol violation: sayingFinish while ${streamActive.kind} is active (genseq=${String(dlg.activeGenSeq)}, provider=${providerCfg.apiType})`;
1919
+ log_1.log.error(detail, new Error('stream_overlap_violation'));
1920
+ await dlg.streamError(detail);
1921
+ throw new Error(detail);
1922
+ }
1923
+ streamActive = { kind: 'idle' };
1924
+ await parser.finalize();
1925
+ const sayingMessage = {
1926
+ type: 'saying_msg',
1927
+ role: 'assistant',
1928
+ genseq: dlg.activeGenSeq,
1929
+ content: currentSayingContent,
1930
+ };
1931
+ newMsgs.push(sayingMessage);
1932
+ lastAssistantSayingContent = currentSayingContent;
1933
+ await dlg.sayingFinish();
1934
+ },
1935
+ funcCall: async (callId, name, args) => {
1936
+ throwIfAborted(abortSignal, dlg.id);
1937
+ sawAnyStreamContent = true;
1938
+ const genseq = dlg.activeGenSeq;
1939
+ if (genseq === undefined) {
1940
+ return;
1941
+ }
1942
+ streamedFuncCalls.push({
1943
+ type: 'func_call_msg',
1944
+ role: 'assistant',
1945
+ genseq,
1946
+ id: callId,
1947
+ name,
1948
+ arguments: args,
1949
+ });
1950
+ },
1951
+ webSearchCall: async (call) => {
1952
+ throwIfAborted(abortSignal, dlg.id);
1953
+ sawAnyStreamContent = true;
1954
+ await dlg.webSearchCall(call);
1955
+ },
1956
+ }, dlg.activeGenSeq, abortSignal);
1957
+ },
1958
+ });
1959
+ }
1960
+ catch (err) {
1961
+ if (abortSignal.aborted) {
1962
+ throwIfAborted(abortSignal, dlg.id);
1963
+ }
1964
+ generationHadError = true;
1965
+ log_1.log.error(`LLM gen error:`, err);
1966
+ throw err;
1967
+ }
1968
+ if (!streamResult) {
1969
+ throw new Error('Internal error: missing stream result after successful generation');
1970
+ }
1971
+ if (typeof streamResult.llmGenModel === 'string' &&
1972
+ streamResult.llmGenModel.trim() !== '') {
1973
+ llmGenModelForGen = streamResult.llmGenModel.trim();
1974
+ }
1975
+ if (!agent.model) {
1976
+ throw new Error(`Internal error: Model is undefined for agent '${agent.id}'`);
1977
+ }
1978
+ contextHealthForGen = computeContextHealthSnapshot({
1979
+ providerCfg,
1980
+ model: agent.model,
1981
+ usage: streamResult.usage,
1982
+ });
1983
+ dlg.setLastContextHealth(contextHealthForGen);
1984
+ // Execute collected calls concurrently after streaming completes
1985
+ const collectedCalls = parser.getCollectedCalls();
1986
+ const malformedToolOutputs = await emitMalformedTellaskResponses(dlg, collectedCalls);
1987
+ if (collectedCalls.length > 0 && !collectedCalls[0].callId) {
1988
+ throw new Error('Collected calls missing callId - parser should have allocated one per call');
1989
+ }
1990
+ const validCalls = collectedCalls.filter((call) => call.validation.kind === 'valid');
1991
+ const streamingPolicyViolation = resolveDrivePolicyViolationKind({
1992
+ policy: drivePolicy,
1993
+ tellaskCalls: collectedCalls,
1994
+ functionCallCount: streamedFuncCalls.length,
1995
+ });
1996
+ if (streamingPolicyViolation) {
1997
+ const violationText = (0, driver_messages_1.formatDomindsNoteFbrToollessViolation)((0, runtime_language_1.getWorkLanguage)(), {
1998
+ kind: streamingPolicyViolation,
1999
+ });
2000
+ const genseq = dlg.activeGenSeq ?? 0;
2001
+ newMsgs.push({
2002
+ type: 'saying_msg',
2003
+ role: 'assistant',
2004
+ genseq,
2005
+ content: violationText,
2006
+ });
2007
+ lastAssistantSayingContent = violationText;
2008
+ await dlg.addChatMessages(...newMsgs);
2009
+ await dlg.persistAgentMessage(violationText, genseq, 'saying_msg');
2010
+ return { lastAssistantSayingContent, interrupted: false };
2011
+ }
2012
+ throwIfAborted(abortSignal, dlg.id);
2013
+ const results = await Promise.all(validCalls.map((call) => executeTellaskCall(dlg, agent, call.validation.firstMention, call.tellaskHead, call.body, call.callId)));
2014
+ if (dlg.hasUpNext()) {
2015
+ return { lastAssistantSayingContent, interrupted: false };
2016
+ }
2017
+ // Combine results from all concurrent calls.
2018
+ if (malformedToolOutputs.length > 0) {
2019
+ newMsgs.push(...malformedToolOutputs);
2020
+ }
2021
+ for (const result of results) {
2022
+ if (result.toolOutputs.length > 0) {
2023
+ newMsgs.push(...result.toolOutputs);
2024
+ }
2025
+ if (result.suspend) {
2026
+ suspendForHuman = true;
2027
+ }
2028
+ }
2029
+ const funcResults = [];
2030
+ if (streamedFuncCalls.length > 0) {
2031
+ const functionPromises = streamedFuncCalls.map(async (func) => {
2032
+ throwIfAborted(abortSignal, dlg.id);
2033
+ // Use the genseq from the func_call_msg to ensure tool results share the same generation sequence
2034
+ // This is critical for correct grouping in reconstructAnthropicContext()
2035
+ const callGenseq = func.genseq;
2036
+ // Use the LLM-allocated unique id for tracking
2037
+ // This id comes from func_call_msg and is the proper unique identifier
2038
+ const callId = func.id;
2039
+ // argsStr is still needed for UI event (funcCallRequested)
2040
+ const argsStr = typeof func.arguments === 'string'
2041
+ ? func.arguments
2042
+ : JSON.stringify(func.arguments ?? {});
2043
+ const tool = agentTools.find((t) => t.type === 'func' && t.name === func.name);
2044
+ if (!tool) {
2045
+ const errorResult = {
2046
+ type: 'func_result_msg',
2047
+ id: func.id,
2048
+ name: func.name,
2049
+ content: `Tool '${func.name}' not found`,
2050
+ role: 'tool',
2051
+ genseq: callGenseq,
2052
+ };
2053
+ await dlg.receiveFuncResult(errorResult);
2054
+ return errorResult;
2055
+ }
2056
+ let rawArgs = {};
2057
+ if (typeof func.arguments === 'string' && func.arguments.trim()) {
2058
+ try {
2059
+ rawArgs = JSON.parse(func.arguments);
2060
+ }
2061
+ catch (parseErr) {
2062
+ rawArgs = null;
2063
+ log_1.log.warn('Failed to parse function arguments as JSON', {
2064
+ funcName: func.name,
2065
+ arguments: func.arguments,
2066
+ error: parseErr,
2067
+ });
2068
+ }
2069
+ }
2070
+ let result;
2071
+ const argsValidation = validateFuncToolArguments(tool, rawArgs);
2072
+ if (argsValidation.ok) {
2073
+ const argsObj = argsValidation.args;
2074
+ // Emit func_call_requested event to build the func-call section UI
2075
+ try {
2076
+ await dlg.funcCallRequested(func.id, func.name, argsStr);
2077
+ }
2078
+ catch (err) {
2079
+ log_1.log.warn('Failed to emit func_call_requested event', err);
2080
+ }
2081
+ try {
2082
+ await dlg.persistFunctionCall(func.id, func.name, argsObj, callGenseq);
2083
+ }
2084
+ catch (err) {
2085
+ log_1.log.warn('Failed to persist function call', err);
2086
+ }
2087
+ try {
2088
+ throwIfAborted(abortSignal, dlg.id);
2089
+ const output = await tool.call(dlg, agent, argsObj);
2090
+ const normalized = typeof output === 'string'
2091
+ ? { content: output, contentItems: undefined }
2092
+ : {
2093
+ content: typeof output.content === 'string' ? output.content : String(output),
2094
+ contentItems: Array.isArray(output.contentItems)
2095
+ ? output.contentItems
2096
+ : undefined,
2097
+ };
2098
+ result = {
2099
+ type: 'func_result_msg',
2100
+ id: func.id,
2101
+ name: func.name,
2102
+ content: String(normalized.content),
2103
+ contentItems: normalized.contentItems,
2104
+ role: 'tool',
2105
+ genseq: callGenseq,
2106
+ };
2107
+ }
2108
+ catch (err) {
2109
+ result = {
2110
+ type: 'func_result_msg',
2111
+ id: func.id,
2112
+ name: func.name,
2113
+ content: `Function '${func.name}' execution failed: ${showErrorToAi(err)}`,
2114
+ role: 'tool',
2115
+ genseq: callGenseq,
2116
+ };
2117
+ }
2118
+ }
2119
+ else {
2120
+ result = {
2121
+ type: 'func_result_msg',
2122
+ id: func.id,
2123
+ name: func.name,
2124
+ content: `Invalid arguments: ${argsValidation.error}`,
2125
+ role: 'tool',
2126
+ genseq: callGenseq,
2127
+ };
2128
+ }
2129
+ await dlg.receiveFuncResult(result);
2130
+ return result;
2131
+ });
2132
+ funcResults.push(...(await Promise.all(functionPromises)));
2133
+ }
2134
+ if (streamedFuncCalls.length > 0) {
2135
+ for (let i = 0; i < streamedFuncCalls.length; i++) {
2136
+ newMsgs.push(streamedFuncCalls[i]);
2137
+ if (i < funcResults.length) {
2138
+ newMsgs.push(funcResults[i]);
2139
+ }
2140
+ }
2141
+ }
2142
+ await dlg.addChatMessages(...newMsgs);
2143
+ if (dlg.hasUpNext()) {
2144
+ pendingPrompt = resolveUpNextPrompt(dlg);
2145
+ continue;
2146
+ }
2147
+ // After tool execution, check latest remindersVer with published info,
2148
+ // publish new version to propagate updated reminders to ui
2149
+ if (dlg.remindersVer > pubRemindersVer) {
2150
+ try {
2151
+ await dlg.processReminderUpdates();
2152
+ pubRemindersVer = dlg.remindersVer;
2153
+ }
2154
+ catch (err) {
2155
+ log_1.log.warn('Failed to propagate reminder text after tools', err);
2156
+ }
2157
+ }
2158
+ await Promise.resolve();
2159
+ if (suspendForHuman) {
2160
+ try {
2161
+ // Q4H suspension resets Diligence Push budget so post-Q4H continuation gets a fresh counter.
2162
+ if (await dlg.hasPendingQ4H()) {
2163
+ const configuredMax = resolveMemberDiligencePushMax(team, dlg.agentId);
2164
+ if (typeof configuredMax === 'number' && Number.isFinite(configuredMax)) {
2165
+ const next = Math.floor(configuredMax);
2166
+ dlg.diligencePushRemainingBudget =
2167
+ next > 0 ? next : Math.max(0, Math.floor(dlg.diligencePushRemainingBudget));
2168
+ }
2169
+ else {
2170
+ dlg.diligencePushRemainingBudget = Math.max(0, Math.floor(dlg.diligencePushRemainingBudget));
2171
+ }
2172
+ }
2173
+ }
2174
+ catch (err) {
2175
+ log_1.log.warn('Failed to check Q4H state for Diligence Push reset', err, {
2176
+ dialogId: dlg.id.valueOf(),
2177
+ });
2178
+ }
2179
+ break;
2180
+ }
2181
+ // Continue only when this round executed function tools.
2182
+ // Tellask-only rounds must stop and wait for sideline/backfill feedback.
2183
+ const shouldContinue = streamedFuncCalls.length > 0 || funcResults.length > 0;
2184
+ if (!shouldContinue) {
2185
+ // Diligence Push (root dialog only): prevent ALL stopping except legitimate suspension.
2186
+ if (dlg instanceof dialog_1.RootDialog) {
2187
+ const suspension = await dlg.getSuspensionStatus();
2188
+ if (!suspension.canDrive) {
2189
+ if (suspension.q4h) {
2190
+ const configuredMax = resolveMemberDiligencePushMax(team, dlg.agentId);
2191
+ if (typeof configuredMax === 'number' && Number.isFinite(configuredMax)) {
2192
+ const next = Math.floor(configuredMax);
2193
+ dlg.diligencePushRemainingBudget =
2194
+ next > 0 ? next : Math.max(0, Math.floor(dlg.diligencePushRemainingBudget));
2195
+ }
2196
+ else {
2197
+ dlg.diligencePushRemainingBudget = Math.max(0, Math.floor(dlg.diligencePushRemainingBudget));
2198
+ }
2199
+ void persistence_1.DialogPersistence.mutateDialogLatest(dlg.id, () => ({
2200
+ kind: 'patch',
2201
+ patch: { diligencePushRemainingBudget: dlg.diligencePushRemainingBudget },
2202
+ }));
2203
+ }
2204
+ break;
2205
+ }
2206
+ const hasQueuedResponses = await hasQueuedSubdialogResponses(dlg.id);
2207
+ if (hasQueuedResponses) {
2208
+ log_1.log.info('Skip diligence prompt while subdialog responses are still queued', {
2209
+ dialogId: dlg.id.valueOf(),
2210
+ });
2211
+ break;
2212
+ }
2213
+ const prepared = await maybePrepareDiligenceAutoContinuePrompt({
2214
+ dlg,
2215
+ isRootDialog: true,
2216
+ remainingBudget: dlg.diligencePushRemainingBudget,
2217
+ diligencePushMax: resolveMemberDiligencePushMax(team, dlg.agentId),
2218
+ suppressDiligencePush: suppressDiligencePushForDrive,
2219
+ });
2220
+ dlg.diligencePushRemainingBudget = prepared.nextRemainingBudget;
2221
+ void persistence_1.DialogPersistence.mutateDialogLatest(dlg.id, () => ({
2222
+ kind: 'patch',
2223
+ patch: { diligencePushRemainingBudget: dlg.diligencePushRemainingBudget },
2224
+ }));
2225
+ if (prepared.kind !== 'disabled') {
2226
+ (0, evt_registry_1.postDialogEvent)(dlg, {
2227
+ type: 'diligence_budget_evt',
2228
+ maxInjectCount: prepared.maxInjectCount,
2229
+ injectedCount: Math.max(0, prepared.maxInjectCount - prepared.nextRemainingBudget),
2230
+ remainingCount: Math.max(0, prepared.nextRemainingBudget),
2231
+ disableDiligencePush: dlg.disableDiligencePush || suppressDiligencePushForDrive,
2232
+ });
2233
+ }
2234
+ if (prepared.kind === 'budget_exhausted') {
2235
+ await suspendForKeepGoingBudgetExhausted({
2236
+ dlg,
2237
+ maxInjectCount: prepared.maxInjectCount,
2238
+ });
2239
+ dlg.diligencePushRemainingBudget = 0;
2240
+ break;
2241
+ }
2242
+ if (prepared.kind === 'prompt') {
2243
+ pendingPrompt = prepared.prompt;
2244
+ continue;
2245
+ }
2246
+ }
2247
+ break;
2248
+ }
2249
+ }
2250
+ }
2251
+ finally {
2252
+ await dlg.notifyGeneratingFinish(contextHealthForGen, llmGenModelForGen);
2253
+ }
2254
+ }
2255
+ finalRunState = await (0, dialog_run_state_1.computeIdleRunState)(dlg);
2256
+ return { lastAssistantSayingContent, interrupted: false };
2257
+ }
2258
+ catch (err) {
2259
+ const stopRequested = (0, dialog_run_state_1.getStopRequestedReason)(dlg.id);
2260
+ const interruptedReason = err instanceof DialogInterruptedError
2261
+ ? err.reason
2262
+ : abortSignal.aborted
2263
+ ? stopRequested === 'emergency_stop'
2264
+ ? { kind: 'emergency_stop' }
2265
+ : stopRequested === 'user_stop'
2266
+ ? { kind: 'user_stop' }
2267
+ : { kind: 'system_stop', detail: 'Aborted.' }
2268
+ : undefined;
2269
+ if (interruptedReason) {
2270
+ // If subdialog responses were taken during this drive, interruption means they
2271
+ // should be rolled back for retry instead of being committed as consumed.
2272
+ generationHadError = true;
2273
+ finalRunState = { kind: 'interrupted', reason: interruptedReason };
2274
+ (0, dialog_run_state_1.broadcastRunStateMarker)(dlg.id, { kind: 'interrupted', reason: interruptedReason });
2275
+ return { lastAssistantSayingContent, interrupted: true };
2276
+ }
2277
+ generationHadError = true;
2278
+ const errText = (0, log_1.extractErrorDetails)(err).message;
2279
+ try {
2280
+ await dlg.streamError(errText);
2281
+ }
2282
+ catch (_emitErr) {
2283
+ // best-effort
2284
+ }
2285
+ finalRunState = { kind: 'interrupted', reason: { kind: 'system_stop', detail: errText } };
2286
+ (0, dialog_run_state_1.broadcastRunStateMarker)(dlg.id, {
2287
+ kind: 'interrupted',
2288
+ reason: { kind: 'system_stop', detail: errText },
2289
+ });
2290
+ return { lastAssistantSayingContent, interrupted: true };
2291
+ }
2292
+ finally {
2293
+ (0, dialog_run_state_1.clearActiveRun)(dlg.id);
2294
+ if (!finalRunState) {
2295
+ try {
2296
+ finalRunState = await (0, dialog_run_state_1.computeIdleRunState)(dlg);
2297
+ }
2298
+ catch (stateErr) {
2299
+ log_1.log.warn('Failed to compute final run state; falling back to idle', stateErr, {
2300
+ dialogId: dlg.id.valueOf(),
2301
+ });
2302
+ finalRunState = { kind: 'idle_waiting_user' };
2303
+ }
2304
+ }
2305
+ // "dead" is irreversible. If another actor declared this dialog dead during an in-flight
2306
+ // generation, do not overwrite it with a computed idle/interrupted state.
2307
+ try {
2308
+ const latest = await persistence_1.DialogPersistence.loadDialogLatest(dlg.id, 'running');
2309
+ if (dlg.id.selfId !== dlg.id.rootId &&
2310
+ latest &&
2311
+ latest.runState &&
2312
+ latest.runState.kind === 'dead') {
2313
+ finalRunState = latest.runState;
2314
+ }
2315
+ }
2316
+ catch (err) {
2317
+ log_1.log.warn('Failed to re-check runState before finalizing; proceeding best-effort', err, {
2318
+ dialogId: dlg.id.valueOf(),
2319
+ });
2320
+ }
2321
+ await (0, dialog_run_state_1.setDialogRunState)(dlg.id, finalRunState);
2322
+ if (tookSubdialogResponses) {
2323
+ try {
2324
+ await withSuspensionStateLock(dlg.id, async () => {
2325
+ if (generationHadError) {
2326
+ await persistence_1.DialogPersistence.rollbackTakenSubdialogResponses(dlg.id);
2327
+ }
2328
+ else {
2329
+ await persistence_1.DialogPersistence.commitTakenSubdialogResponses(dlg.id);
2330
+ committedTakenSubdialogResponses = true;
2331
+ }
2332
+ });
2333
+ }
2334
+ catch (err2) {
2335
+ log_1.log.warn('Failed to finalize subdialog response queue after drive', {
2336
+ dialogId: dlg.id.selfId,
2337
+ error: err2,
2338
+ });
2339
+ }
2340
+ }
2341
+ if (committedTakenSubdialogResponses && takenSubdialogResponses.length > 0) {
2342
+ try {
2343
+ const mirroredMsgs = takenSubdialogResponses.map((response) => ({
2344
+ type: 'tellask_result_msg',
2345
+ role: 'tool',
2346
+ responderId: response.responderId,
2347
+ tellaskHead: response.tellaskHead,
2348
+ status: response.status ?? 'completed',
2349
+ content: (0, inter_dialog_format_1.formatTeammateResponseContent)({
2350
+ responderId: response.responderId,
2351
+ requesterId: response.originMemberId,
2352
+ originalCallHeadLine: response.tellaskHead,
2353
+ responseBody: response.response,
2354
+ language: (0, runtime_language_1.getWorkLanguage)(),
2355
+ }),
2356
+ }));
2357
+ await dlg.addChatMessages(...mirroredMsgs);
2358
+ }
2359
+ catch (err) {
2360
+ log_1.log.warn('Failed to mirror committed subdialog responses into dialog msgs', {
2361
+ dialogId: dlg.id.selfId,
2362
+ error: err,
2363
+ });
2364
+ }
2365
+ }
2366
+ }
2367
+ } // Close while loop
2368
+ // Dialog stream has completed - no need to mark queue as complete since we're using receivers
2369
+ // === SINGLE DIALOG HIERARCHY RESTORATION API ===
2370
+ /**
2371
+ * Single API for restoring the complete dialog hierarchy (main dialog + all subdialogs)
2372
+ * This is the only public restoration API - all serialization is implicit
2373
+ */
2374
+ async function restoreDialogHierarchy(rootDialogId, status = 'running') {
2375
+ try {
2376
+ const rootDialogIdent = new dialog_1.DialogID(rootDialogId);
2377
+ // Assert that the ID refers to a root dialog, not a subdialog selfId.
2378
+ const rootMeta = await persistence_1.DialogPersistence.loadRootDialogMetadata(rootDialogIdent, status);
2379
+ if (rootMeta?.supdialogId) {
2380
+ throw new Error(`Expected root dialog ${rootDialogId} but found subdialog metadata with supdialogId: ${rootMeta.supdialogId}`);
2381
+ }
2382
+ const rootDialog = await (0, dialog_instance_registry_1.getOrRestoreRootDialog)(rootDialogId, status);
2383
+ if (!rootDialog) {
2384
+ throw new Error(`Failed to restore dialog hierarchy for ${rootDialogId} from status ${status}`);
2385
+ }
2386
+ dialog_global_registry_1.globalDialogRegistry.register(rootDialog);
2387
+ // Restore all subdialogs under this root by reading the persisted subdialogs directory,
2388
+ // then ensuring each subdialog is loaded into the root dialog's local registry.
2389
+ const subdialogs = new Map();
2390
+ const rootPath = persistence_1.DialogPersistence.getRootDialogPath(rootDialogIdent, status);
2391
+ const subPath = path.join(rootPath, persistence_1.DialogPersistence.SUBDIALOGS_DIR);
2392
+ let allSubdialogIds = [];
2393
+ try {
2394
+ const entries = await fs.promises.readdir(subPath, { withFileTypes: true });
2395
+ allSubdialogIds = entries.filter((e) => e.isDirectory()).map((e) => e.name);
2396
+ }
2397
+ catch (err) {
2398
+ const code = typeof err === 'object' && err !== null && 'code' in err
2399
+ ? err.code
2400
+ : undefined;
2401
+ if (code !== 'ENOENT') {
2402
+ log_1.log.warn(`Failed to read subdialogs directory: ${subPath}, returning empty array`, err);
2403
+ }
2404
+ allSubdialogIds = [];
2405
+ }
2406
+ for (const subdialogId of allSubdialogIds) {
2407
+ const restoredSubdialogId = new dialog_1.DialogID(subdialogId, rootDialog.id.rootId);
2408
+ const dialog = await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, restoredSubdialogId, status);
2409
+ if (dialog && dialog.id.selfId !== dialog.id.rootId) {
2410
+ subdialogs.set(subdialogId, dialog);
2411
+ }
2412
+ }
2413
+ // Calculate summary statistics
2414
+ let totalMessages = rootDialog.msgs.length;
2415
+ let totalCourses = rootDialog.currentCourse;
2416
+ for (const dlg of subdialogs.values()) {
2417
+ totalMessages += dlg.msgs.length;
2418
+ if (dlg.currentCourse > totalCourses)
2419
+ totalCourses = dlg.currentCourse;
2420
+ }
2421
+ const summary = {
2422
+ totalMessages,
2423
+ totalCourses,
2424
+ completionStatus: 'incomplete',
2425
+ };
2426
+ return {
2427
+ rootDialog,
2428
+ subdialogs,
2429
+ summary,
2430
+ };
2431
+ }
2432
+ catch (error) {
2433
+ log_1.log.error(`Failed to restore dialog hierarchy for ${rootDialogId}:`, error);
2434
+ throw error;
2435
+ }
2436
+ }
2437
+ function isValidTellaskSession(tellaskSession) {
2438
+ const segments = tellaskSession.split('.');
2439
+ if (segments.length === 0)
2440
+ return false;
2441
+ return segments.every((segment) => /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(segment));
2442
+ }
2443
+ function parseTellaskSessionDirectiveFromHeadline(tellaskHead) {
2444
+ const re = /(^|\s)!tellaskSession\s+([^\s]+)/g;
2445
+ const ids = [];
2446
+ for (const match of tellaskHead.matchAll(re)) {
2447
+ const raw = match[2] ?? '';
2448
+ const candidate = raw.trim();
2449
+ const m = candidate.match(/^([a-zA-Z][a-zA-Z0-9_-]*(?:\.[a-zA-Z0-9_-]+)*)/);
2450
+ const tellaskSession = (m?.[1] ?? '').trim();
2451
+ if (!isValidTellaskSession(tellaskSession)) {
2452
+ return { kind: 'invalid' };
2453
+ }
2454
+ ids.push(tellaskSession);
2455
+ }
2456
+ const unique = Array.from(new Set(ids));
2457
+ if (unique.length === 0)
2458
+ return { kind: 'none' };
2459
+ if (unique.length === 1)
2460
+ return { kind: 'one', tellaskSession: unique[0] ?? '' };
2461
+ return { kind: 'multiple' };
2462
+ }
2463
+ function extractSingleTellaskSessionFromHeadline(tellaskHead) {
2464
+ const parsed = parseTellaskSessionDirectiveFromHeadline(tellaskHead);
2465
+ if (parsed.kind === 'one')
2466
+ return parsed.tellaskSession;
2467
+ return null;
2468
+ }
2469
+ function isFbrSelfTellaskHeadLine(tellaskHead) {
2470
+ return /^\s*@self\b/.test(tellaskHead);
2471
+ }
2472
+ function replaceTellaskSessionDirective(tellaskHead, tellaskSession) {
2473
+ // Replace the first occurrence only. If missing, append it as a best-effort.
2474
+ const re = /(^|\s)!tellaskSession\s+([^\s]+)/;
2475
+ if (re.test(tellaskHead)) {
2476
+ return tellaskHead.replace(re, (m, p1) => `${String(p1)}!tellaskSession ${tellaskSession}`);
2477
+ }
2478
+ return `${tellaskHead} !tellaskSession ${tellaskSession}`;
2479
+ }
2480
+ function resolveFbrEffort(member) {
2481
+ const raw = member?.fbr_effort;
2482
+ if (typeof raw !== 'number' || !Number.isFinite(raw))
2483
+ return 0;
2484
+ if (!Number.isInteger(raw))
2485
+ return 0;
2486
+ if (raw < 0)
2487
+ return 0;
2488
+ if (raw > 100)
2489
+ return 0;
2490
+ return raw;
2491
+ }
2492
+ function mergeModelParams(base, overlay) {
2493
+ if (!base && !overlay)
2494
+ return undefined;
2495
+ if (!base)
2496
+ return overlay;
2497
+ if (!overlay)
2498
+ return base;
2499
+ return {
2500
+ max_tokens: overlay.max_tokens ?? base.max_tokens,
2501
+ codex: { ...(base.codex ?? {}), ...(overlay.codex ?? {}) },
2502
+ openai: { ...(base.openai ?? {}), ...(overlay.openai ?? {}) },
2503
+ anthropic: { ...(base.anthropic ?? {}), ...(overlay.anthropic ?? {}) },
2504
+ };
2505
+ }
2506
+ function buildDrivePolicy(args) {
2507
+ const { dlg, agent, systemPrompt, agentTools, language } = args;
2508
+ if (!isToollessFbrSelfSubdialog(dlg)) {
2509
+ return {
2510
+ mode: 'default',
2511
+ effectiveAgent: agent,
2512
+ effectiveSystemPrompt: systemPrompt,
2513
+ effectiveAgentTools: agentTools,
2514
+ prependedContextMessages: [],
2515
+ tellaskPolicy: 'allow_any',
2516
+ allowFunctionCalls: true,
2517
+ };
2518
+ }
2519
+ const effectiveAgent = Object.assign(Object.create(agent), {
2520
+ model_params: mergeModelParams(agent.model_params, agent.fbr_model_params),
2521
+ });
2522
+ return {
2523
+ mode: 'fbr_toolless',
2524
+ effectiveAgent,
2525
+ effectiveSystemPrompt: buildFbrSystemPrompt(language),
2526
+ effectiveAgentTools: [],
2527
+ prependedContextMessages: [
2528
+ {
2529
+ type: 'environment_msg',
2530
+ role: 'user',
2531
+ content: (0, system_prompt_parts_1.buildNoToolsNotice)(language),
2532
+ },
2533
+ ],
2534
+ tellaskPolicy: 'tellasker_only',
2535
+ allowFunctionCalls: false,
2536
+ };
2537
+ }
2538
+ function hasTellaskPolicyViolation(policy, calls) {
2539
+ if (policy.tellaskPolicy === 'allow_any') {
2540
+ return false;
2541
+ }
2542
+ return calls.some((call) => {
2543
+ if (call.validation.kind !== 'valid') {
2544
+ return true;
2545
+ }
2546
+ return call.validation.firstMention !== 'tellasker';
2547
+ });
2548
+ }
2549
+ function resolveDrivePolicyViolationKind(args) {
2550
+ const tellaskViolation = hasTellaskPolicyViolation(args.policy, args.tellaskCalls);
2551
+ const toolViolation = !args.policy.allowFunctionCalls && args.functionCallCount > 0;
2552
+ if (tellaskViolation && toolViolation) {
2553
+ return 'tellask_and_tool';
2554
+ }
2555
+ if (tellaskViolation) {
2556
+ return 'tellask';
2557
+ }
2558
+ if (toolViolation) {
2559
+ return 'tool';
2560
+ }
2561
+ return null;
2562
+ }
2563
+ function validateDrivePolicyInvariants(policy, language) {
2564
+ if (policy.mode !== 'fbr_toolless') {
2565
+ return { ok: true };
2566
+ }
2567
+ const expectedSystemPrompt = buildFbrSystemPrompt(language);
2568
+ if (policy.effectiveSystemPrompt !== expectedSystemPrompt) {
2569
+ return {
2570
+ ok: false,
2571
+ detail: 'FBR must use buildFbrSystemPrompt(language) exactly.',
2572
+ };
2573
+ }
2574
+ if (policy.effectiveAgentTools.length > 0) {
2575
+ return {
2576
+ ok: false,
2577
+ detail: 'FBR effectiveAgentTools must be empty.',
2578
+ };
2579
+ }
2580
+ if (policy.allowFunctionCalls) {
2581
+ return {
2582
+ ok: false,
2583
+ detail: 'FBR allowFunctionCalls must be false.',
2584
+ };
2585
+ }
2586
+ if (policy.tellaskPolicy !== 'tellasker_only') {
2587
+ return {
2588
+ ok: false,
2589
+ detail: 'FBR tellaskPolicy must be tellasker_only.',
2590
+ };
2591
+ }
2592
+ const expectedNoToolsNotice = (0, system_prompt_parts_1.buildNoToolsNotice)(language);
2593
+ if (policy.prependedContextMessages.length !== 1) {
2594
+ return {
2595
+ ok: false,
2596
+ detail: 'FBR must prepend exactly one no-tools notice message.',
2597
+ };
2598
+ }
2599
+ const [notice] = policy.prependedContextMessages;
2600
+ if (!notice ||
2601
+ notice.type !== 'environment_msg' ||
2602
+ notice.role !== 'user' ||
2603
+ notice.content !== expectedNoToolsNotice) {
2604
+ return {
2605
+ ok: false,
2606
+ detail: 'FBR prepended notice must exactly match buildNoToolsNotice(language).',
2607
+ };
2608
+ }
2609
+ return { ok: true };
2610
+ }
2611
+ function buildFbrSystemPrompt(language) {
2612
+ const prefix = language === 'zh'
2613
+ ? [
2614
+ '# 扪心自问(FBR)支线对话',
2615
+ '',
2616
+ '- 你正在处理一次由 `!?@self` 发起的 FBR 支线对话。',
2617
+ '- 诉请正文是主要任务上下文;不要假设能访问诉请者对话历史。',
2618
+ '- 若使用可恢复的 `!tellaskSession` 形式,你可以使用本支线对话自身的 `tellaskSession` 历史作为显式上下文。',
2619
+ '- 若诉请正文缺少关键上下文,请在输出中列出缺失信息与阻塞原因。',
2620
+ '- 仅当必须澄清关键缺失上下文时,允许用 `!?@tellasker` 回问诉请者;除此之外不要发起任何诉请。',
2621
+ ].join('\n')
2622
+ : [
2623
+ '# Fresh Boots Reasoning (FBR) sideline dialog',
2624
+ '',
2625
+ '- This is an FBR sideline dialog created by `!?@self`.',
2626
+ '- The tellask body is the primary task context; do not assume access to tellasker dialog history.',
2627
+ '- If this is the resumable `!tellaskSession` form, you may use this sideline dialog’s own tellaskSession history as explicit context.',
2628
+ '- If the tellask body is missing critical context, list what is missing and why it blocks reasoning.',
2629
+ '- `!?@tellasker` is allowed only when you must clarify critical missing context; otherwise do not emit any tellasks.',
2630
+ ].join('\n');
2631
+ return prefix.trim();
2632
+ }
2633
+ function isToollessFbrSelfSubdialog(dlg) {
2634
+ return dlg instanceof dialog_1.SubDialog && isFbrSelfTellaskHeadLine(dlg.assignmentFromSup.tellaskHead);
2635
+ }
2636
+ /**
2637
+ * Parse a teammate tellask pattern and return the appropriate type result.
2638
+ *
2639
+ * Patterns:
2640
+ * - @<supdialogAgentId> (in subdialog context, matching supdialog.agentId) → Type A (supdialog suspension)
2641
+ * - @<agentId> !tellaskSession <tellaskSession> → Type B (registered subdialog)
2642
+ * - @<agentId> → Type C (transient subdialog)
2643
+ *
2644
+ * @param firstMention The first teammate mention extracted by the streaming parser (e.g., "teammate")
2645
+ * @param tellaskHead The full headline text from the streaming parser
2646
+ * @param currentDialog Optional current dialog context to detect Type A (subdialog calling parent)
2647
+ * @returns The parsed TeammateTellaskParseResult
2648
+ */
2649
+ function parseTeammateTellask(firstMention, tellaskHead, currentDialog) {
2650
+ // Fresh Boots Reasoning (FBR) syntax sugar:
2651
+ // `@self` always targets the current dialog's agentId (same persona/config).
2652
+ //
2653
+ // This avoids ambiguous `@teammate`-to-`@teammate` self-tellasks which can also be produced accidentally
2654
+ // by echoing/quoting an assignment headline. We keep parsing behavior the same for all other
2655
+ // mentions.
2656
+ if (firstMention === 'self') {
2657
+ const agentId = currentDialog?.agentId ?? 'self';
2658
+ const tellaskSession = extractSingleTellaskSessionFromHeadline(tellaskHead);
2659
+ if (tellaskSession) {
2660
+ return {
2661
+ type: 'B',
2662
+ agentId,
2663
+ tellaskSession,
2664
+ };
2665
+ }
2666
+ return {
2667
+ type: 'C',
2668
+ agentId,
2669
+ };
2670
+ }
2671
+ const tellaskSession = extractSingleTellaskSessionFromHeadline(tellaskHead);
2672
+ if (tellaskSession) {
2673
+ return {
2674
+ type: 'B',
2675
+ agentId: firstMention,
2676
+ tellaskSession,
2677
+ };
2678
+ }
2679
+ // Phase 11: Check if this is a Type A call (subdialog calling its direct parent)
2680
+ // Type A only applies when:
2681
+ // 1. A current dialog context is provided
2682
+ // 2. The current dialog is a SubDialog (has a supdialog)
2683
+ // 3. The @agentId matches the supdialog's agentId
2684
+ if (currentDialog &&
2685
+ currentDialog.supdialog &&
2686
+ firstMention === currentDialog.supdialog.agentId) {
2687
+ return {
2688
+ type: 'A',
2689
+ agentId: firstMention,
2690
+ };
2691
+ }
2692
+ // Type C: Any @agentId is a transient subdialog call
2693
+ return {
2694
+ type: 'C',
2695
+ agentId: firstMention,
2696
+ };
2697
+ }
2698
+ // === CONVENIENCE METHODS USING SINGLE RESTORATION API ===
2699
+ /**
2700
+ * Continue dialog with human response (uses single restoration API)
2701
+ */
2702
+ async function continueDialogWithHumanResponse(rootDialogId, humanPrompt, options) {
2703
+ try {
2704
+ // Restore the complete dialog hierarchy (pure restoration, no continuation)
2705
+ const result = await restoreDialogHierarchy(rootDialogId, 'running');
2706
+ // Then perform continuation separately
2707
+ if (options?.targetSubdialogId && result.subdialogs.has(options.targetSubdialogId)) {
2708
+ // Continue specific subdialog
2709
+ const targetSubdialog = result.subdialogs.get(options.targetSubdialogId);
2710
+ await driveDialogStream(targetSubdialog, humanPrompt);
2711
+ }
2712
+ else {
2713
+ // Continue root dialog
2714
+ await driveDialogStream(result.rootDialog, humanPrompt);
2715
+ }
2716
+ }
2717
+ catch (error) {
2718
+ log_1.log.error(`Failed to continue dialog with human response:`, error);
2719
+ throw error;
2720
+ }
2721
+ }
2722
+ /**
2723
+ * Continue root dialog with followup message (uses single restoration API)
2724
+ */
2725
+ async function continueRootDialog(rootDialogId, humanPrompt) {
2726
+ try {
2727
+ // Restore the complete dialog hierarchy (pure restoration, no continuation)
2728
+ const result = await restoreDialogHierarchy(rootDialogId, 'running');
2729
+ // Then perform continuation separately
2730
+ await driveDialogStream(result.rootDialog, humanPrompt);
2731
+ }
2732
+ catch (error) {
2733
+ log_1.log.error(`Failed to continue root dialog:`, error);
2734
+ throw error;
2735
+ }
2736
+ }
2737
+ /**
2738
+ * Unified function to extract the last assistant message from an array of messages.
2739
+ * Prefers saying_msg over thinking_msg, returns full content without truncation.
2740
+ *
2741
+ * @param messages Array of chat messages to search
2742
+ * @param defaultMessage Default message if no assistant message found
2743
+ * @returns The extracted message content or default
2744
+ */
2745
+ function extractLastAssistantResponse(messages, defaultMessage) {
2746
+ let responseText = '';
2747
+ for (let i = messages.length - 1; i >= 0; i--) {
2748
+ const msg = messages[i];
2749
+ if (msg.type === 'saying_msg' && typeof msg.content === 'string') {
2750
+ responseText = msg.content;
2751
+ break;
2752
+ }
2753
+ if (msg.type === 'thinking_msg' && typeof msg.content === 'string') {
2754
+ responseText = msg.content;
2755
+ // Keep looking for a saying_msg which is more complete
2756
+ }
2757
+ }
2758
+ // If no assistant message found, use the default
2759
+ if (!responseText) {
2760
+ responseText = defaultMessage;
2761
+ }
2762
+ return responseText;
2763
+ }
2764
+ /**
2765
+ * Phase 11: Extract response from supdialog's current messages for Type A mechanism.
2766
+ * Used when a subdialog calls its parent (supdialog) and needs the parent's response.
2767
+ * Reads from the in-memory dialog object which contains the latest messages after driving.
2768
+ *
2769
+ * @param supdialog The supdialog that was just driven
2770
+ * @returns The response text from the supdialog's last assistant message
2771
+ */
2772
+ async function extractSupdialogResponseForTypeA(supdialog) {
2773
+ try {
2774
+ return extractLastAssistantResponse(supdialog.msgs, 'Supdialog completed without producing output.');
2775
+ }
2776
+ catch (err) {
2777
+ log_1.log.warn('Failed to extract supdialog response for Type A', { error: err });
2778
+ return 'Supdialog completed with errors.';
2779
+ }
2780
+ }
2781
+ async function updateSubdialogAssignment(subdialog, assignment) {
2782
+ subdialog.assignmentFromSup = assignment;
2783
+ await persistence_1.DialogPersistence.updateSubdialogAssignment(subdialog.id, assignment);
2784
+ }
2785
+ async function lookupLiveRegisteredSubdialog(rootDialog, agentId, tellaskSession) {
2786
+ const existing = rootDialog.lookupSubdialog(agentId, tellaskSession);
2787
+ if (!existing) {
2788
+ return undefined;
2789
+ }
2790
+ const existingSession = existing.tellaskSession;
2791
+ if (!existingSession) {
2792
+ throw new Error(`Type B registry invariant violation: lookupSubdialog returned entry without tellaskSession (root=${rootDialog.id.valueOf()} sub=${existing.id.valueOf()})`);
2793
+ }
2794
+ const latest = await persistence_1.DialogPersistence.loadDialogLatest(existing.id, rootDialog.status);
2795
+ const runState = latest?.runState;
2796
+ if (!runState || runState.kind !== 'dead') {
2797
+ return existing;
2798
+ }
2799
+ const removed = rootDialog.unregisterSubdialog(existing.agentId, existingSession);
2800
+ if (!removed) {
2801
+ throw new Error(`Failed to unregister dead registered subdialog: root=${rootDialog.id.valueOf()} sub=${existing.id.valueOf()} session=${existingSession}`);
2802
+ }
2803
+ await rootDialog.saveSubdialogRegistry();
2804
+ log_1.log.info('Pruned dead registered subdialog from Type B registry', undefined, {
2805
+ rootId: rootDialog.id.rootId,
2806
+ subdialogId: existing.id.selfId,
2807
+ agentId: existing.agentId,
2808
+ tellaskSession: existingSession,
2809
+ });
2810
+ return undefined;
2811
+ }
2812
+ let agentPrimingModulePromise = null;
2813
+ async function scheduleInheritedAgentPrimingForSubdialog(callerDialog, subdialog) {
2814
+ const rootDialog = callerDialog instanceof dialog_1.RootDialog
2815
+ ? callerDialog
2816
+ : callerDialog instanceof dialog_1.SubDialog
2817
+ ? callerDialog.rootDialog
2818
+ : undefined;
2819
+ if (!rootDialog)
2820
+ return;
2821
+ const inheritedMode = rootDialog.subdialogAgentPrimingMode;
2822
+ if (inheritedMode === 'skip')
2823
+ return;
2824
+ if (!agentPrimingModulePromise) {
2825
+ agentPrimingModulePromise = Promise.resolve().then(() => __importStar(require('../agent-priming')));
2826
+ }
2827
+ const agentPrimingModule = await agentPrimingModulePromise;
2828
+ await agentPrimingModule.scheduleAgentPrimingForNewDialog(subdialog, { mode: inheritedMode });
2829
+ }
2830
+ async function createSubDialogWithInheritedPriming(callerDialog, targetAgentId, tellaskHead, tellaskBody, options) {
2831
+ const subdialog = await callerDialog.createSubDialog(targetAgentId, tellaskHead, tellaskBody, options);
2832
+ await scheduleInheritedAgentPrimingForSubdialog(callerDialog, subdialog);
2833
+ return subdialog;
2834
+ }
2835
+ async function resolveOwnerDialogBySelfId(subdialog, ownerDialogId) {
2836
+ const rootDialog = subdialog.rootDialog;
2837
+ if (ownerDialogId === rootDialog.id.selfId) {
2838
+ return rootDialog;
2839
+ }
2840
+ const existing = rootDialog.lookupDialog(ownerDialogId);
2841
+ if (existing)
2842
+ return existing;
2843
+ return await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, new dialog_1.DialogID(ownerDialogId, rootDialog.id.rootId), 'running');
2844
+ }
2845
+ async function supplySubdialogResponseToSpecificCallerIfPending(subdialog, responseText, target) {
2846
+ const assignment = subdialog.assignmentFromSup;
2847
+ if (!assignment) {
2848
+ return false;
2849
+ }
2850
+ const ownerDialog = await resolveOwnerDialogBySelfId(subdialog, target.ownerDialogId);
2851
+ if (!ownerDialog) {
2852
+ return false;
2853
+ }
2854
+ const pending = await persistence_1.DialogPersistence.loadPendingSubdialogs(ownerDialog.id);
2855
+ const pendingRecord = pending.find((p) => p.subdialogId === subdialog.id.selfId);
2856
+ if (!pendingRecord) {
2857
+ return false;
2858
+ }
2859
+ if (pendingRecord.callType !== target.callType) {
2860
+ log_1.log.warn('Reply target callType does not match pending callType; skipping stale reply target', {
2861
+ rootId: subdialog.rootDialog.id.rootId,
2862
+ subdialogId: subdialog.id.selfId,
2863
+ ownerDialogId: ownerDialog.id.selfId,
2864
+ targetCallType: target.callType,
2865
+ pendingCallType: pendingRecord.callType,
2866
+ });
2867
+ return false;
2868
+ }
2869
+ await supplyResponseToSupdialog(ownerDialog, subdialog.id, responseText, pendingRecord.callType, target.callId, 'completed');
2870
+ return true;
2871
+ }
2872
+ async function supplySubdialogResponseToAssignedCallerIfPending(subdialog, responseText) {
2873
+ const assignment = subdialog.assignmentFromSup;
2874
+ if (!assignment) {
2875
+ return false;
2876
+ }
2877
+ const callerDialog = await resolveOwnerDialogBySelfId(subdialog, assignment.callerDialogId);
2878
+ if (!callerDialog) {
2879
+ log_1.log.warn('Missing caller dialog for subdialog response supply', {
2880
+ rootId: subdialog.rootDialog.id.rootId,
2881
+ subdialogId: subdialog.id.selfId,
2882
+ callerDialogId: assignment.callerDialogId,
2883
+ });
2884
+ return false;
2885
+ }
2886
+ const pending = await persistence_1.DialogPersistence.loadPendingSubdialogs(callerDialog.id);
2887
+ const pendingRecord = pending.find((p) => p.subdialogId === subdialog.id.selfId);
2888
+ if (!pendingRecord) {
2889
+ return false;
2890
+ }
2891
+ await supplyResponseToSupdialog(callerDialog, subdialog.id, responseText, pendingRecord.callType, assignment.callId, 'completed');
2892
+ return true;
2893
+ }
2894
+ // === PHASE 6: SUBDIALOG SUPPLY MECHANISM ===
2895
+ /**
2896
+ * Create a Type A subdialog for supdialog suspension.
2897
+ * Creates subdialog, persists pending record, and returns suspended promise.
2898
+ *
2899
+ * Type A: Supdialog suspension call where subdialog calls parent and parent suspends.
2900
+ *
2901
+ * @param supdialog The supdialog making the call
2902
+ * @param targetAgentId The agent to handle the subdialog
2903
+ * @param tellaskHead The headline for the subdialog
2904
+ * @param tellaskBody The body content for the subdialog
2905
+ * @returns Promise resolving when subdialog is created and pending record saved
2906
+ */
2907
+ async function createSubdialogForSupdialog(supdialog, targetAgentId, tellaskHead, tellaskBody, callId) {
2908
+ try {
2909
+ // Create the subdialog
2910
+ const subdialog = await createSubDialogWithInheritedPriming(supdialog, targetAgentId, tellaskHead, tellaskBody, {
2911
+ originMemberId: supdialog.agentId,
2912
+ callerDialogId: supdialog.id.selfId,
2913
+ callId,
2914
+ collectiveTargets: [targetAgentId],
2915
+ });
2916
+ // Persist pending subdialog record
2917
+ const pendingRecord = {
2918
+ subdialogId: subdialog.id.selfId,
2919
+ createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
2920
+ tellaskHead,
2921
+ targetAgentId,
2922
+ callType: 'A',
2923
+ };
2924
+ // Load existing pending subdialogs and add new one
2925
+ await withSuspensionStateLock(supdialog.id, async () => {
2926
+ await persistence_1.DialogPersistence.appendPendingSubdialog(supdialog.id, pendingRecord);
2927
+ });
2928
+ await syncPendingTellaskReminderBestEffort(supdialog, 'createSubdialogForSupdialog:appendPending');
2929
+ // Drive the subdialog asynchronously
2930
+ void (async () => {
2931
+ try {
2932
+ const initPrompt = {
2933
+ content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
2934
+ fromAgentId: supdialog.agentId,
2935
+ toAgentId: subdialog.agentId,
2936
+ tellaskHead,
2937
+ tellaskBody: tellaskBody,
2938
+ language: (0, runtime_language_1.getWorkLanguage)(),
2939
+ collectiveTargets: [targetAgentId],
2940
+ }),
2941
+ msgId: (0, id_1.generateShortId)(),
2942
+ grammar: 'markdown',
2943
+ subdialogReplyTarget: {
2944
+ ownerDialogId: supdialog.id.selfId,
2945
+ callType: 'A',
2946
+ callId,
2947
+ },
2948
+ };
2949
+ await driveDialogStream(subdialog, initPrompt, true);
2950
+ }
2951
+ catch (err) {
2952
+ log_1.log.warn('Type A subdialog processing error:', err);
2953
+ }
2954
+ })();
2955
+ }
2956
+ catch (error) {
2957
+ log_1.log.error('Failed to create Type A subdialog for supdialog', {
2958
+ supdialogId: supdialog.id.selfId,
2959
+ targetAgentId,
2960
+ error,
2961
+ });
2962
+ throw error;
2963
+ }
2964
+ }
2965
+ /**
2966
+ * Create a Type B registered subdialog with registry lookup/register.
2967
+ * Creates or resumes a registered subdialog tracked in registry.yaml.
2968
+ *
2969
+ * Type B: @<agentId> !tellaskSession <tellaskSession> - Creates/resumes registered subdialog.
2970
+ *
2971
+ * @param rootDialog The root dialog making the call
2972
+ * @param agentId The agent to handle the subdialog
2973
+ * @param tellaskSession The tellask session key for registry lookup
2974
+ * @param tellaskHead The headline for the subdialog
2975
+ * @param tellaskBody The body content for the subdialog
2976
+ * @returns Promise resolving when subdialog is created/registered
2977
+ */
2978
+ /**
2979
+ * Supply a response from a completed subdialog to the supdialog.
2980
+ * Writes the response to persistence for later incorporation.
2981
+ *
2982
+ * @param parentDialog The supdialog that created the subdialog
2983
+ * @param subdialogId The ID of the completed subdialog
2984
+ * @param responseText The full response text from the subdialog
2985
+ * @param callType The call type ('A', 'B', or 'C')
2986
+ * @param callId Optional callId for Type C subdialog tracking
2987
+ */
2988
+ async function supplyResponseToSupdialog(parentDialog, subdialogId, responseText, callType, callId, status = 'completed') {
2989
+ try {
2990
+ const result = await withSuspensionStateLock(parentDialog.id, async () => {
2991
+ const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(parentDialog.id);
2992
+ let pendingRecord;
2993
+ const filteredPending = [];
2994
+ for (const pending of pendingSubdialogs) {
2995
+ if (pending.subdialogId === subdialogId.selfId) {
2996
+ pendingRecord = pending;
2997
+ }
2998
+ else {
2999
+ filteredPending.push(pending);
3000
+ }
3001
+ }
3002
+ let responderId = subdialogId.rootId;
3003
+ let responderAgentId;
3004
+ let tellaskHead = responseText;
3005
+ let originMemberId;
3006
+ try {
3007
+ let metadata = await persistence_1.DialogPersistence.loadDialogMetadata(subdialogId, 'running');
3008
+ if (!metadata) {
3009
+ metadata = await persistence_1.DialogPersistence.loadDialogMetadata(subdialogId, 'completed');
3010
+ }
3011
+ if (metadata && metadata.assignmentFromSup) {
3012
+ originMemberId = metadata.assignmentFromSup.originMemberId;
3013
+ if (!pendingRecord) {
3014
+ const assignmentHead = metadata.assignmentFromSup.tellaskHead;
3015
+ if (typeof assignmentHead === 'string' && assignmentHead.trim() !== '') {
3016
+ tellaskHead = assignmentHead;
3017
+ }
3018
+ }
3019
+ }
3020
+ if (!pendingRecord && metadata && typeof metadata.agentId === 'string') {
3021
+ if (metadata.agentId.trim() !== '') {
3022
+ responderId = metadata.agentId;
3023
+ responderAgentId = metadata.agentId;
3024
+ }
3025
+ }
3026
+ }
3027
+ catch (err) {
3028
+ log_1.log.warn('Failed to load subdialog metadata for response record', {
3029
+ parentId: parentDialog.id.selfId,
3030
+ subdialogId: subdialogId.selfId,
3031
+ error: err,
3032
+ });
3033
+ }
3034
+ if (!originMemberId) {
3035
+ originMemberId = parentDialog.agentId;
3036
+ }
3037
+ if (pendingRecord) {
3038
+ responderId = pendingRecord.targetAgentId;
3039
+ responderAgentId = pendingRecord.targetAgentId;
3040
+ tellaskHead = pendingRecord.tellaskHead;
3041
+ }
3042
+ if (tellaskHead.trim() === '') {
3043
+ tellaskHead = responseText.slice(0, 100) + (responseText.length > 100 ? '...' : '');
3044
+ }
3045
+ const responseContent = (0, inter_dialog_format_1.formatTeammateResponseContent)({
3046
+ responderId,
3047
+ requesterId: originMemberId,
3048
+ originalCallHeadLine: tellaskHead,
3049
+ responseBody: responseText,
3050
+ language: (0, runtime_language_1.getWorkLanguage)(),
3051
+ });
3052
+ const completedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
3053
+ const responseId = (0, id_1.generateShortId)();
3054
+ await persistence_1.DialogPersistence.appendSubdialogResponse(parentDialog.id, {
3055
+ responseId,
3056
+ subdialogId: subdialogId.selfId,
3057
+ response: responseText,
3058
+ completedAt,
3059
+ status,
3060
+ callType,
3061
+ tellaskHead,
3062
+ responderId,
3063
+ originMemberId,
3064
+ callId: callId ?? '',
3065
+ });
3066
+ await persistence_1.DialogPersistence.savePendingSubdialogs(parentDialog.id, filteredPending);
3067
+ const hasQ4H = await parentDialog.hasPendingQ4H();
3068
+ const shouldRevive = !hasQ4H && filteredPending.length === 0;
3069
+ if (shouldRevive && parentDialog instanceof dialog_1.RootDialog) {
3070
+ await persistence_1.DialogPersistence.setNeedsDrive(parentDialog.id, true, parentDialog.status);
3071
+ }
3072
+ return {
3073
+ responderId,
3074
+ responderAgentId,
3075
+ tellaskHead,
3076
+ originMemberId,
3077
+ responseContent,
3078
+ filteredPendingCount: filteredPending.length,
3079
+ shouldRevive,
3080
+ };
3081
+ });
3082
+ const resolvedAgentId = result.responderAgentId ?? result.responderId;
3083
+ const resolvedOriginMemberId = result.originMemberId ?? parentDialog.agentId;
3084
+ const resolvedCallId = callId ?? '';
3085
+ await syncPendingTellaskReminderBestEffort(parentDialog, 'supplyResponseToSupdialog:savePending');
3086
+ await parentDialog.receiveTeammateResponse(result.responderId, result.tellaskHead, status, subdialogId, {
3087
+ response: responseText,
3088
+ agentId: resolvedAgentId,
3089
+ callId: resolvedCallId,
3090
+ originMemberId: resolvedOriginMemberId,
3091
+ });
3092
+ if (result.shouldRevive) {
3093
+ log_1.log.info(`All Type ${callType} subdialogs complete, parent ${parentDialog.id.selfId} auto-reviving`);
3094
+ if (parentDialog instanceof dialog_1.RootDialog) {
3095
+ dialog_global_registry_1.globalDialogRegistry.markNeedsDrive(parentDialog.id.rootId);
3096
+ }
3097
+ const suppressDiligencePushForReviveDrive = parentDialog.disableDiligencePush;
3098
+ void driveDialogStream(parentDialog, undefined, true, {
3099
+ suppressDiligencePush: suppressDiligencePushForReviveDrive,
3100
+ });
3101
+ }
3102
+ }
3103
+ catch (error) {
3104
+ log_1.log.error('Failed to supply subdialog response', {
3105
+ parentId: parentDialog.id.selfId,
3106
+ subdialogId: subdialogId.selfId,
3107
+ error,
3108
+ });
3109
+ throw error;
3110
+ }
3111
+ }
3112
+ /**
3113
+ * Check if all pending Type A subdialogs are satisfied (have responses).
3114
+ *
3115
+ * @param rootDialogId The root dialog ID to check
3116
+ * @returns Promise<boolean> True if all Type A subdialogs have responses
3117
+ */
3118
+ async function areAllSubdialogsSatisfied(rootDialogId) {
3119
+ try {
3120
+ const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(rootDialogId);
3121
+ const responses = await persistence_1.DialogPersistence.loadSubdialogResponses(rootDialogId);
3122
+ // Check if any pending subdialogs have responses
3123
+ const pendingIds = new Set(pendingSubdialogs.map((p) => p.subdialogId));
3124
+ const respondedIds = new Set(responses.map((r) => r.subdialogId));
3125
+ // Check if all pending subdialogs have been responded to
3126
+ for (const pendingId of pendingIds) {
3127
+ if (!respondedIds.has(pendingId)) {
3128
+ return false;
3129
+ }
3130
+ }
3131
+ return true;
3132
+ }
3133
+ catch (error) {
3134
+ log_1.log.error('Failed to check subdialog satisfaction', {
3135
+ rootDialogId: rootDialogId.selfId,
3136
+ error,
3137
+ });
3138
+ return false;
3139
+ }
3140
+ }
3141
+ /**
3142
+ * Incorporate subdialog responses into the supdialog and resume.
3143
+ * Reads responses from persistence and clears them after incorporation.
3144
+ *
3145
+ * @param rootDialog The root dialog to resume
3146
+ * @returns Promise<Array<{ subdialogId: string; response: string; callType: 'A' | 'B' | 'C' }>>
3147
+ * Array of incorporated responses (response holds full response text)
3148
+ */
3149
+ async function incorporateSubdialogResponses(rootDialog) {
3150
+ try {
3151
+ const responses = await persistence_1.DialogPersistence.loadSubdialogResponses(rootDialog.id);
3152
+ // Incorporate each response
3153
+ for (const response of responses) {
3154
+ const subdialogId = new dialog_1.DialogID(response.subdialogId, rootDialog.id.rootId);
3155
+ // Emit subdialog response event (payload contains full response text)
3156
+ await rootDialog.postSubdialogResponse(subdialogId, response.response);
3157
+ }
3158
+ // Clear responses after incorporation
3159
+ await persistence_1.DialogPersistence.saveSubdialogResponses(rootDialog.id, []);
3160
+ // Clear pending subdialogs that have been responded to
3161
+ const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(rootDialog.id);
3162
+ const respondedIds = new Set(responses.map((r) => r.subdialogId));
3163
+ const filteredPending = pendingSubdialogs.filter((p) => !respondedIds.has(p.subdialogId));
3164
+ await persistence_1.DialogPersistence.savePendingSubdialogs(rootDialog.id, filteredPending);
3165
+ await syncPendingTellaskReminderBestEffort(rootDialog, 'incorporateSubdialogResponses:savePending');
3166
+ return responses;
3167
+ }
3168
+ catch (error) {
3169
+ log_1.log.error('Failed to incorporate subdialog responses', {
3170
+ rootDialogId: rootDialog.id.selfId,
3171
+ error,
3172
+ });
3173
+ throw error;
3174
+ }
3175
+ }
3176
+ /**
3177
+ * Collect tellask calls using the streaming parser, then execute them
3178
+ */
3179
+ async function executeTellaskCalls(dlg, agent, collectedCalls) {
3180
+ const malformedToolOutputs = await emitMalformedTellaskResponses(dlg, collectedCalls);
3181
+ const validCalls = collectedCalls.filter((call) => call.validation.kind === 'valid');
3182
+ // Execute collected calls concurrently
3183
+ const results = await Promise.all(validCalls.map((call) => executeTellaskCall(dlg, agent, call.validation.firstMention, call.tellaskHead, call.body, call.callId)));
3184
+ // Combine results from all concurrent calls
3185
+ const suspend = results.some((result) => result.suspend);
3186
+ const toolOutputs = [...malformedToolOutputs, ...results.flatMap((result) => result.toolOutputs)];
3187
+ const subdialogsCreated = results.flatMap((result) => result.subdialogsCreated);
3188
+ return { suspend, toolOutputs, subdialogsCreated };
3189
+ }
3190
+ async function emitMalformedTellaskResponses(dlg, collectedCalls) {
3191
+ const toolOutputs = [];
3192
+ const language = (0, runtime_language_1.getWorkLanguage)();
3193
+ for (const call of collectedCalls) {
3194
+ if (call.validation.kind !== 'malformed')
3195
+ continue;
3196
+ const firstLineAfterPrefix = (call.tellaskHead.split('\n')[0] ?? '').trim();
3197
+ const msg = (0, driver_messages_1.formatDomindsNoteMalformedTellaskCall)(language, call.validation.reason, {
3198
+ firstLineAfterPrefix,
3199
+ });
3200
+ toolOutputs.push({
3201
+ type: 'environment_msg',
3202
+ role: 'user',
3203
+ content: msg,
3204
+ });
3205
+ toolOutputs.push({
3206
+ type: 'tellask_result_msg',
3207
+ role: 'tool',
3208
+ responderId: 'dominds',
3209
+ tellaskHead: call.tellaskHead,
3210
+ status: 'failed',
3211
+ content: msg,
3212
+ });
3213
+ await dlg.receiveTeammateCallResult('dominds', call.tellaskHead, msg, 'failed', call.callId);
3214
+ dlg.clearCurrentCallId();
3215
+ }
3216
+ return toolOutputs;
3217
+ }
3218
+ /**
3219
+ * Execute a single tellask call using Phase 5 3-Type Taxonomy.
3220
+ * Handles Type A (supdialog suspension), Type B (registered subdialog), and Type C (transient subdialog).
3221
+ */
3222
+ async function executeTellaskCall(dlg, agent, firstMention, tellaskHead, body, callId, options) {
3223
+ const toolOutputs = [];
3224
+ let suspend = false;
3225
+ const subdialogsCreated = [];
3226
+ const team = await team_1.Team.load();
3227
+ const isSelfAlias = firstMention === 'self';
3228
+ const isTellaskerAlias = firstMention === 'tellasker';
3229
+ const member = isSelfAlias
3230
+ ? team.getMember(dlg.agentId)
3231
+ : isTellaskerAlias
3232
+ ? null
3233
+ : team.getMember(firstMention);
3234
+ // Multi-teammate fan-out (collective teammate tellask):
3235
+ // A single tellask block can target multiple teammates by including multiple teammate mentions
3236
+ // anywhere inside the (possibly multiline) headline. The full headline/body is passed verbatim
3237
+ // to each target so each subdialog can see this is a collective assignment.
3238
+ const allowMultiTeammateTargets = options?.allowMultiTeammateTargets ?? true;
3239
+ if (allowMultiTeammateTargets && member && !isSelfAlias && !isTellaskerAlias) {
3240
+ const mentioned = extractMentionIdsFromHeadline(tellaskHead);
3241
+ const uniqueMentioned = Array.from(new Set(mentioned));
3242
+ const knownTargets = uniqueMentioned.filter((id) => team.getMember(id) !== null);
3243
+ if (!knownTargets.includes(firstMention)) {
3244
+ knownTargets.unshift(firstMention);
3245
+ }
3246
+ if (knownTargets.length >= 2) {
3247
+ const unknown = uniqueMentioned.filter((id) => team.getMember(id) === null &&
3248
+ id !== 'self' &&
3249
+ id !== 'tellasker' &&
3250
+ id !== 'human' &&
3251
+ id !== 'dominds');
3252
+ if (unknown.length > 0) {
3253
+ const msg = (0, driver_messages_1.formatDomindsNoteInvalidMultiTeammateTargets)((0, runtime_language_1.getWorkLanguage)(), { unknown });
3254
+ toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
3255
+ toolOutputs.push({
3256
+ type: 'tellask_result_msg',
3257
+ role: 'tool',
3258
+ responderId: 'dominds',
3259
+ tellaskHead,
3260
+ status: 'failed',
3261
+ content: msg,
3262
+ });
3263
+ await dlg.receiveTeammateCallResult('dominds', tellaskHead, msg, 'failed', callId);
3264
+ dlg.clearCurrentCallId();
3265
+ return { toolOutputs, suspend: false, subdialogsCreated: [] };
3266
+ }
3267
+ if (options?.skipTellaskSessionDirectiveValidation !== true) {
3268
+ const tellaskSessionDirective = parseTellaskSessionDirectiveFromHeadline(tellaskHead);
3269
+ if (tellaskSessionDirective.kind === 'multiple') {
3270
+ const msg = (0, driver_messages_1.formatDomindsNoteMultipleTellaskSessionDirectives)((0, runtime_language_1.getWorkLanguage)());
3271
+ toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
3272
+ toolOutputs.push({
3273
+ type: 'tellask_result_msg',
3274
+ role: 'tool',
3275
+ responderId: 'dominds',
3276
+ tellaskHead,
3277
+ status: 'failed',
3278
+ content: msg,
3279
+ });
3280
+ await dlg.receiveTeammateCallResult('dominds', tellaskHead, msg, 'failed', callId);
3281
+ dlg.clearCurrentCallId();
3282
+ return { toolOutputs, suspend: false, subdialogsCreated: [] };
3283
+ }
3284
+ if (tellaskSessionDirective.kind === 'invalid') {
3285
+ const msg = (0, driver_messages_1.formatDomindsNoteInvalidTellaskSessionDirective)((0, runtime_language_1.getWorkLanguage)());
3286
+ toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
3287
+ toolOutputs.push({
3288
+ type: 'tellask_result_msg',
3289
+ role: 'tool',
3290
+ responderId: 'dominds',
3291
+ tellaskHead,
3292
+ status: 'failed',
3293
+ content: msg,
3294
+ });
3295
+ await dlg.receiveTeammateCallResult('dominds', tellaskHead, msg, 'failed', callId);
3296
+ dlg.clearCurrentCallId();
3297
+ return { toolOutputs, suspend: false, subdialogsCreated: [] };
3298
+ }
3299
+ }
3300
+ const perTargetResults = await Promise.all(knownTargets.map(async (targetId) => {
3301
+ return await executeTellaskCall(dlg, agent, targetId, tellaskHead, body, callId, {
3302
+ allowMultiTeammateTargets: false,
3303
+ collectiveTargets: knownTargets,
3304
+ skipTellaskSessionDirectiveValidation: true,
3305
+ });
3306
+ }));
3307
+ return {
3308
+ toolOutputs: perTargetResults.flatMap((r) => r.toolOutputs),
3309
+ suspend: perTargetResults.some((r) => r.suspend),
3310
+ subdialogsCreated: perTargetResults.flatMap((r) => r.subdialogsCreated),
3311
+ };
3312
+ }
3313
+ }
3314
+ // === Q4H: Handle @human teammate tellasks (Questions for Human) ===
3315
+ // Q4H works for both user-initiated and assistant-initiated @human calls
3316
+ const isQ4H = firstMention === 'human';
3317
+ if (isQ4H) {
3318
+ try {
3319
+ // Create HumanQuestion entry
3320
+ const questionId = `q4h-${(0, id_2.generateDialogID)()}`;
3321
+ const question = {
3322
+ id: questionId,
3323
+ tellaskHead: tellaskHead.trim(),
3324
+ bodyContent: body.trim(),
3325
+ askedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
3326
+ callId: callId.trim() === '' ? undefined : callId,
3327
+ callSiteRef: {
3328
+ course: dlg.currentCourse,
3329
+ messageIndex: dlg.msgs.length,
3330
+ },
3331
+ };
3332
+ await persistence_1.DialogPersistence.appendQuestion4HumanState(dlg.id, question);
3333
+ // Emit new_q4h_asked event
3334
+ const newQuestionEvent = {
3335
+ type: 'new_q4h_asked',
3336
+ question: {
3337
+ id: question.id,
3338
+ selfId: dlg.id.selfId,
3339
+ tellaskHead: question.tellaskHead,
3340
+ bodyContent: question.bodyContent,
3341
+ askedAt: question.askedAt,
3342
+ callId: question.callId,
3343
+ callSiteRef: question.callSiteRef,
3344
+ rootId: dlg.id.rootId,
3345
+ agentId: dlg.agentId,
3346
+ taskDocPath: dlg.taskDocPath,
3347
+ },
3348
+ };
3349
+ (0, evt_registry_1.postDialogEvent)(dlg, newQuestionEvent);
3350
+ // Return empty output and suspend for human answer
3351
+ return { toolOutputs, suspend: true, subdialogsCreated: [] };
3352
+ }
3353
+ catch (q4hErr) {
3354
+ const errMsg = q4hErr instanceof Error ? q4hErr.message : String(q4hErr);
3355
+ const streamErr = `Q4H register invariant violation: dialog=${dlg.id.selfId} callId=${callId.trim()} reason=${errMsg}`;
3356
+ try {
3357
+ await dlg.streamError(streamErr);
3358
+ }
3359
+ catch (streamErrPost) {
3360
+ log_1.log.warn('Q4H: failed to emit stream_error_evt', streamErrPost, {
3361
+ dialogId: dlg.id.selfId,
3362
+ });
3363
+ }
3364
+ log_1.log.error('Q4H: Failed to register question', q4hErr, {
3365
+ dialogId: dlg.id.selfId,
3366
+ tellaskHead: tellaskHead.substring(0, 100),
3367
+ });
3368
+ const msg = (0, driver_messages_1.formatDomindsNoteQ4HRegisterFailed)((0, runtime_language_1.getWorkLanguage)(), { error: errMsg });
3369
+ toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
3370
+ toolOutputs.push({
3371
+ type: 'tellask_result_msg',
3372
+ role: 'tool',
3373
+ responderId: 'dominds',
3374
+ tellaskHead,
3375
+ status: 'failed',
3376
+ content: msg,
3377
+ });
3378
+ await dlg.receiveTeammateCallResult('dominds', tellaskHead, msg, 'failed', callId);
3379
+ dlg.clearCurrentCallId();
3380
+ return { toolOutputs, suspend: false, subdialogsCreated: [] };
3381
+ }
3382
+ }
3383
+ if (member || isSelfAlias || isTellaskerAlias) {
3384
+ // This is a teammate tellask - parse using Phase 5 taxonomy (Type A/B/C).
3385
+ if (isTellaskerAlias && !(dlg instanceof dialog_1.SubDialog)) {
3386
+ const response = (0, driver_messages_1.formatDomindsNoteTellaskerOnlyInSidelineDialog)((0, runtime_language_1.getWorkLanguage)());
3387
+ try {
3388
+ await dlg.receiveTeammateResponse('dominds', tellaskHead, 'failed', dlg.id, {
3389
+ response,
3390
+ agentId: 'dominds',
3391
+ callId,
3392
+ originMemberId: dlg.agentId,
3393
+ });
3394
+ }
3395
+ catch (err) {
3396
+ log_1.log.warn('Failed to emit @tellasker misuse response', err, {
3397
+ dialogId: dlg.id.selfId,
3398
+ agentId: dlg.agentId,
3399
+ });
3400
+ }
3401
+ return { toolOutputs, suspend: false, subdialogsCreated: [] };
3402
+ }
3403
+ if (options?.skipTellaskSessionDirectiveValidation !== true) {
3404
+ const tellaskSessionDirective = parseTellaskSessionDirectiveFromHeadline(tellaskHead);
3405
+ if (isTellaskerAlias && tellaskSessionDirective.kind !== 'none') {
3406
+ const response = (0, driver_messages_1.formatDomindsNoteTellaskerNoTellaskSession)((0, runtime_language_1.getWorkLanguage)());
3407
+ try {
3408
+ await dlg.receiveTeammateResponse('dominds', tellaskHead, 'failed', dlg.id, {
3409
+ response,
3410
+ agentId: 'dominds',
3411
+ callId,
3412
+ originMemberId: dlg.agentId,
3413
+ });
3414
+ }
3415
+ catch (err) {
3416
+ log_1.log.warn('Failed to emit @tellasker !tellaskSession syntax error response', err, {
3417
+ dialogId: dlg.id.selfId,
3418
+ agentId: dlg.agentId,
3419
+ });
3420
+ }
3421
+ return { toolOutputs, suspend: false, subdialogsCreated: [] };
3422
+ }
3423
+ if (tellaskSessionDirective.kind === 'multiple') {
3424
+ const msg = (0, driver_messages_1.formatDomindsNoteMultipleTellaskSessionDirectives)((0, runtime_language_1.getWorkLanguage)());
3425
+ toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
3426
+ toolOutputs.push({
3427
+ type: 'tellask_result_msg',
3428
+ role: 'tool',
3429
+ responderId: 'dominds',
3430
+ tellaskHead,
3431
+ status: 'failed',
3432
+ content: msg,
3433
+ });
3434
+ await dlg.receiveTeammateCallResult('dominds', tellaskHead, msg, 'failed', callId);
3435
+ dlg.clearCurrentCallId();
3436
+ return { toolOutputs, suspend: false, subdialogsCreated: [] };
3437
+ }
3438
+ if (tellaskSessionDirective.kind === 'invalid') {
3439
+ const msg = (0, driver_messages_1.formatDomindsNoteInvalidTellaskSessionDirective)((0, runtime_language_1.getWorkLanguage)());
3440
+ toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
3441
+ toolOutputs.push({
3442
+ type: 'tellask_result_msg',
3443
+ role: 'tool',
3444
+ responderId: 'dominds',
3445
+ tellaskHead,
3446
+ status: 'failed',
3447
+ content: msg,
3448
+ });
3449
+ await dlg.receiveTeammateCallResult('dominds', tellaskHead, msg, 'failed', callId);
3450
+ dlg.clearCurrentCallId();
3451
+ return { toolOutputs, suspend: false, subdialogsCreated: [] };
3452
+ }
3453
+ }
3454
+ const parseResult = isTellaskerAlias
3455
+ ? { type: 'A', agentId: dlg.supdialog.agentId }
3456
+ : parseTeammateTellask(firstMention, tellaskHead, dlg);
3457
+ // `@self` FBR enhancements:
3458
+ // - Respect per-member fbr_effort (0 disables; 1..100 fan-out)
3459
+ // - Fan-out creates multiple sideline dialogs concurrently for a single `@self` tellask
3460
+ if (isSelfAlias) {
3461
+ const fbrEffort = resolveFbrEffort(member);
3462
+ if (fbrEffort < 1) {
3463
+ const msg = (0, driver_messages_1.formatDomindsNoteFbrDisabled)((0, runtime_language_1.getWorkLanguage)());
3464
+ toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
3465
+ toolOutputs.push({
3466
+ type: 'tellask_result_msg',
3467
+ role: 'tool',
3468
+ responderId: 'dominds',
3469
+ tellaskHead,
3470
+ status: 'failed',
3471
+ content: msg,
3472
+ });
3473
+ await dlg.receiveTeammateCallResult('dominds', tellaskHead, msg, 'failed', callId);
3474
+ dlg.clearCurrentCallId();
3475
+ return { toolOutputs, suspend: false, subdialogsCreated: [] };
3476
+ }
3477
+ const callerDialog = dlg;
3478
+ const originMemberId = dlg.agentId;
3479
+ if (parseResult.type === 'C') {
3480
+ const createdSubs = [];
3481
+ const pendingRecords = [];
3482
+ for (let i = 1; i <= fbrEffort; i++) {
3483
+ const sub = await createSubDialogWithInheritedPriming(dlg, parseResult.agentId, tellaskHead, body, {
3484
+ originMemberId,
3485
+ callerDialogId: callerDialog.id.selfId,
3486
+ callId,
3487
+ collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
3488
+ });
3489
+ createdSubs.push(sub);
3490
+ pendingRecords.push({
3491
+ subdialogId: sub.id.selfId,
3492
+ createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
3493
+ tellaskHead,
3494
+ targetAgentId: parseResult.agentId,
3495
+ callType: 'C',
3496
+ });
3497
+ }
3498
+ await withSuspensionStateLock(dlg.id, async () => {
3499
+ await persistence_1.DialogPersistence.mutatePendingSubdialogs(dlg.id, (previous) => ({
3500
+ kind: 'replace',
3501
+ records: [...previous, ...pendingRecords],
3502
+ }));
3503
+ });
3504
+ await syncPendingTellaskReminderBestEffort(dlg, 'executeTellaskCall:FBR-TypeC:replacePending');
3505
+ for (const sub of createdSubs) {
3506
+ void (async () => {
3507
+ try {
3508
+ const initPrompt = {
3509
+ content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
3510
+ fromAgentId: dlg.agentId,
3511
+ toAgentId: sub.agentId,
3512
+ tellaskHead,
3513
+ tellaskBody: body,
3514
+ language: (0, runtime_language_1.getWorkLanguage)(),
3515
+ collectiveTargets: options?.collectiveTargets ?? [sub.agentId],
3516
+ }),
3517
+ msgId: (0, id_1.generateShortId)(),
3518
+ grammar: 'markdown',
3519
+ subdialogReplyTarget: {
3520
+ ownerDialogId: callerDialog.id.selfId,
3521
+ callType: 'C',
3522
+ callId,
3523
+ },
3524
+ };
3525
+ await driveDialogStream(sub, initPrompt, true);
3526
+ }
3527
+ catch (err) {
3528
+ log_1.log.warn('FBR Type C subdialog processing error:', err);
3529
+ }
3530
+ })();
3531
+ subdialogsCreated.push(sub.id);
3532
+ }
3533
+ return { toolOutputs, suspend: true, subdialogsCreated };
3534
+ }
3535
+ if (parseResult.type === 'B') {
3536
+ let rootDialog;
3537
+ if (dlg instanceof dialog_1.RootDialog) {
3538
+ rootDialog = dlg;
3539
+ }
3540
+ else if (dlg instanceof dialog_1.SubDialog) {
3541
+ rootDialog = dlg.rootDialog;
3542
+ }
3543
+ if (!rootDialog) {
3544
+ const msg = (0, driver_messages_1.formatDomindsNoteFbrToollessViolation)((0, runtime_language_1.getWorkLanguage)(), {
3545
+ kind: 'internal_error',
3546
+ });
3547
+ toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
3548
+ toolOutputs.push({
3549
+ type: 'tellask_result_msg',
3550
+ role: 'tool',
3551
+ responderId: 'dominds',
3552
+ tellaskHead,
3553
+ status: 'failed',
3554
+ content: msg,
3555
+ });
3556
+ await dlg.receiveTeammateCallResult('dominds', tellaskHead, msg, 'failed', callId);
3557
+ dlg.clearCurrentCallId();
3558
+ return { toolOutputs, suspend: false, subdialogsCreated: [] };
3559
+ }
3560
+ const pendingOwner = callerDialog;
3561
+ const baseSession = parseResult.tellaskSession;
3562
+ const derivedPrefix = `${baseSession}.fbr-`;
3563
+ const createdOrExisting = await withSuspensionStateLock(rootDialog.id, async () => {
3564
+ const results = [];
3565
+ const ensurePoolSessions = (desired) => {
3566
+ if (desired <= 1)
3567
+ return [baseSession];
3568
+ const set = new Set();
3569
+ for (const sub of rootDialog.getRegisteredSubdialogs()) {
3570
+ const tellaskSession = sub.tellaskSession;
3571
+ if (typeof tellaskSession !== 'string')
3572
+ continue;
3573
+ if (sub.agentId !== parseResult.agentId)
3574
+ continue;
3575
+ if (!tellaskSession.startsWith(derivedPrefix))
3576
+ continue;
3577
+ set.add(tellaskSession);
3578
+ }
3579
+ while (set.size < desired) {
3580
+ const candidate = `${derivedPrefix}${(0, id_1.generateShortId)()}`;
3581
+ set.add(candidate);
3582
+ }
3583
+ const pool = Array.from(set);
3584
+ for (let i = pool.length - 1; i >= 1; i--) {
3585
+ const j = Math.floor(Math.random() * (i + 1));
3586
+ const tmp = pool[i];
3587
+ pool[i] = pool[j];
3588
+ pool[j] = tmp;
3589
+ }
3590
+ return pool.slice(0, desired);
3591
+ };
3592
+ const sessions = ensurePoolSessions(fbrEffort);
3593
+ for (const derivedSession of sessions) {
3594
+ // Important: do not embed stable per-instance indexing into the headline.
3595
+ // Even with `!tellaskSession`, fan-out instances should not look like “self #1/#2/#3”.
3596
+ const indexedHeadLine = tellaskHead;
3597
+ const assignment = {
3598
+ tellaskHead: indexedHeadLine,
3599
+ tellaskBody: body,
3600
+ originMemberId,
3601
+ callerDialogId: callerDialog.id.selfId,
3602
+ callId,
3603
+ collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
3604
+ };
3605
+ const existing = await lookupLiveRegisteredSubdialog(rootDialog, parseResult.agentId, derivedSession);
3606
+ if (existing) {
3607
+ try {
3608
+ await updateSubdialogAssignment(existing, assignment);
3609
+ }
3610
+ catch (err) {
3611
+ log_1.log.warn('Failed to update registered FBR subdialog assignment', err);
3612
+ }
3613
+ results.push({
3614
+ kind: 'existing',
3615
+ subdialog: existing,
3616
+ tellaskSession: derivedSession,
3617
+ indexedHeadLine,
3618
+ });
3619
+ continue;
3620
+ }
3621
+ const created = await createSubDialogWithInheritedPriming(rootDialog, parseResult.agentId, indexedHeadLine, body, {
3622
+ originMemberId,
3623
+ callerDialogId: callerDialog.id.selfId,
3624
+ callId,
3625
+ tellaskSession: derivedSession,
3626
+ collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
3627
+ });
3628
+ rootDialog.registerSubdialog(created);
3629
+ results.push({
3630
+ kind: 'created',
3631
+ subdialog: created,
3632
+ tellaskSession: derivedSession,
3633
+ indexedHeadLine,
3634
+ });
3635
+ }
3636
+ await rootDialog.saveSubdialogRegistry();
3637
+ return results;
3638
+ });
3639
+ const pendingRecords = createdOrExisting.map((r) => ({
3640
+ subdialogId: r.subdialog.id.selfId,
3641
+ createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
3642
+ tellaskHead: r.indexedHeadLine,
3643
+ targetAgentId: parseResult.agentId,
3644
+ callType: 'B',
3645
+ tellaskSession: r.tellaskSession,
3646
+ }));
3647
+ await withSuspensionStateLock(pendingOwner.id, async () => {
3648
+ const toRemove = new Set(pendingRecords.map((p) => p.subdialogId));
3649
+ await persistence_1.DialogPersistence.mutatePendingSubdialogs(pendingOwner.id, (previous) => {
3650
+ const next = previous.filter((p) => !toRemove.has(p.subdialogId));
3651
+ next.push(...pendingRecords);
3652
+ return { kind: 'replace', records: next };
3653
+ });
3654
+ });
3655
+ await syncPendingTellaskReminderBestEffort(pendingOwner, 'executeTellaskCall:FBR-TypeB:replacePending');
3656
+ for (const r of createdOrExisting) {
3657
+ void (async () => {
3658
+ try {
3659
+ const prompt = {
3660
+ content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
3661
+ fromAgentId: dlg.agentId,
3662
+ toAgentId: r.subdialog.agentId,
3663
+ tellaskHead: r.indexedHeadLine,
3664
+ tellaskBody: body,
3665
+ language: (0, runtime_language_1.getWorkLanguage)(),
3666
+ collectiveTargets: options?.collectiveTargets ?? [r.subdialog.agentId],
3667
+ }),
3668
+ msgId: (0, id_1.generateShortId)(),
3669
+ grammar: 'markdown',
3670
+ subdialogReplyTarget: {
3671
+ ownerDialogId: pendingOwner.id.selfId,
3672
+ callType: 'B',
3673
+ callId,
3674
+ },
3675
+ };
3676
+ await driveDialogStream(r.subdialog, prompt, true);
3677
+ }
3678
+ catch (err) {
3679
+ log_1.log.warn(r.kind === 'existing'
3680
+ ? 'FBR Type B registered subdialog resumption error:'
3681
+ : 'FBR Type B subdialog processing error:', err);
3682
+ }
3683
+ })();
3684
+ subdialogsCreated.push(r.subdialog.id);
3685
+ }
3686
+ return { toolOutputs, suspend: true, subdialogsCreated };
3687
+ }
3688
+ }
3689
+ // If the agent calls itself via `@<agentId>` (instead of `@self`), allow it to proceed
3690
+ // (self-tellasks are useful for FBR), but emit a correction bubble so the user can distinguish
3691
+ // intentional self-FBR from accidental echo/quote triggers.
3692
+ const isDirectSelfCall = !isSelfAlias && !isTellaskerAlias && parseResult.agentId === dlg.agentId;
3693
+ if (isDirectSelfCall) {
3694
+ const response = (0, driver_messages_1.formatDomindsNoteDirectSelfCall)((0, runtime_language_1.getWorkLanguage)());
3695
+ try {
3696
+ await dlg.receiveTeammateResponse('dominds', tellaskHead, 'completed', dlg.id, {
3697
+ response,
3698
+ agentId: 'dominds',
3699
+ callId,
3700
+ originMemberId: dlg.agentId,
3701
+ });
3702
+ }
3703
+ catch (err) {
3704
+ log_1.log.warn('Failed to emit self-tellask correction response', err, {
3705
+ dialogId: dlg.id.selfId,
3706
+ agentId: dlg.agentId,
3707
+ });
3708
+ }
3709
+ }
3710
+ // Phase 11: Type A handling - subdialog calling its direct parent (supdialog)
3711
+ // This suspends the subdialog, drives the supdialog for one course, then returns to subdialog
3712
+ if (parseResult.type === 'A') {
3713
+ // Type A is only valid from a subdialog (calling back to its supdialog).
3714
+ if (dlg instanceof dialog_1.SubDialog) {
3715
+ const supdialog = dlg.supdialog;
3716
+ // Suspend the subdialog
3717
+ dlg.setSuspensionState('suspended');
3718
+ try {
3719
+ const tellaskHeadForSupdialog = isTellaskerAlias && tellaskHead.startsWith('@tellasker')
3720
+ ? `@${supdialog.agentId}${tellaskHead.slice('@tellasker'.length)}`
3721
+ : tellaskHead;
3722
+ const assignment = dlg.assignmentFromSup;
3723
+ const supPrompt = {
3724
+ content: (0, inter_dialog_format_1.formatSupdialogCallPrompt)({
3725
+ fromAgentId: dlg.agentId,
3726
+ toAgentId: supdialog.agentId,
3727
+ subdialogRequest: {
3728
+ tellaskHead: tellaskHeadForSupdialog,
3729
+ tellaskBody: body,
3730
+ },
3731
+ supdialogAssignment: {
3732
+ tellaskHead: assignment.tellaskHead,
3733
+ tellaskBody: assignment.tellaskBody,
3734
+ },
3735
+ language: (0, runtime_language_1.getWorkLanguage)(),
3736
+ }),
3737
+ msgId: (0, id_1.generateShortId)(),
3738
+ grammar: 'markdown',
3739
+ };
3740
+ // Drive the supdialog for one course (queue if already driving)
3741
+ await driveDialogStream(supdialog, supPrompt, true);
3742
+ // Extract response from supdialog's last assistant message
3743
+ const responseText = await extractSupdialogResponseForTypeA(supdialog);
3744
+ const responseContent = (0, inter_dialog_format_1.formatTeammateResponseContent)({
3745
+ responderId: parseResult.agentId,
3746
+ requesterId: dlg.agentId,
3747
+ originalCallHeadLine: tellaskHead,
3748
+ responseBody: responseText,
3749
+ language: (0, runtime_language_1.getWorkLanguage)(),
3750
+ });
3751
+ // Resume the subdialog with the supdialog's response
3752
+ dlg.setSuspensionState('resumed');
3753
+ const resultMsg = {
3754
+ type: 'tellask_result_msg',
3755
+ role: 'tool',
3756
+ responderId: parseResult.agentId,
3757
+ tellaskHead,
3758
+ status: 'completed',
3759
+ content: responseContent,
3760
+ };
3761
+ toolOutputs.push(resultMsg);
3762
+ await dlg.receiveTeammateResponse(parseResult.agentId, tellaskHead, 'completed', supdialog.id, {
3763
+ response: responseText,
3764
+ agentId: parseResult.agentId,
3765
+ callId,
3766
+ originMemberId: dlg.agentId,
3767
+ });
3768
+ }
3769
+ catch (err) {
3770
+ log_1.log.warn('Type A supdialog processing error:', err);
3771
+ // Resume the subdialog even on error
3772
+ dlg.setSuspensionState('resumed');
3773
+ const errorText = `❌ **Error processing request to @${parseResult.agentId}:**\n\n${showErrorToAi(err)}`;
3774
+ const resultMsg = {
3775
+ type: 'tellask_result_msg',
3776
+ role: 'tool',
3777
+ responderId: parseResult.agentId,
3778
+ tellaskHead,
3779
+ status: 'failed',
3780
+ content: errorText,
3781
+ };
3782
+ toolOutputs.push(resultMsg);
3783
+ await dlg.receiveTeammateResponse(parseResult.agentId, tellaskHead, 'failed', supdialog.id, {
3784
+ response: errorText,
3785
+ agentId: parseResult.agentId,
3786
+ callId,
3787
+ originMemberId: dlg.agentId,
3788
+ });
3789
+ }
3790
+ }
3791
+ else {
3792
+ log_1.log.warn('Type A call on dialog without supdialog, falling back to Type C', {
3793
+ dialogId: dlg.id.selfId,
3794
+ });
3795
+ // Fall through to Type C handling
3796
+ }
3797
+ }
3798
+ else if (parseResult.type === 'B') {
3799
+ // Type B: Registered subdialog with tellaskSession (root registry, caller can be root or subdialog)
3800
+ const callerDialog = dlg;
3801
+ let rootDialog;
3802
+ if (dlg instanceof dialog_1.RootDialog) {
3803
+ rootDialog = dlg;
3804
+ }
3805
+ else if (dlg instanceof dialog_1.SubDialog) {
3806
+ rootDialog = dlg.rootDialog;
3807
+ }
3808
+ if (!rootDialog) {
3809
+ log_1.log.warn('Type B call without root dialog, falling back to Type C', {
3810
+ dialogId: dlg.id.selfId,
3811
+ });
3812
+ try {
3813
+ const sub = await createSubDialogWithInheritedPriming(dlg, parseResult.agentId, tellaskHead, body, {
3814
+ originMemberId: dlg.agentId,
3815
+ callerDialogId: callerDialog.id.selfId,
3816
+ callId,
3817
+ tellaskSession: parseResult.tellaskSession,
3818
+ collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
3819
+ });
3820
+ const pendingRecord = {
3821
+ subdialogId: sub.id.selfId,
3822
+ createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
3823
+ tellaskHead,
3824
+ targetAgentId: parseResult.agentId,
3825
+ callType: 'C',
3826
+ tellaskSession: parseResult.tellaskSession,
3827
+ };
3828
+ await withSuspensionStateLock(dlg.id, async () => {
3829
+ await persistence_1.DialogPersistence.appendPendingSubdialog(dlg.id, pendingRecord);
3830
+ });
3831
+ await syncPendingTellaskReminderBestEffort(dlg, 'executeTellaskCall:TypeB-fallback:appendPending');
3832
+ const task = (async () => {
3833
+ try {
3834
+ const initPrompt = {
3835
+ content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
3836
+ fromAgentId: dlg.agentId,
3837
+ toAgentId: sub.agentId,
3838
+ tellaskHead,
3839
+ tellaskBody: body,
3840
+ language: (0, runtime_language_1.getWorkLanguage)(),
3841
+ collectiveTargets: options?.collectiveTargets ?? [sub.agentId],
3842
+ }),
3843
+ msgId: (0, id_1.generateShortId)(),
3844
+ grammar: 'markdown',
3845
+ subdialogReplyTarget: {
3846
+ ownerDialogId: callerDialog.id.selfId,
3847
+ callType: 'C',
3848
+ callId,
3849
+ },
3850
+ };
3851
+ await driveDialogStream(sub, initPrompt, true);
3852
+ }
3853
+ catch (err) {
3854
+ log_1.log.warn('Type B fallback subdialog processing error:', err);
3855
+ }
3856
+ })();
3857
+ void task;
3858
+ subdialogsCreated.push(sub.id);
3859
+ suspend = true;
3860
+ }
3861
+ catch (err) {
3862
+ log_1.log.warn('Type B fallback subdialog creation error:', err);
3863
+ }
3864
+ }
3865
+ else {
3866
+ const originMemberId = dlg.agentId;
3867
+ const assignment = {
3868
+ tellaskHead,
3869
+ tellaskBody: body,
3870
+ originMemberId,
3871
+ callerDialogId: callerDialog.id.selfId,
3872
+ callId,
3873
+ collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
3874
+ };
3875
+ const pendingOwner = callerDialog;
3876
+ const result = await withSuspensionStateLock(rootDialog.id, async () => {
3877
+ const existing = await lookupLiveRegisteredSubdialog(rootDialog, parseResult.agentId, parseResult.tellaskSession);
3878
+ if (existing) {
3879
+ try {
3880
+ await updateSubdialogAssignment(existing, assignment);
3881
+ }
3882
+ catch (err) {
3883
+ log_1.log.warn('Failed to update registered subdialog assignment', err);
3884
+ }
3885
+ return { kind: 'existing', subdialog: existing };
3886
+ }
3887
+ const created = await createSubDialogWithInheritedPriming(rootDialog, parseResult.agentId, tellaskHead, body, {
3888
+ originMemberId,
3889
+ callerDialogId: callerDialog.id.selfId,
3890
+ callId,
3891
+ tellaskSession: parseResult.tellaskSession,
3892
+ collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
3893
+ });
3894
+ rootDialog.registerSubdialog(created);
3895
+ await rootDialog.saveSubdialogRegistry();
3896
+ return { kind: 'created', subdialog: created };
3897
+ });
3898
+ const pendingRecord = {
3899
+ subdialogId: result.subdialog.id.selfId,
3900
+ createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
3901
+ tellaskHead,
3902
+ targetAgentId: parseResult.agentId,
3903
+ callType: 'B',
3904
+ tellaskSession: parseResult.tellaskSession,
3905
+ };
3906
+ await withSuspensionStateLock(pendingOwner.id, async () => {
3907
+ await persistence_1.DialogPersistence.mutatePendingSubdialogs(pendingOwner.id, (previous) => {
3908
+ const next = previous.filter((p) => p.subdialogId !== pendingRecord.subdialogId);
3909
+ next.push(pendingRecord);
3910
+ return { kind: 'replace', records: next };
3911
+ });
3912
+ });
3913
+ await syncPendingTellaskReminderBestEffort(pendingOwner, 'executeTellaskCall:TypeB:replacePending');
3914
+ const task = (async () => {
3915
+ try {
3916
+ if (result.kind === 'existing') {
3917
+ const resumePrompt = {
3918
+ content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
3919
+ fromAgentId: dlg.agentId,
3920
+ toAgentId: result.subdialog.agentId,
3921
+ tellaskHead,
3922
+ tellaskBody: body,
3923
+ language: (0, runtime_language_1.getWorkLanguage)(),
3924
+ collectiveTargets: options?.collectiveTargets ?? [result.subdialog.agentId],
3925
+ }),
3926
+ msgId: (0, id_1.generateShortId)(),
3927
+ grammar: 'markdown',
3928
+ subdialogReplyTarget: {
3929
+ ownerDialogId: pendingOwner.id.selfId,
3930
+ callType: 'B',
3931
+ callId,
3932
+ },
3933
+ };
3934
+ await driveDialogStream(result.subdialog, resumePrompt, true);
3935
+ return;
3936
+ }
3937
+ const initPrompt = {
3938
+ content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
3939
+ fromAgentId: rootDialog.agentId,
3940
+ toAgentId: result.subdialog.agentId,
3941
+ tellaskHead,
3942
+ tellaskBody: body,
3943
+ language: (0, runtime_language_1.getWorkLanguage)(),
3944
+ collectiveTargets: options?.collectiveTargets ?? [result.subdialog.agentId],
3945
+ }),
3946
+ msgId: (0, id_1.generateShortId)(),
3947
+ grammar: 'markdown',
3948
+ subdialogReplyTarget: {
3949
+ ownerDialogId: pendingOwner.id.selfId,
3950
+ callType: 'B',
3951
+ callId,
3952
+ },
3953
+ };
3954
+ await driveDialogStream(result.subdialog, initPrompt, true);
3955
+ }
3956
+ catch (err) {
3957
+ log_1.log.warn(result.kind === 'existing'
3958
+ ? 'Type B registered subdialog resumption error:'
3959
+ : 'Type B subdialog processing error:', err);
3960
+ }
3961
+ })();
3962
+ void task;
3963
+ subdialogsCreated.push(result.subdialog.id);
3964
+ suspend = true;
3965
+ }
3966
+ }
3967
+ // Type C: Transient subdialog (unregistered)
3968
+ if (parseResult.type === 'C') {
3969
+ try {
3970
+ const sub = await createSubDialogWithInheritedPriming(dlg, parseResult.agentId, tellaskHead, body, {
3971
+ originMemberId: dlg.agentId,
3972
+ callerDialogId: dlg.id.selfId,
3973
+ callId,
3974
+ collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
3975
+ });
3976
+ const pendingRecord = {
3977
+ subdialogId: sub.id.selfId,
3978
+ createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
3979
+ tellaskHead,
3980
+ targetAgentId: parseResult.agentId,
3981
+ callType: 'C',
3982
+ };
3983
+ await withSuspensionStateLock(dlg.id, async () => {
3984
+ await persistence_1.DialogPersistence.appendPendingSubdialog(dlg.id, pendingRecord);
3985
+ });
3986
+ await syncPendingTellaskReminderBestEffort(dlg, 'executeTellaskCall:TypeC:appendPending');
3987
+ const task = (async () => {
3988
+ try {
3989
+ const initPrompt = {
3990
+ content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
3991
+ fromAgentId: dlg.agentId,
3992
+ toAgentId: sub.agentId,
3993
+ tellaskHead,
3994
+ tellaskBody: body,
3995
+ language: (0, runtime_language_1.getWorkLanguage)(),
3996
+ collectiveTargets: options?.collectiveTargets ?? [sub.agentId],
3997
+ }),
3998
+ msgId: (0, id_1.generateShortId)(),
3999
+ grammar: 'markdown',
4000
+ subdialogReplyTarget: {
4001
+ ownerDialogId: dlg.id.selfId,
4002
+ callType: 'C',
4003
+ callId,
4004
+ },
4005
+ };
4006
+ // Type C: Move to done/ on completion (handled by subdialog completion)
4007
+ await driveDialogStream(sub, initPrompt, true);
4008
+ }
4009
+ catch (err) {
4010
+ log_1.log.warn('Type C subdialog processing error:', err);
4011
+ }
4012
+ })();
4013
+ void task;
4014
+ subdialogsCreated.push(sub.id);
4015
+ suspend = true;
4016
+ }
4017
+ catch (err) {
4018
+ log_1.log.warn('Subdialog creation error:', err);
4019
+ }
4020
+ }
4021
+ }
4022
+ else {
4023
+ // Not a team member: tellask is reserved for teammate tellasks.
4024
+ // All tools (including dialog control tools) must use native function-calling.
4025
+ const msg = (0, driver_messages_1.formatDomindsNoteTellaskForTeammatesOnly)((0, runtime_language_1.getWorkLanguage)(), { firstMention });
4026
+ toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
4027
+ toolOutputs.push({
4028
+ type: 'tellask_result_msg',
4029
+ role: 'tool',
4030
+ responderId: 'dominds',
4031
+ tellaskHead,
4032
+ status: 'failed',
4033
+ content: msg,
4034
+ });
4035
+ await dlg.receiveTeammateCallResult('dominds', tellaskHead, msg, 'failed', callId);
4036
+ dlg.clearCurrentCallId();
4037
+ }
4038
+ return { toolOutputs, suspend, subdialogsCreated };
4039
+ }
4040
+ function isValidMentionChar(char) {
4041
+ const charCode = char.charCodeAt(0);
4042
+ return ((charCode >= 48 && charCode <= 57) ||
4043
+ (charCode >= 65 && charCode <= 90) ||
4044
+ (charCode >= 97 && charCode <= 122) ||
4045
+ char === '_' ||
4046
+ char === '-' ||
4047
+ char === '.' ||
4048
+ /\p{L}/u.test(char) ||
4049
+ /\p{N}/u.test(char));
4050
+ }
4051
+ function trimTrailingDots(value) {
4052
+ let out = value;
4053
+ while (out.endsWith('.'))
4054
+ out = out.slice(0, -1);
4055
+ return out;
4056
+ }
4057
+ function isMentionBoundaryChar(char) {
4058
+ if (char === '' || char === '\n' || char === '\t' || char === ' ')
4059
+ return true;
4060
+ return !isValidMentionChar(char);
4061
+ }
4062
+ function extractMentionIdsFromHeadline(tellaskHead) {
4063
+ const out = [];
4064
+ const seen = new Set();
4065
+ for (let i = 0; i < tellaskHead.length; i++) {
4066
+ const ch = tellaskHead[i] ?? '';
4067
+ if (ch !== '@')
4068
+ continue;
4069
+ const prev = i === 0 ? '' : (tellaskHead[i - 1] ?? '');
4070
+ if (!isMentionBoundaryChar(prev))
4071
+ continue;
4072
+ let j = i + 1;
4073
+ let raw = '';
4074
+ while (j < tellaskHead.length) {
4075
+ const c = tellaskHead[j] ?? '';
4076
+ if (c !== '' && isValidMentionChar(c)) {
4077
+ raw += c;
4078
+ j += 1;
4079
+ continue;
4080
+ }
4081
+ break;
4082
+ }
4083
+ const id = trimTrailingDots(raw);
4084
+ if (id === '')
4085
+ continue;
4086
+ if (!seen.has(id)) {
4087
+ seen.add(id);
4088
+ out.push(id);
4089
+ }
4090
+ i = j - 1;
4091
+ }
4092
+ return out;
4093
+ }