dominds 1.23.0 → 1.23.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/access-control.js +4 -4
  2. package/dist/cli/webui.js +8 -1
  3. package/dist/dialog-display-state.d.ts +1 -0
  4. package/dist/dialog-display-state.js +36 -4
  5. package/dist/docs/encapsulated-taskdoc.md +11 -8
  6. package/dist/docs/encapsulated-taskdoc.zh.md +12 -9
  7. package/dist/llm/defaults.yaml +3 -0
  8. package/dist/llm/gen/mock.js +3 -6
  9. package/dist/llm/kernel-driver/flow.js +11 -13
  10. package/dist/llm/kernel-driver/loop.js +3 -1
  11. package/dist/llm/kernel-driver/reply-guidance.js +3 -7
  12. package/dist/llm/kernel-driver/types.d.ts +1 -0
  13. package/dist/minds/minds-i18n.js +2 -2
  14. package/dist/minds/system-prompt-parts.js +8 -8
  15. package/dist/persistence.js +1 -0
  16. package/dist/recovery/proceeding-drive.d.ts +1 -0
  17. package/dist/recovery/proceeding-drive.js +89 -0
  18. package/dist/runtime/driver-messages.js +4 -4
  19. package/dist/runtime/reply-prompt-copy.d.ts +5 -1
  20. package/dist/runtime/reply-prompt-copy.js +13 -3
  21. package/dist/server.d.ts +1 -0
  22. package/dist/server.js +68 -14
  23. package/dist/tools/cmd-runner.js +76 -17
  24. package/dist/tools/ctrl.d.ts +1 -1
  25. package/dist/tools/ctrl.js +29 -27
  26. package/dist/tools/prompts/control/en/principles.md +2 -2
  27. package/dist/tools/prompts/control/en/scenarios.md +25 -4
  28. package/dist/tools/prompts/control/en/tools.md +17 -8
  29. package/dist/tools/prompts/control/zh/principles.md +2 -2
  30. package/dist/tools/prompts/control/zh/scenarios.md +25 -4
  31. package/dist/tools/prompts/control/zh/tools.md +17 -8
  32. package/dist/utils/task-package.d.ts +1 -1
  33. package/dist/utils/task-package.js +3 -4
  34. package/dist/utils/taskdoc.js +18 -23
  35. package/package.json +1 -1
  36. package/webapp/dist/assets/{_basePickBy-CGhMqD96.js → _basePickBy-C3SVVywm.js} +3 -3
  37. package/webapp/dist/assets/{_basePickBy-CGhMqD96.js.map → _basePickBy-C3SVVywm.js.map} +1 -1
  38. package/webapp/dist/assets/{_baseUniq-XCMW7z1Y.js → _baseUniq-egNq6cCa.js} +2 -2
  39. package/webapp/dist/assets/{_baseUniq-XCMW7z1Y.js.map → _baseUniq-egNq6cCa.js.map} +1 -1
  40. package/webapp/dist/assets/{arc-B6fzk0T5.js → arc-CASAlRvm.js} +2 -2
  41. package/webapp/dist/assets/{arc-B6fzk0T5.js.map → arc-CASAlRvm.js.map} +1 -1
  42. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-DmSI_GUt.js → architectureDiagram-2XIMDMQ5-B6n5FQmS.js} +7 -7
  43. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-DmSI_GUt.js.map → architectureDiagram-2XIMDMQ5-B6n5FQmS.js.map} +1 -1
  44. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-Bp0nb8IZ.js → blockDiagram-WCTKOSBZ-D2UGGjic.js} +7 -7
  45. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-Bp0nb8IZ.js.map → blockDiagram-WCTKOSBZ-D2UGGjic.js.map} +1 -1
  46. package/webapp/dist/assets/{c4Diagram-IC4MRINW-gYpylqGb.js → c4Diagram-IC4MRINW-BOY_bQFP.js} +3 -3
  47. package/webapp/dist/assets/{c4Diagram-IC4MRINW-gYpylqGb.js.map → c4Diagram-IC4MRINW-BOY_bQFP.js.map} +1 -1
  48. package/webapp/dist/assets/{channel-BmQ3YyUn.js → channel-CZ_X09H1.js} +2 -2
  49. package/webapp/dist/assets/{channel-BmQ3YyUn.js.map → channel-CZ_X09H1.js.map} +1 -1
  50. package/webapp/dist/assets/{chunk-4BX2VUAB-B5pwFRwA.js → chunk-4BX2VUAB-BIwyAzZQ.js} +2 -2
  51. package/webapp/dist/assets/{chunk-4BX2VUAB-B5pwFRwA.js.map → chunk-4BX2VUAB-BIwyAzZQ.js.map} +1 -1
  52. package/webapp/dist/assets/{chunk-55IACEB6-CZf6oMWM.js → chunk-55IACEB6-zSjTFoCX.js} +2 -2
  53. package/webapp/dist/assets/{chunk-55IACEB6-CZf6oMWM.js.map → chunk-55IACEB6-zSjTFoCX.js.map} +1 -1
  54. package/webapp/dist/assets/{chunk-FMBD7UC4-yZWCDzVz.js → chunk-FMBD7UC4-CTpuRfdB.js} +2 -2
  55. package/webapp/dist/assets/{chunk-FMBD7UC4-yZWCDzVz.js.map → chunk-FMBD7UC4-CTpuRfdB.js.map} +1 -1
  56. package/webapp/dist/assets/{chunk-JSJVCQXG-Cg1ST73M.js → chunk-JSJVCQXG-CJ3DM4in.js} +2 -2
  57. package/webapp/dist/assets/{chunk-JSJVCQXG-Cg1ST73M.js.map → chunk-JSJVCQXG-CJ3DM4in.js.map} +1 -1
  58. package/webapp/dist/assets/{chunk-KX2RTZJC-ByGtlX9q.js → chunk-KX2RTZJC-B97EakhO.js} +2 -2
  59. package/webapp/dist/assets/{chunk-KX2RTZJC-ByGtlX9q.js.map → chunk-KX2RTZJC-B97EakhO.js.map} +1 -1
  60. package/webapp/dist/assets/{chunk-NQ4KR5QH-DoXvfhSY.js → chunk-NQ4KR5QH-5o1o5x0z.js} +4 -4
  61. package/webapp/dist/assets/{chunk-NQ4KR5QH-DoXvfhSY.js.map → chunk-NQ4KR5QH-5o1o5x0z.js.map} +1 -1
  62. package/webapp/dist/assets/{chunk-QZHKN3VN-2gX2qsHB.js → chunk-QZHKN3VN-D33FSIEb.js} +2 -2
  63. package/webapp/dist/assets/{chunk-QZHKN3VN-2gX2qsHB.js.map → chunk-QZHKN3VN-D33FSIEb.js.map} +1 -1
  64. package/webapp/dist/assets/{chunk-WL4C6EOR-L-9bPNxS.js → chunk-WL4C6EOR-C2InqFin.js} +6 -6
  65. package/webapp/dist/assets/{chunk-WL4C6EOR-L-9bPNxS.js.map → chunk-WL4C6EOR-C2InqFin.js.map} +1 -1
  66. package/webapp/dist/assets/{classDiagram-VBA2DB6C-B79Oe3Df.js → classDiagram-VBA2DB6C-BfLZmK48.js} +7 -7
  67. package/webapp/dist/assets/{classDiagram-VBA2DB6C-B79Oe3Df.js.map → classDiagram-VBA2DB6C-BfLZmK48.js.map} +1 -1
  68. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-B79Oe3Df.js → classDiagram-v2-RAHNMMFH-BfLZmK48.js} +7 -7
  69. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-B79Oe3Df.js.map → classDiagram-v2-RAHNMMFH-BfLZmK48.js.map} +1 -1
  70. package/webapp/dist/assets/{clone-DvlY9Sdn.js → clone-BSCHnHfl.js} +2 -2
  71. package/webapp/dist/assets/{clone-DvlY9Sdn.js.map → clone-BSCHnHfl.js.map} +1 -1
  72. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-CtkGd0W6.js → cose-bilkent-S5V4N54A-CeQ1jAJJ.js} +2 -2
  73. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-CtkGd0W6.js.map → cose-bilkent-S5V4N54A-CeQ1jAJJ.js.map} +1 -1
  74. package/webapp/dist/assets/{dagre-KLK3FWXG-B3W6QTXQ.js → dagre-KLK3FWXG-WUuNYzcK.js} +7 -7
  75. package/webapp/dist/assets/{dagre-KLK3FWXG-B3W6QTXQ.js.map → dagre-KLK3FWXG-WUuNYzcK.js.map} +1 -1
  76. package/webapp/dist/assets/{diagram-E7M64L7V-B6-J_E5Q.js → diagram-E7M64L7V-jOVCIExP.js} +8 -8
  77. package/webapp/dist/assets/{diagram-E7M64L7V-B6-J_E5Q.js.map → diagram-E7M64L7V-jOVCIExP.js.map} +1 -1
  78. package/webapp/dist/assets/{diagram-IFDJBPK2-C-PH_Yx3.js → diagram-IFDJBPK2-cCeQqotA.js} +7 -7
  79. package/webapp/dist/assets/{diagram-IFDJBPK2-C-PH_Yx3.js.map → diagram-IFDJBPK2-cCeQqotA.js.map} +1 -1
  80. package/webapp/dist/assets/{diagram-P4PSJMXO-CB5xXDup.js → diagram-P4PSJMXO-DjAYFRLv.js} +7 -7
  81. package/webapp/dist/assets/{diagram-P4PSJMXO-CB5xXDup.js.map → diagram-P4PSJMXO-DjAYFRLv.js.map} +1 -1
  82. package/webapp/dist/assets/{erDiagram-INFDFZHY-CuAhTGVU.js → erDiagram-INFDFZHY-Dl_6U5fV.js} +5 -5
  83. package/webapp/dist/assets/{erDiagram-INFDFZHY-CuAhTGVU.js.map → erDiagram-INFDFZHY-Dl_6U5fV.js.map} +1 -1
  84. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-D3cIOfGj.js → flowDiagram-PKNHOUZH-D80nrZ3S.js} +7 -7
  85. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-D3cIOfGj.js.map → flowDiagram-PKNHOUZH-D80nrZ3S.js.map} +1 -1
  86. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-CSFhVYXV.js → ganttDiagram-A5KZAMGK-T3WdMrgj.js} +3 -3
  87. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-CSFhVYXV.js.map → ganttDiagram-A5KZAMGK-T3WdMrgj.js.map} +1 -1
  88. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-CiEaxACG.js → gitGraphDiagram-K3NZZRJ6-ifV-jkKL.js} +8 -8
  89. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-CiEaxACG.js.map → gitGraphDiagram-K3NZZRJ6-ifV-jkKL.js.map} +1 -1
  90. package/webapp/dist/assets/{graph-CYGALRuy.js → graph-D5jmnb35.js} +3 -3
  91. package/webapp/dist/assets/{graph-CYGALRuy.js.map → graph-D5jmnb35.js.map} +1 -1
  92. package/webapp/dist/assets/{index-BTazqQrV.js → index-CGbZlct2.js} +503 -142
  93. package/webapp/dist/assets/{index-BTazqQrV.js.map → index-CGbZlct2.js.map} +1 -1
  94. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-CiWJjQyU.js → infoDiagram-LFFYTUFH-C340CY5x.js} +6 -6
  95. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-CiWJjQyU.js.map → infoDiagram-LFFYTUFH-C340CY5x.js.map} +1 -1
  96. package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56-MlIIUO7o.js → ishikawaDiagram-PHBUUO56-Bu4RsELK.js} +2 -2
  97. package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56-MlIIUO7o.js.map → ishikawaDiagram-PHBUUO56-Bu4RsELK.js.map} +1 -1
  98. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-BBqwkA3d.js → journeyDiagram-4ABVD52K-Dvv3ypKA.js} +5 -5
  99. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-BBqwkA3d.js.map → journeyDiagram-4ABVD52K-Dvv3ypKA.js.map} +1 -1
  100. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-BS4GzDbo.js → kanban-definition-K7BYSVSG-D_rsetjW.js} +3 -3
  101. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-BS4GzDbo.js.map → kanban-definition-K7BYSVSG-D_rsetjW.js.map} +1 -1
  102. package/webapp/dist/assets/{layout-DaJO-1DW.js → layout-CLmOfwnS.js} +5 -5
  103. package/webapp/dist/assets/{layout-DaJO-1DW.js.map → layout-CLmOfwnS.js.map} +1 -1
  104. package/webapp/dist/assets/{linear-SWy2Cl7G.js → linear-DFAmViqi.js} +2 -2
  105. package/webapp/dist/assets/{linear-SWy2Cl7G.js.map → linear-DFAmViqi.js.map} +1 -1
  106. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-B_gNrT3l.js → mindmap-definition-YRQLILUH-Ce3_czeS.js} +4 -4
  107. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-B_gNrT3l.js.map → mindmap-definition-YRQLILUH-Ce3_czeS.js.map} +1 -1
  108. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-CVh3lLBA.js → pieDiagram-SKSYHLDU-U6_un5Sc.js} +8 -8
  109. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-CVh3lLBA.js.map → pieDiagram-SKSYHLDU-U6_un5Sc.js.map} +1 -1
  110. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-D0zAkVD7.js → quadrantDiagram-337W2JSQ-DmVJQItS.js} +3 -3
  111. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-D0zAkVD7.js.map → quadrantDiagram-337W2JSQ-DmVJQItS.js.map} +1 -1
  112. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP--CTFD60w.js → requirementDiagram-Z7DCOOCP-BkNhSY-g.js} +4 -4
  113. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP--CTFD60w.js.map → requirementDiagram-Z7DCOOCP-BkNhSY-g.js.map} +1 -1
  114. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-XZnViaAX.js → sankeyDiagram-WA2Y5GQK-E3UEy5SX.js} +2 -2
  115. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-XZnViaAX.js.map → sankeyDiagram-WA2Y5GQK-E3UEy5SX.js.map} +1 -1
  116. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-BWAMhn_x.js → sequenceDiagram-2WXFIKYE-k0w9WKAO.js} +4 -4
  117. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-BWAMhn_x.js.map → sequenceDiagram-2WXFIKYE-k0w9WKAO.js.map} +1 -1
  118. package/webapp/dist/assets/{stateDiagram-RAJIS63D--jsLD0Dg.js → stateDiagram-RAJIS63D-DRgdJrlx.js} +9 -9
  119. package/webapp/dist/assets/{stateDiagram-RAJIS63D--jsLD0Dg.js.map → stateDiagram-RAJIS63D-DRgdJrlx.js.map} +1 -1
  120. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-DUG9vyI5.js → stateDiagram-v2-FVOUBMTO-edtkX9x6.js} +5 -5
  121. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-DUG9vyI5.js.map → stateDiagram-v2-FVOUBMTO-edtkX9x6.js.map} +1 -1
  122. package/webapp/dist/assets/{timeline-definition-YZTLITO2-BROLp1hL.js → timeline-definition-YZTLITO2-D0zyXyNL.js} +3 -3
  123. package/webapp/dist/assets/{timeline-definition-YZTLITO2-BROLp1hL.js.map → timeline-definition-YZTLITO2-D0zyXyNL.js.map} +1 -1
  124. package/webapp/dist/assets/{treemap-KZPCXAKY-BXuIlbYo.js → treemap-KZPCXAKY-CglsYqbQ.js} +5 -5
  125. package/webapp/dist/assets/{treemap-KZPCXAKY-BXuIlbYo.js.map → treemap-KZPCXAKY-CglsYqbQ.js.map} +1 -1
  126. package/webapp/dist/assets/{vennDiagram-LZ73GAT5-BXWd8gBB.js → vennDiagram-LZ73GAT5-DsUizzvt.js} +2 -2
  127. package/webapp/dist/assets/{vennDiagram-LZ73GAT5-BXWd8gBB.js.map → vennDiagram-LZ73GAT5-DsUizzvt.js.map} +1 -1
  128. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-DR5z9o6h.js → xychartDiagram-JWTSCODW-DvYQ4zKY.js} +3 -3
  129. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-DR5z9o6h.js.map → xychartDiagram-JWTSCODW-DvYQ4zKY.js.map} +1 -1
  130. package/webapp/dist/index.html +1 -1
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.recoverProceedingDrivesAfterRestart = recoverProceedingDrivesAfterRestart;
4
+ const dialog_display_state_1 = require("../dialog-display-state");
5
+ const dialog_global_registry_1 = require("../dialog-global-registry");
6
+ const dialog_instance_registry_1 = require("../dialog-instance-registry");
7
+ const kernel_driver_1 = require("../llm/kernel-driver");
8
+ const log_1 = require("../log");
9
+ const persistence_1 = require("../persistence");
10
+ const persistence_errors_1 = require("../persistence-errors");
11
+ const log = (0, log_1.createLogger)('proceeding-drive-recovery');
12
+ async function restoreDialogForProceedingDrive(dialogId) {
13
+ const mainDialog = await (0, dialog_instance_registry_1.getOrRestoreMainDialog)(dialogId.rootId, 'running');
14
+ if (!mainDialog) {
15
+ return undefined;
16
+ }
17
+ if (dialogId.selfId === dialogId.rootId) {
18
+ return mainDialog;
19
+ }
20
+ return await (0, dialog_instance_registry_1.ensureDialogLoaded)(mainDialog, dialogId, 'running');
21
+ }
22
+ async function recoverRootProceedingDrive(dialog) {
23
+ await persistence_1.DialogPersistence.setNeedsDrive(dialog.id, true, dialog.status);
24
+ dialog_global_registry_1.globalDialogRegistry.markNeedsDrive(dialog.id.rootId, {
25
+ source: 'restart_recovery',
26
+ reason: 'persisted_drive_in_progress',
27
+ });
28
+ }
29
+ async function recoverSideDialogProceedingDrive(dialog) {
30
+ await (0, kernel_driver_1.driveDialogStream)(dialog, undefined, true, {
31
+ source: 'kernel_driver_sideDialog_resume',
32
+ reason: 'restart_recovery:persisted_drive_in_progress',
33
+ resumeInProgressGeneration: true,
34
+ });
35
+ }
36
+ async function recoverProceedingDrivesAfterRestart() {
37
+ const dialogIds = await persistence_1.DialogPersistence.listAllDialogIds('running');
38
+ const recoveredRootIds = new Set();
39
+ const recoveredDialogKeys = new Set();
40
+ for (const dialogId of dialogIds) {
41
+ let latest;
42
+ try {
43
+ latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
44
+ }
45
+ catch (error) {
46
+ if (!(0, persistence_errors_1.findDomindsPersistenceFileError)(error)) {
47
+ throw error;
48
+ }
49
+ log.warn('Skipping malformed dialog during proceeding-drive restart recovery', error, {
50
+ dialogId: dialogId.valueOf(),
51
+ });
52
+ continue;
53
+ }
54
+ if (!(0, dialog_display_state_1.isRecoverableGeneratingLatest)(latest)) {
55
+ continue;
56
+ }
57
+ try {
58
+ const dialog = await restoreDialogForProceedingDrive(dialogId);
59
+ if (!dialog) {
60
+ log.warn('Proceeding-drive restart recovery could not restore dialog', undefined, {
61
+ rootId: dialogId.rootId,
62
+ selfId: dialogId.selfId,
63
+ });
64
+ continue;
65
+ }
66
+ const dialogKey = dialog.id.key();
67
+ if (recoveredDialogKeys.has(dialogKey)) {
68
+ continue;
69
+ }
70
+ recoveredDialogKeys.add(dialogKey);
71
+ if (dialog.id.selfId === dialog.id.rootId) {
72
+ if (recoveredRootIds.has(dialog.id.rootId)) {
73
+ continue;
74
+ }
75
+ recoveredRootIds.add(dialog.id.rootId);
76
+ await recoverRootProceedingDrive(dialog);
77
+ }
78
+ else {
79
+ await recoverSideDialogProceedingDrive(dialog);
80
+ }
81
+ }
82
+ catch (error) {
83
+ log.error('Failed to recover proceeding drive after restart', error, {
84
+ rootId: dialogId.rootId,
85
+ selfId: dialogId.selfId,
86
+ });
87
+ }
88
+ }
89
+ }
@@ -407,7 +407,7 @@ function formatAgentFacingContextHealthV3RemediationGuide(language, args) {
407
407
  '',
408
408
  '操作:',
409
409
  '- 优先新增差遣牒章节保存讨论细节:do_mind({ "category": "<category>", "selector": "<selector>", "content": "..." })',
410
- '- 只有在确实需要改写已有章节、且已对照当前差遣牒内容完成合并并带上 `previous_content_hash` 时,才更新:change_mind({"selector":"<selector>","content":"...","previous_content_hash":"sha256:..."})',
410
+ '- 只有在确实需要改写已有章节、且已对照当前差遣牒内容完成合并时,才先调用 recall_taskdoc({"selector":"<selector>"}) 取得 content_hash,再更新:change_mind({"selector":"<selector>","content":"...","previous_content_hash":"crc32:..."})',
411
411
  '- 优先新增过桥提醒项:add_reminder({ "content": "..." })',
412
412
  '- 只有在确实能就地复用现有提醒项、且不会额外增加当前程认知负担时,才更新:update_reminder({ "reminder_id": "<现有 reminder_id>", "content": "..." })',
413
413
  ].join('\n');
@@ -443,7 +443,7 @@ function formatAgentFacingContextHealthV3RemediationGuide(language, args) {
443
443
  '',
444
444
  '操作:',
445
445
  '- 优先新增差遣牒章节保存讨论细节:do_mind({ "category": "<category>", "selector": "<selector>", "content": "..." })',
446
- '- 只有在确实需要改写已有章节、且已对照当前差遣牒内容完成合并并带上 `previous_content_hash` 时,才更新:change_mind({"selector":"<selector>","content":"...","previous_content_hash":"sha256:..."})',
446
+ '- 只有在确实需要改写已有章节、且已对照当前差遣牒内容完成合并时,才先调用 recall_taskdoc({"selector":"<selector>"}) 取得 content_hash,再更新:change_mind({"selector":"<selector>","content":"...","previous_content_hash":"crc32:..."})',
447
447
  '- 优先新增过桥提醒项:add_reminder({ "content": "..." })',
448
448
  '- 只有在确实能就地复用现有提醒项、且不会额外增加当前程认知负担时,才更新:update_reminder({ "reminder_id": "<现有 reminder_id>", "content": "..." })',
449
449
  '- clear_mind({})',
@@ -484,7 +484,7 @@ function formatAgentFacingContextHealthV3RemediationGuide(language, args) {
484
484
  '',
485
485
  'Operations:',
486
486
  '- Prefer creating a new Taskdoc section for discussion details: do_mind({ "category": "<category>", "selector": "<selector>", "content": "..." })',
487
- '- Only update when an existing section truly needs rewriting and you have merged against the current Taskdoc content and can provide `previous_content_hash`: change_mind({"selector":"<selector>","content":"...","previous_content_hash":"sha256:..."})',
487
+ '- Only update when an existing section truly needs rewriting and you have merged against the current Taskdoc content: first call recall_taskdoc({"selector":"<selector>"}) for content_hash, then change_mind({"selector":"<selector>","content":"...","previous_content_hash":"crc32:..."})',
488
488
  '- Prefer adding a bridge reminder first: add_reminder({ "content": "..." })',
489
489
  '- Only if an existing reminder is clearly the right place, and updating it would not add extra cognitive load in the current course: update_reminder({ "reminder_id": "<existing reminder_id>", "content": "..." })',
490
490
  ].join('\n');
@@ -520,7 +520,7 @@ function formatAgentFacingContextHealthV3RemediationGuide(language, args) {
520
520
  '',
521
521
  'Operations:',
522
522
  '- Prefer creating a new Taskdoc section for discussion details: do_mind({ "category": "<category>", "selector": "<selector>", "content": "..." })',
523
- '- Only update when an existing section truly needs rewriting and you have merged against the current Taskdoc content and can provide `previous_content_hash`: change_mind({"selector":"<selector>","content":"...","previous_content_hash":"sha256:..."})',
523
+ '- Only update when an existing section truly needs rewriting and you have merged against the current Taskdoc content: first call recall_taskdoc({"selector":"<selector>"}) for content_hash, then change_mind({"selector":"<selector>","content":"...","previous_content_hash":"crc32:..."})',
524
524
  '- Prefer adding a bridge reminder first: add_reminder({ "content": "..." })',
525
525
  '- Only if an existing reminder is clearly the right place, and updating it would not add extra cognitive load in the current course: update_reminder({ "reminder_id": "<existing reminder_id>", "content": "..." })',
526
526
  '- clear_mind({})',
@@ -2,6 +2,10 @@ import type { LanguageCode } from '@longrun-ai/kernel/types/language';
2
2
  import type { TellaskReplyDirective } from '@longrun-ai/kernel/types/storage';
3
3
  export declare const ACTIVE_REPLY_TOOL_PREFIX_EN = "[Dominds active reply tool]";
4
4
  export declare const ACTIVE_REPLY_TOOL_PREFIX_ZH = "[Dominds \u5F53\u524D\u56DE\u590D\u5DE5\u5177]";
5
+ export declare const NO_ACTIVE_REPLY_PREFIX_EN = "[Dominds no active inter-dialog reply]";
6
+ export declare const NO_ACTIVE_REPLY_PREFIX_ZH = "[Dominds \u5F53\u524D\u65E0\u8DE8\u5BF9\u8BDD\u56DE\u590D\u4E49\u52A1]";
7
+ export declare const REPLY_TOOL_REMINDER_PREFIX_EN = "[Dominds replyTellask required]";
8
+ export declare const REPLY_TOOL_REMINDER_PREFIX_ZH = "[Dominds \u5FC5\u987B\u8C03\u7528\u56DE\u590D\u5DE5\u5177]";
5
9
  export declare const REPLY_REASSERTION_PREFIX_EN = "[Dominds long-line reminder]";
6
10
  export declare const REPLY_REASSERTION_PREFIX_ZH = "[Dominds \u957F\u7EBF\u63D0\u9192]";
7
11
  export declare const REPLY_SUPPRESSION_PREFIX_EN = "[Dominds handle this interjection first]";
@@ -30,8 +34,8 @@ export declare function buildReplyObligationReassertionText(args: ReplyObligatio
30
34
  export declare function buildReplyToolReminderText(args: {
31
35
  language: LanguageCode;
32
36
  directive: TellaskReplyDirective;
33
- prefix: string;
34
37
  replyTargetAgentId?: string;
35
38
  }): string;
39
+ export declare function isReplyToolReminderPromptContent(content: string): boolean;
36
40
  export declare function isStandaloneRuntimeGuidePromptContent(content: string): boolean;
37
41
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.REPLY_SUPPRESSION_PREFIX_ZH = exports.REPLY_SUPPRESSION_PREFIX_EN = exports.REPLY_REASSERTION_PREFIX_ZH = exports.REPLY_REASSERTION_PREFIX_EN = exports.ACTIVE_REPLY_TOOL_PREFIX_ZH = exports.ACTIVE_REPLY_TOOL_PREFIX_EN = void 0;
3
+ exports.REPLY_SUPPRESSION_PREFIX_ZH = exports.REPLY_SUPPRESSION_PREFIX_EN = exports.REPLY_REASSERTION_PREFIX_ZH = exports.REPLY_REASSERTION_PREFIX_EN = exports.REPLY_TOOL_REMINDER_PREFIX_ZH = exports.REPLY_TOOL_REMINDER_PREFIX_EN = exports.NO_ACTIVE_REPLY_PREFIX_ZH = exports.NO_ACTIVE_REPLY_PREFIX_EN = exports.ACTIVE_REPLY_TOOL_PREFIX_ZH = exports.ACTIVE_REPLY_TOOL_PREFIX_EN = void 0;
4
4
  exports.buildActiveReplyToolNote = buildActiveReplyToolNote;
5
5
  exports.buildActiveReplyObligationContextText = buildActiveReplyObligationContextText;
6
6
  exports.buildSideDialogCompletionRule = buildSideDialogCompletionRule;
@@ -8,10 +8,15 @@ exports.buildSideDialogRoleHeaderCopy = buildSideDialogRoleHeaderCopy;
8
8
  exports.buildReplyObligationSuppressionGuideText = buildReplyObligationSuppressionGuideText;
9
9
  exports.buildReplyObligationReassertionText = buildReplyObligationReassertionText;
10
10
  exports.buildReplyToolReminderText = buildReplyToolReminderText;
11
+ exports.isReplyToolReminderPromptContent = isReplyToolReminderPromptContent;
11
12
  exports.isStandaloneRuntimeGuidePromptContent = isStandaloneRuntimeGuidePromptContent;
12
13
  const tellask_labels_1 = require("./tellask-labels");
13
14
  exports.ACTIVE_REPLY_TOOL_PREFIX_EN = '[Dominds active reply tool]';
14
15
  exports.ACTIVE_REPLY_TOOL_PREFIX_ZH = '[Dominds 当前回复工具]';
16
+ exports.NO_ACTIVE_REPLY_PREFIX_EN = '[Dominds no active inter-dialog reply]';
17
+ exports.NO_ACTIVE_REPLY_PREFIX_ZH = '[Dominds 当前无跨对话回复义务]';
18
+ exports.REPLY_TOOL_REMINDER_PREFIX_EN = '[Dominds replyTellask required]';
19
+ exports.REPLY_TOOL_REMINDER_PREFIX_ZH = '[Dominds 必须调用回复工具]';
15
20
  exports.REPLY_REASSERTION_PREFIX_EN = '[Dominds long-line reminder]';
16
21
  exports.REPLY_REASSERTION_PREFIX_ZH = '[Dominds 长线提醒]';
17
22
  exports.REPLY_SUPPRESSION_PREFIX_EN = '[Dominds handle this interjection first]';
@@ -139,9 +144,10 @@ function buildReplyObligationReassertionText(args) {
139
144
  ].join('\n');
140
145
  }
141
146
  function buildReplyToolReminderText(args) {
147
+ const prefix = args.language === 'zh' ? exports.REPLY_TOOL_REMINDER_PREFIX_ZH : exports.REPLY_TOOL_REMINDER_PREFIX_EN;
142
148
  return args.language === 'zh'
143
149
  ? [
144
- args.prefix,
150
+ prefix,
145
151
  '',
146
152
  `你刚才已经写了正文,但还没调用 \`${args.directive.expectedReplyCallName}\`。`,
147
153
  '',
@@ -149,7 +155,7 @@ function buildReplyToolReminderText(args) {
149
155
  '如果你再次直接输出最终消息而仍不调用该工具,运行时会按 direct-reply fallback 投递,并在 UI/传递正文中明确标注。',
150
156
  ].join('\n')
151
157
  : [
152
- args.prefix,
158
+ prefix,
153
159
  '',
154
160
  `You already wrote the reply body, but you still have not called \`${args.directive.expectedReplyCallName}\`.`,
155
161
  '',
@@ -157,6 +163,10 @@ function buildReplyToolReminderText(args) {
157
163
  'If you still emit a plain final message without the tool, runtime will deliver it via direct-reply fallback and label that path explicitly in UI and transfer text.',
158
164
  ].join('\n');
159
165
  }
166
+ function isReplyToolReminderPromptContent(content) {
167
+ return (content.startsWith(exports.REPLY_TOOL_REMINDER_PREFIX_ZH) ||
168
+ content.startsWith(exports.REPLY_TOOL_REMINDER_PREFIX_EN));
169
+ }
160
170
  function isStandaloneRuntimeGuidePromptContent(content) {
161
171
  return (content.startsWith(exports.REPLY_REASSERTION_PREFIX_ZH) ||
162
172
  content.startsWith(exports.REPLY_REASSERTION_PREFIX_EN) ||
package/dist/server.d.ts CHANGED
@@ -7,6 +7,7 @@ export type ServerOptions = {
7
7
  host?: string;
8
8
  mode?: 'dev' | 'prod';
9
9
  startBackendDriver?: boolean;
10
+ returnAfterListen?: boolean;
10
11
  strictPort?: boolean;
11
12
  portAutoDirection?: WebuiPortAutoDirection;
12
13
  };
package/dist/server.js CHANGED
@@ -50,6 +50,7 @@ const dialog_display_state_1 = require("./dialog-display-state");
50
50
  const kernel_driver_1 = require("./llm/kernel-driver");
51
51
  const log_1 = require("./log");
52
52
  const supervisor_1 = require("./mcp/supervisor");
53
+ const proceeding_drive_1 = require("./recovery/proceeding-drive");
53
54
  const reply_special_1 = require("./recovery/reply-special");
54
55
  const work_language_1 = require("./runtime/work-language");
55
56
  const auth_1 = require("./server/auth");
@@ -113,12 +114,53 @@ function parseArgs(argv) {
113
114
  }
114
115
  return out;
115
116
  }
117
+ function attachPostListenStartupCancellation(httpServer, token) {
118
+ const originalStop = httpServer.stop.bind(httpServer);
119
+ httpServer.stop = async () => {
120
+ token.canceled = true;
121
+ await originalStop();
122
+ };
123
+ return httpServer;
124
+ }
116
125
  function getErrnoCode(error) {
117
126
  if (!(error instanceof Error))
118
127
  return undefined;
119
128
  const withCode = error;
120
129
  return typeof withCode.code === 'string' ? withCode.code : undefined;
121
130
  }
131
+ async function runPostListenStartup(params) {
132
+ await new Promise((resolve) => {
133
+ setImmediate(resolve);
134
+ });
135
+ if (params.token.canceled)
136
+ return;
137
+ // Apps host is optional for server boot: app failures must stay loud, but they must not block WebUI startup.
138
+ try {
139
+ await (0, runtime_1.initAppsRuntime)({ rtwsRootAbs: params.rtwsRootAbs, kernel: params.kernel });
140
+ }
141
+ catch (error) {
142
+ if (params.token.canceled)
143
+ return;
144
+ log.warn('Apps runtime initialization failed during server startup; continuing without app runtime capabilities until the app issue is fixed', error instanceof Error ? error : new Error(String(error)));
145
+ }
146
+ if (params.token.canceled)
147
+ return;
148
+ // Crash recovery: persisted in-flight generations are re-queued; stale non-generating queues
149
+ // are surfaced as blocked/resumable from durable facts.
150
+ await (0, dialog_display_state_1.reconcileDisplayStatesAfterRestart)();
151
+ if (params.token.canceled)
152
+ return;
153
+ await (0, proceeding_drive_1.recoverProceedingDrivesAfterRestart)();
154
+ if (params.token.canceled)
155
+ return;
156
+ await (0, reply_special_1.recoverPendingReplyTellaskCallsAfterRestart)();
157
+ if (params.token.canceled)
158
+ return;
159
+ // Tests may opt out so the process can shut down cleanly without a driver stop API.
160
+ if (params.startBackendDriver) {
161
+ void (0, kernel_driver_1.runBackendDriver)();
162
+ }
163
+ }
122
164
  async function startServer(opts = {}) {
123
165
  const { language: resolvedLanguage, source } = (0, work_language_1.resolveWorkLanguage)({ env: process.env });
124
166
  (0, work_language_1.setWorkLanguage)(resolvedLanguage);
@@ -129,6 +171,7 @@ async function startServer(opts = {}) {
129
171
  const portAutoDirection = opts.portAutoDirection ?? 'down';
130
172
  const host = opts.host || '127.0.0.1';
131
173
  const startBackendDriver = opts.startBackendDriver ?? true;
174
+ const returnAfterListen = opts.returnAfterListen === true;
132
175
  const portCandidates = (0, port_selection_1.buildWebuiPortCandidates)({
133
176
  preferredPort,
134
177
  strictPort,
@@ -182,37 +225,48 @@ async function startServer(opts = {}) {
182
225
  if (!strictPort && boundPort !== preferredPort) {
183
226
  log.warn(`WebUI preferred port ${preferredPort} was unavailable; listening on ${boundPort}`);
184
227
  }
228
+ const postListenStartupToken = { canceled: false };
229
+ const httpServer = attachPostListenStartupCancellation(startedCore, postListenStartupToken);
185
230
  try {
231
+ const rtwsRootAbs = process.cwd();
186
232
  (0, dominds_self_update_1.configureDomindsSelfUpdate)({
187
233
  host,
188
234
  port: boundPort,
189
235
  mode: serverMode,
190
236
  stopServer: async () => {
191
- await startedCore.stop();
237
+ await httpServer.stop();
192
238
  },
193
239
  });
194
240
  // MCP is best-effort: startup must not be blocked by MCP config/server issues.
195
- (0, supervisor_1.startMcpSupervisor)();
196
- // Apps host is optional for server boot: app failures must stay loud, but they must not block WebUI startup.
197
241
  try {
198
- await (0, runtime_1.initAppsRuntime)({ rtwsRootAbs: process.cwd(), kernel: { host, port: boundPort } });
242
+ (0, supervisor_1.startMcpSupervisor)();
199
243
  }
200
244
  catch (error) {
201
- log.warn('Apps runtime initialization failed during server startup; continuing without app runtime capabilities until the app issue is fixed', error instanceof Error ? error : new Error(String(error)));
245
+ log.warn('MCP supervisor startup failed during server startup; continuing without MCP runtime capabilities until the MCP issue is fixed', error instanceof Error ? error : new Error(String(error)));
202
246
  }
203
- // Crash recovery: any dialogs left in "proceeding" state are surfaced as interrupted/resumable.
204
- await (0, dialog_display_state_1.reconcileDisplayStatesAfterRestart)();
205
- await (0, reply_special_1.recoverPendingReplyTellaskCallsAfterRestart)();
206
- // Tests may opt out so the process can shut down cleanly without a driver stop API.
207
- if (startBackendDriver) {
208
- void (0, kernel_driver_1.runBackendDriver)();
247
+ const postListenStartup = runPostListenStartup({
248
+ rtwsRootAbs,
249
+ kernel: { host, port: boundPort },
250
+ startBackendDriver,
251
+ token: postListenStartupToken,
252
+ });
253
+ if (returnAfterListen) {
254
+ void postListenStartup.catch((error) => {
255
+ log.error('Post-listen server startup failed; WebUI remains reachable, but runtime recovery/driver startup did not complete', error instanceof Error ? error : new Error(String(error)));
256
+ });
257
+ }
258
+ else {
259
+ await postListenStartup;
209
260
  }
210
261
  }
211
262
  catch (error) {
212
- await startedCore.stop();
263
+ if (!returnAfterListen) {
264
+ await httpServer.stop();
265
+ await (0, runtime_1.shutdownAppsRuntime)();
266
+ }
213
267
  throw error;
214
268
  }
215
- return { httpServer: startedCore, auth, host, port: boundPort, mode };
269
+ return { httpServer, auth, host, port: boundPort, mode };
216
270
  }
217
271
  // Main function for CLI execution
218
272
  async function main() {
@@ -240,7 +294,7 @@ async function main() {
240
294
  const portAutoDirection = parsedPort?.portAutoDirection;
241
295
  const host = cliArgs['H'] || undefined;
242
296
  const mode = cliArgs['mode'] || undefined;
243
- await startServer({ port, host, mode, strictPort, portAutoDirection });
297
+ await startServer({ port, host, mode, strictPort, portAutoDirection, returnAfterListen: true });
244
298
  }
245
299
  // Start server if this file is run directly
246
300
  if (require.main === module) {
@@ -37,18 +37,13 @@ class ScrollingBuffer {
37
37
  };
38
38
  }
39
39
  }
40
- function sendIpc(msg) {
41
- if (typeof process.send !== 'function') {
42
- throw new Error('cmd_runner must be launched with an IPC channel');
43
- }
44
- process.send(msg);
45
- }
46
40
  async function flushIpc(msg) {
47
- if (typeof process.send !== 'function') {
41
+ const send = process.send;
42
+ if (typeof send !== 'function') {
48
43
  throw new Error('cmd_runner must be launched with an IPC channel');
49
44
  }
50
45
  await new Promise((resolve, reject) => {
51
- process.send?.(msg, (error) => {
46
+ send.call(process, msg, (error) => {
52
47
  if (error) {
53
48
  reject(error);
54
49
  return;
@@ -120,6 +115,16 @@ async function ensureSocketParentDir(endpoint) {
120
115
  function writeSocketResponse(socket, response) {
121
116
  socket.end(`${JSON.stringify(response)}\n`);
122
117
  }
118
+ function isProcessAlive(pid) {
119
+ try {
120
+ process.kill(pid, 0);
121
+ return true;
122
+ }
123
+ catch (error) {
124
+ const code = typeof error === 'object' && error !== null ? error.code : undefined;
125
+ return code !== 'ESRCH';
126
+ }
127
+ }
123
128
  async function main() {
124
129
  const initMessage = await new Promise((resolve, reject) => {
125
130
  const timeout = setTimeout(() => {
@@ -139,8 +144,12 @@ async function main() {
139
144
  if (typeof daemonPid !== 'number') {
140
145
  throw new Error('cmd_runner failed to spawn daemon command: missing pid');
141
146
  }
147
+ const stdout = childProcess.stdout;
148
+ const stderr = childProcess.stderr;
149
+ if (stdout === null || stderr === null) {
150
+ throw new Error('cmd_runner failed to spawn daemon command with piped stdout/stderr');
151
+ }
142
152
  const endpoint = (0, cmd_runner_protocol_1.getCmdRunnerEndpointForDaemonPid)(daemonPid);
143
- await ensureSocketParentDir(endpoint);
144
153
  const state = {
145
154
  endpoint,
146
155
  daemonPid,
@@ -157,6 +166,46 @@ async function main() {
157
166
  let server;
158
167
  let closeRequested = false;
159
168
  let timeoutHandle;
169
+ let initialResultSent = false;
170
+ const tryFlushInitialResult = async (msg) => {
171
+ if (initialResultSent) {
172
+ return false;
173
+ }
174
+ initialResultSent = true;
175
+ await flushIpc(msg);
176
+ return true;
177
+ };
178
+ const waitForObservedChildExitOrMissingPid = async (timeoutMs) => {
179
+ if (!state.isRunning || closeRequested || !isProcessAlive(daemonPid)) {
180
+ return true;
181
+ }
182
+ return await new Promise((resolve) => {
183
+ let settled = false;
184
+ let timeout;
185
+ let interval;
186
+ const finish = (exited) => {
187
+ if (settled)
188
+ return;
189
+ settled = true;
190
+ clearTimeout(timeout);
191
+ clearInterval(interval);
192
+ childProcess.off('close', onClose);
193
+ resolve(exited || !state.isRunning || closeRequested || !isProcessAlive(daemonPid));
194
+ };
195
+ const onClose = () => {
196
+ finish(true);
197
+ };
198
+ timeout = setTimeout(() => {
199
+ finish(false);
200
+ }, timeoutMs);
201
+ interval = setInterval(() => {
202
+ if (!isProcessAlive(daemonPid)) {
203
+ finish(true);
204
+ }
205
+ }, 25);
206
+ childProcess.once('close', onClose);
207
+ });
208
+ };
160
209
  const closeServerAndExit = (code) => {
161
210
  if (closeRequested) {
162
211
  return;
@@ -204,8 +253,8 @@ async function main() {
204
253
  state.exitCode = code;
205
254
  state.exitSignal = signal;
206
255
  void (async () => {
207
- if (state.daemonCommandLine === null) {
208
- await flushIpc({
256
+ if (state.daemonCommandLine === null && !initialResultSent) {
257
+ await tryFlushInitialResult({
209
258
  type: 'completed',
210
259
  exitCode: code,
211
260
  exitSignal: signal,
@@ -222,7 +271,7 @@ async function main() {
222
271
  if (timeoutHandle) {
223
272
  clearTimeout(timeoutHandle);
224
273
  }
225
- void flushIpc({
274
+ void tryFlushInitialResult({
226
275
  type: 'failed',
227
276
  errorText: error.message,
228
277
  })
@@ -231,12 +280,16 @@ async function main() {
231
280
  closeServerAndExit(1);
232
281
  });
233
282
  });
234
- childProcess.stdout?.on('data', (data) => {
283
+ stdout.on('data', (data) => {
235
284
  state.stdout.addText(data.toString());
236
285
  });
237
- childProcess.stderr?.on('data', (data) => {
286
+ stderr.on('data', (data) => {
238
287
  state.stderr.addText(data.toString());
239
288
  });
289
+ await ensureSocketParentDir(endpoint);
290
+ if (closeRequested) {
291
+ return;
292
+ }
240
293
  server = node_net_1.default.createServer((socket) => {
241
294
  socket.setEncoding('utf8');
242
295
  let buffer = '';
@@ -309,6 +362,9 @@ async function main() {
309
362
  return;
310
363
  }
311
364
  if (daemonCommandLine === undefined || daemonCommandLine.trim() === '') {
365
+ if (await waitForObservedChildExitOrMissingPid(250)) {
366
+ return;
367
+ }
312
368
  try {
313
369
  process.kill(daemonPid, 'SIGTERM');
314
370
  }
@@ -318,7 +374,7 @@ async function main() {
318
374
  if (!state.isRunning) {
319
375
  return;
320
376
  }
321
- sendIpc({
377
+ await tryFlushInitialResult({
322
378
  type: 'failed',
323
379
  errorText: `failed to capture daemon command line from OS for pid ${String(daemonPid)}`,
324
380
  });
@@ -328,7 +384,7 @@ async function main() {
328
384
  return;
329
385
  }
330
386
  state.daemonCommandLine = daemonCommandLine;
331
- sendIpc({
387
+ await tryFlushInitialResult({
332
388
  type: 'daemonized',
333
389
  daemonPid,
334
390
  daemonCommandLine,
@@ -339,7 +395,10 @@ async function main() {
339
395
  startTime: state.startTime,
340
396
  });
341
397
  })().catch((error) => {
342
- sendIpc({
398
+ if (initialResultSent) {
399
+ return;
400
+ }
401
+ void tryFlushInitialResult({
343
402
  type: 'failed',
344
403
  errorText: error instanceof Error ? error.message : String(error),
345
404
  });
@@ -17,7 +17,7 @@
17
17
  * - change_mind: Main Dialog only; update a `.tsk/` Taskdoc section without starting a new course
18
18
  * - mind_more: Main Dialog only; append entries to a `.tsk/` Taskdoc section without starting a new course
19
19
  * - never_mind: Main Dialog only; delete a `.tsk/` Taskdoc section file without starting a new course
20
- * - recall_taskdoc: Read a Taskdoc section from `*.tsk/` by (category, selector)
20
+ * - recall_taskdoc: Read a Taskdoc section from `*.tsk/` by selector and optional category
21
21
  *
22
22
  * USAGE CONTEXT:
23
23
  * Can both be triggered by an agent autonomously, or by human with role='user' msg,