dominds 1.25.9 → 1.25.11

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 (105) hide show
  1. package/dist/llm/kernel-driver/drive.js +234 -34
  2. package/dist/llm/kernel-driver/fbr.js +2 -0
  3. package/dist/llm/kernel-driver/tellask-special.d.ts +1 -0
  4. package/dist/llm/kernel-driver/tellask-special.js +6 -1
  5. package/dist/server/dominds-self-update.d.ts +1 -0
  6. package/dist/server/dominds-self-update.js +63 -5
  7. package/dist/server/websocket-handler.js +31 -2
  8. package/dist/server.js +13 -0
  9. package/dist/tools/ctrl.js +1 -0
  10. package/package.json +3 -3
  11. package/webapp/dist/assets/{_basePickBy-B4PyBhGX.js → _basePickBy-BF9Zg9uq.js} +3 -3
  12. package/webapp/dist/assets/{_basePickBy-B4PyBhGX.js.map → _basePickBy-BF9Zg9uq.js.map} +1 -1
  13. package/webapp/dist/assets/{_baseUniq-Cj1Wg2fB.js → _baseUniq-CFjISsgz.js} +2 -2
  14. package/webapp/dist/assets/{_baseUniq-Cj1Wg2fB.js.map → _baseUniq-CFjISsgz.js.map} +1 -1
  15. package/webapp/dist/assets/{arc-CWrSn-79.js → arc-BVyYGzE7.js} +2 -2
  16. package/webapp/dist/assets/{arc-CWrSn-79.js.map → arc-BVyYGzE7.js.map} +1 -1
  17. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-7iHYM2Z2.js → architectureDiagram-2XIMDMQ5-SEmNTU1b.js} +7 -7
  18. package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-7iHYM2Z2.js.map → architectureDiagram-2XIMDMQ5-SEmNTU1b.js.map} +1 -1
  19. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-B1TGBHuF.js → blockDiagram-WCTKOSBZ-BndD4gLF.js} +7 -7
  20. package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-B1TGBHuF.js.map → blockDiagram-WCTKOSBZ-BndD4gLF.js.map} +1 -1
  21. package/webapp/dist/assets/{c4Diagram-IC4MRINW-BXTEEF9u.js → c4Diagram-IC4MRINW-fGAz7umu.js} +3 -3
  22. package/webapp/dist/assets/{c4Diagram-IC4MRINW-BXTEEF9u.js.map → c4Diagram-IC4MRINW-fGAz7umu.js.map} +1 -1
  23. package/webapp/dist/assets/{channel-C9f2l1ub.js → channel-Blt7S1Sn.js} +2 -2
  24. package/webapp/dist/assets/{channel-C9f2l1ub.js.map → channel-Blt7S1Sn.js.map} +1 -1
  25. package/webapp/dist/assets/{chunk-4BX2VUAB-CXrwbryc.js → chunk-4BX2VUAB-C2FKcyob.js} +2 -2
  26. package/webapp/dist/assets/{chunk-4BX2VUAB-CXrwbryc.js.map → chunk-4BX2VUAB-C2FKcyob.js.map} +1 -1
  27. package/webapp/dist/assets/{chunk-55IACEB6-WAoUKKa8.js → chunk-55IACEB6-CN8ZmdUP.js} +2 -2
  28. package/webapp/dist/assets/{chunk-55IACEB6-WAoUKKa8.js.map → chunk-55IACEB6-CN8ZmdUP.js.map} +1 -1
  29. package/webapp/dist/assets/{chunk-FMBD7UC4-CP1cvzbf.js → chunk-FMBD7UC4-B9Uq2tt2.js} +2 -2
  30. package/webapp/dist/assets/{chunk-FMBD7UC4-CP1cvzbf.js.map → chunk-FMBD7UC4-B9Uq2tt2.js.map} +1 -1
  31. package/webapp/dist/assets/{chunk-JSJVCQXG-Od7hQgy7.js → chunk-JSJVCQXG-vVrXi8LV.js} +2 -2
  32. package/webapp/dist/assets/{chunk-JSJVCQXG-Od7hQgy7.js.map → chunk-JSJVCQXG-vVrXi8LV.js.map} +1 -1
  33. package/webapp/dist/assets/{chunk-KX2RTZJC-Cp53YoAm.js → chunk-KX2RTZJC-DtZmdBq3.js} +2 -2
  34. package/webapp/dist/assets/{chunk-KX2RTZJC-Cp53YoAm.js.map → chunk-KX2RTZJC-DtZmdBq3.js.map} +1 -1
  35. package/webapp/dist/assets/{chunk-NQ4KR5QH-DkyaPUwI.js → chunk-NQ4KR5QH-C3rU1XEw.js} +4 -4
  36. package/webapp/dist/assets/{chunk-NQ4KR5QH-DkyaPUwI.js.map → chunk-NQ4KR5QH-C3rU1XEw.js.map} +1 -1
  37. package/webapp/dist/assets/{chunk-QZHKN3VN-DPcrZWPF.js → chunk-QZHKN3VN-CeHQK_vs.js} +2 -2
  38. package/webapp/dist/assets/{chunk-QZHKN3VN-DPcrZWPF.js.map → chunk-QZHKN3VN-CeHQK_vs.js.map} +1 -1
  39. package/webapp/dist/assets/{chunk-WL4C6EOR-BCx3EtFm.js → chunk-WL4C6EOR-Bb8GUwSo.js} +6 -6
  40. package/webapp/dist/assets/{chunk-WL4C6EOR-BCx3EtFm.js.map → chunk-WL4C6EOR-Bb8GUwSo.js.map} +1 -1
  41. package/webapp/dist/assets/{classDiagram-VBA2DB6C-BnwaVhDD.js → classDiagram-VBA2DB6C-CmP7X8Fj.js} +7 -7
  42. package/webapp/dist/assets/{classDiagram-VBA2DB6C-BnwaVhDD.js.map → classDiagram-VBA2DB6C-CmP7X8Fj.js.map} +1 -1
  43. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-BnwaVhDD.js → classDiagram-v2-RAHNMMFH-CmP7X8Fj.js} +7 -7
  44. package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-BnwaVhDD.js.map → classDiagram-v2-RAHNMMFH-CmP7X8Fj.js.map} +1 -1
  45. package/webapp/dist/assets/{clone-B32zbBXk.js → clone-CzGKO47U.js} +2 -2
  46. package/webapp/dist/assets/{clone-B32zbBXk.js.map → clone-CzGKO47U.js.map} +1 -1
  47. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-DexuXMat.js → cose-bilkent-S5V4N54A-3wbordhk.js} +2 -2
  48. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-DexuXMat.js.map → cose-bilkent-S5V4N54A-3wbordhk.js.map} +1 -1
  49. package/webapp/dist/assets/{dagre-KLK3FWXG-D-3S_ma4.js → dagre-KLK3FWXG-DrM7hFR5.js} +7 -7
  50. package/webapp/dist/assets/{dagre-KLK3FWXG-D-3S_ma4.js.map → dagre-KLK3FWXG-DrM7hFR5.js.map} +1 -1
  51. package/webapp/dist/assets/{diagram-E7M64L7V-CR7hat4A.js → diagram-E7M64L7V-D-bQEVk2.js} +8 -8
  52. package/webapp/dist/assets/{diagram-E7M64L7V-CR7hat4A.js.map → diagram-E7M64L7V-D-bQEVk2.js.map} +1 -1
  53. package/webapp/dist/assets/{diagram-IFDJBPK2-B6qnqfZU.js → diagram-IFDJBPK2-MaU_xQfR.js} +7 -7
  54. package/webapp/dist/assets/{diagram-IFDJBPK2-B6qnqfZU.js.map → diagram-IFDJBPK2-MaU_xQfR.js.map} +1 -1
  55. package/webapp/dist/assets/{diagram-P4PSJMXO-BDGFMbld.js → diagram-P4PSJMXO-BLklmc9h.js} +7 -7
  56. package/webapp/dist/assets/{diagram-P4PSJMXO-BDGFMbld.js.map → diagram-P4PSJMXO-BLklmc9h.js.map} +1 -1
  57. package/webapp/dist/assets/{erDiagram-INFDFZHY-BcXnNg1A.js → erDiagram-INFDFZHY-DHIVFsNv.js} +5 -5
  58. package/webapp/dist/assets/{erDiagram-INFDFZHY-BcXnNg1A.js.map → erDiagram-INFDFZHY-DHIVFsNv.js.map} +1 -1
  59. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-CgKu7usU.js → flowDiagram-PKNHOUZH-HbMxSdg1.js} +7 -7
  60. package/webapp/dist/assets/{flowDiagram-PKNHOUZH-CgKu7usU.js.map → flowDiagram-PKNHOUZH-HbMxSdg1.js.map} +1 -1
  61. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-BwkrQGcZ.js → ganttDiagram-A5KZAMGK-CxBETPNC.js} +3 -3
  62. package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-BwkrQGcZ.js.map → ganttDiagram-A5KZAMGK-CxBETPNC.js.map} +1 -1
  63. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-CVqi-T5p.js → gitGraphDiagram-K3NZZRJ6-DPV-fFTC.js} +8 -8
  64. package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-CVqi-T5p.js.map → gitGraphDiagram-K3NZZRJ6-DPV-fFTC.js.map} +1 -1
  65. package/webapp/dist/assets/{graph-B7VL6q5k.js → graph-C0MlCXJg.js} +3 -3
  66. package/webapp/dist/assets/{graph-B7VL6q5k.js.map → graph-C0MlCXJg.js.map} +1 -1
  67. package/webapp/dist/assets/{index-4xG_gPRh.js → index-CzHjX_nj.js} +168 -60
  68. package/webapp/dist/assets/{index-4xG_gPRh.js.map → index-CzHjX_nj.js.map} +1 -1
  69. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-a9WBcgds.js → infoDiagram-LFFYTUFH-ChTC2kD-.js} +6 -6
  70. package/webapp/dist/assets/{infoDiagram-LFFYTUFH-a9WBcgds.js.map → infoDiagram-LFFYTUFH-ChTC2kD-.js.map} +1 -1
  71. package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56-Bhiex3_9.js → ishikawaDiagram-PHBUUO56--aJd3LM6.js} +2 -2
  72. package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56-Bhiex3_9.js.map → ishikawaDiagram-PHBUUO56--aJd3LM6.js.map} +1 -1
  73. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-DdMDmgOQ.js → journeyDiagram-4ABVD52K-Bzd3cZTs.js} +5 -5
  74. package/webapp/dist/assets/{journeyDiagram-4ABVD52K-DdMDmgOQ.js.map → journeyDiagram-4ABVD52K-Bzd3cZTs.js.map} +1 -1
  75. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-C02Ns-Ee.js → kanban-definition-K7BYSVSG-DiFHcs58.js} +3 -3
  76. package/webapp/dist/assets/{kanban-definition-K7BYSVSG-C02Ns-Ee.js.map → kanban-definition-K7BYSVSG-DiFHcs58.js.map} +1 -1
  77. package/webapp/dist/assets/{layout-CKFoJLnz.js → layout-B9Hsf17G.js} +5 -5
  78. package/webapp/dist/assets/{layout-CKFoJLnz.js.map → layout-B9Hsf17G.js.map} +1 -1
  79. package/webapp/dist/assets/{linear-DK87WR34.js → linear-6xqU78Yu.js} +2 -2
  80. package/webapp/dist/assets/{linear-DK87WR34.js.map → linear-6xqU78Yu.js.map} +1 -1
  81. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-CNPEaAdT.js → mindmap-definition-YRQLILUH-BUI6M5up.js} +4 -4
  82. package/webapp/dist/assets/{mindmap-definition-YRQLILUH-CNPEaAdT.js.map → mindmap-definition-YRQLILUH-BUI6M5up.js.map} +1 -1
  83. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-BAieZgqR.js → pieDiagram-SKSYHLDU-FCGp31Lg.js} +8 -8
  84. package/webapp/dist/assets/{pieDiagram-SKSYHLDU-BAieZgqR.js.map → pieDiagram-SKSYHLDU-FCGp31Lg.js.map} +1 -1
  85. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-DNfJ-efQ.js → quadrantDiagram-337W2JSQ-bJcMGXM6.js} +3 -3
  86. package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-DNfJ-efQ.js.map → quadrantDiagram-337W2JSQ-bJcMGXM6.js.map} +1 -1
  87. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-Ci2ficlW.js → requirementDiagram-Z7DCOOCP-BghuL9nW.js} +4 -4
  88. package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-Ci2ficlW.js.map → requirementDiagram-Z7DCOOCP-BghuL9nW.js.map} +1 -1
  89. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-y2RH0qmH.js → sankeyDiagram-WA2Y5GQK-DpuFpv6d.js} +2 -2
  90. package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-y2RH0qmH.js.map → sankeyDiagram-WA2Y5GQK-DpuFpv6d.js.map} +1 -1
  91. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-C4Mbfwyu.js → sequenceDiagram-2WXFIKYE-BM6rPANl.js} +4 -4
  92. package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-C4Mbfwyu.js.map → sequenceDiagram-2WXFIKYE-BM6rPANl.js.map} +1 -1
  93. package/webapp/dist/assets/{stateDiagram-RAJIS63D-enKQJ7WW.js → stateDiagram-RAJIS63D-C_jQSre0.js} +9 -9
  94. package/webapp/dist/assets/{stateDiagram-RAJIS63D-enKQJ7WW.js.map → stateDiagram-RAJIS63D-C_jQSre0.js.map} +1 -1
  95. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-44zqAgYM.js → stateDiagram-v2-FVOUBMTO-BbQxj-LI.js} +5 -5
  96. package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-44zqAgYM.js.map → stateDiagram-v2-FVOUBMTO-BbQxj-LI.js.map} +1 -1
  97. package/webapp/dist/assets/{timeline-definition-YZTLITO2-Byr_GVOH.js → timeline-definition-YZTLITO2-qVPiYzDY.js} +3 -3
  98. package/webapp/dist/assets/{timeline-definition-YZTLITO2-Byr_GVOH.js.map → timeline-definition-YZTLITO2-qVPiYzDY.js.map} +1 -1
  99. package/webapp/dist/assets/{treemap-KZPCXAKY-BSv-k7HC.js → treemap-KZPCXAKY-CH_Gjw5E.js} +5 -5
  100. package/webapp/dist/assets/{treemap-KZPCXAKY-BSv-k7HC.js.map → treemap-KZPCXAKY-CH_Gjw5E.js.map} +1 -1
  101. package/webapp/dist/assets/{vennDiagram-LZ73GAT5-CPqduRWm.js → vennDiagram-LZ73GAT5-rpOhNWvx.js} +2 -2
  102. package/webapp/dist/assets/{vennDiagram-LZ73GAT5-CPqduRWm.js.map → vennDiagram-LZ73GAT5-rpOhNWvx.js.map} +1 -1
  103. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-B8yG6HaE.js → xychartDiagram-JWTSCODW-D1TcBJrI.js} +3 -3
  104. package/webapp/dist/assets/{xychartDiagram-JWTSCODW-B8yG6HaE.js.map → xychartDiagram-JWTSCODW-D1TcBJrI.js.map} +1 -1
  105. package/webapp/dist/index.html +1 -1
@@ -645,6 +645,7 @@ const TELLASK_SPECIAL_VIRTUAL_TOOLS = [
645
645
  {
646
646
  type: 'func',
647
647
  name: 'replyTellask',
648
+ followupMode: 'deferred',
648
649
  description: 'Deliver final reply for the current tellask session.',
649
650
  parameters: {
650
651
  type: 'object',
@@ -661,6 +662,7 @@ const TELLASK_SPECIAL_VIRTUAL_TOOLS = [
661
662
  {
662
663
  type: 'func',
663
664
  name: 'replyTellaskSessionless',
665
+ followupMode: 'deferred',
664
666
  description: 'Deliver final reply for the current one-shot tellask.',
665
667
  parameters: {
666
668
  type: 'object',
@@ -677,6 +679,7 @@ const TELLASK_SPECIAL_VIRTUAL_TOOLS = [
677
679
  {
678
680
  type: 'func',
679
681
  name: 'replyTellaskBack',
682
+ followupMode: 'deferred',
680
683
  description: 'Deliver final reply for the current tellaskBack request.',
681
684
  parameters: {
682
685
  type: 'object',
@@ -1228,6 +1231,49 @@ async function persistInvalidFuncCallRuntimeGuide(args) {
1228
1231
  });
1229
1232
  await persistAndPostRuntimeGuide(dlg, content);
1230
1233
  }
1234
+ function isQueuedNewCourseRuntimePrompt(prompt) {
1235
+ return (prompt?.kind === 'new_course_runtime_guide' ||
1236
+ prompt?.kind === 'new_course_runtime_reply' ||
1237
+ prompt?.kind === 'new_course_runtime_sideDialog');
1238
+ }
1239
+ function queuedNewCourseRuntimePromptToKernelPrompt(prompt) {
1240
+ const common = {
1241
+ content: prompt.prompt,
1242
+ msgId: prompt.msgId,
1243
+ grammar: prompt.grammar ?? 'markdown',
1244
+ userLanguageCode: prompt.userLanguageCode,
1245
+ runControl: prompt.runControl,
1246
+ origin: 'runtime',
1247
+ ...(prompt.skipTaskdoc === undefined ? {} : { skipTaskdoc: prompt.skipTaskdoc }),
1248
+ };
1249
+ switch (prompt.kind) {
1250
+ case 'new_course_runtime_guide':
1251
+ return common;
1252
+ case 'new_course_runtime_reply':
1253
+ return {
1254
+ ...common,
1255
+ tellaskReplyDirective: prompt.tellaskReplyDirective,
1256
+ };
1257
+ case 'new_course_runtime_sideDialog':
1258
+ return {
1259
+ ...common,
1260
+ tellaskReplyDirective: prompt.tellaskReplyDirective,
1261
+ calleeDialogReplyTarget: prompt.calleeDialogReplyTarget,
1262
+ };
1263
+ }
1264
+ }
1265
+ async function consumeQueuedNewCourseRuntimePromptForSameDrive(dlg) {
1266
+ const queuedPrompt = dlg.peekQueuedPrompt();
1267
+ if (!isQueuedNewCourseRuntimePrompt(queuedPrompt)) {
1268
+ return undefined;
1269
+ }
1270
+ const consumedPrompt = dlg.takeQueuedPrompt();
1271
+ if (!consumedPrompt || consumedPrompt.msgId !== queuedPrompt.msgId) {
1272
+ throw new Error(`queued new-course prompt invariant violation: expected queued prompt ${queuedPrompt.msgId} before same-drive continuation`);
1273
+ }
1274
+ await persistence_1.DialogPersistence.clearPendingRuntimePrompt(dlg.id, queuedPrompt.msgId, dlg.status);
1275
+ return queuedNewCourseRuntimePromptToKernelPrompt(queuedPrompt);
1276
+ }
1231
1277
  function resolveFuncToolFollowupMode(tool) {
1232
1278
  return tool?.followupMode ?? 'immediate';
1233
1279
  }
@@ -1237,6 +1283,7 @@ function summarizeRoutedFunctionResult(routed) {
1237
1283
  hasImmediateTellaskOutputs: routed.hasImmediateTellaskOutputs,
1238
1284
  immediateFollowupCallIds: routed.immediateFollowupCallIds,
1239
1285
  immediateTellaskOutputCallIds: routed.immediateTellaskOutputCallIds,
1286
+ invalidTellaskCallIds: routed.invalidTellaskCallIds,
1240
1287
  shouldStopAfterReplyTool: routed.shouldStopAfterReplyTool,
1241
1288
  shouldStopAfterPendingTellaskWait: routed.shouldStopAfterPendingTellaskWait,
1242
1289
  pairedMessageTypes: routed.pairedMessages.map((msg) => msg.type),
@@ -1347,6 +1394,54 @@ async function maybeWriteUnexpectedIdleAfterToolRoundDebugDump(args) {
1347
1394
  await promises_1.default.mkdir(debugDir, { recursive: true });
1348
1395
  await promises_1.default.writeFile(node_path_1.default.join(debugDir, fileName), `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
1349
1396
  }
1397
+ async function writeMissingImmediateFollowupTriggerDebugDump(args) {
1398
+ const capturedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
1399
+ const debugDir = node_path_1.default.resolve(process.cwd(), '.dialogs', 'debug');
1400
+ const trigger = args.expectation.trigger;
1401
+ const fileName = [
1402
+ 'kernel-driver-missing-immediate-followup-trigger',
1403
+ sanitizeDebugFileSegment(capturedAt),
1404
+ sanitizeDebugFileSegment(args.dlg.id.rootId),
1405
+ sanitizeDebugFileSegment(args.dlg.id.selfId),
1406
+ sanitizeDebugFileSegment(trigger.triggerId),
1407
+ `${(0, id_1.generateShortId)()}.json`,
1408
+ ].join('-');
1409
+ const activeCallees = await persistence_1.DialogPersistence.loadActiveCallees(args.dlg.id, args.dlg.status);
1410
+ const suspension = await args.dlg.getSuspensionStatus();
1411
+ const payload = {
1412
+ kind: args.repairOutcome === 'repaired'
1413
+ ? 'kernel_driver_missing_immediate_followup_trigger_repaired'
1414
+ : 'kernel_driver_missing_immediate_followup_trigger_repair_failed',
1415
+ capturedAt,
1416
+ rtwsRootAbs: process.cwd(),
1417
+ repairOutcome: args.repairOutcome,
1418
+ checkPoint: args.checkPoint,
1419
+ dialog: {
1420
+ rootId: args.dlg.id.rootId,
1421
+ selfId: args.dlg.id.selfId,
1422
+ value: args.dlg.id.valueOf(),
1423
+ agentId: args.dlg.agentId,
1424
+ status: args.dlg.status,
1425
+ currentCourse: args.dlg.currentCourse,
1426
+ activeGenCourse: args.dlg.activeGenCourseOrUndefined ?? null,
1427
+ activeGenSeq: args.dlg.activeGenSeqOrUndefined ?? null,
1428
+ hasQueuedPrompt: args.dlg.hasQueuedPrompt(),
1429
+ queuedPrompt: args.dlg.peekQueuedPrompt() ?? null,
1430
+ },
1431
+ expectation: args.expectation,
1432
+ latestBeforeRepair: args.latestBeforeRepair,
1433
+ latestAfterRepair: args.latestAfterRepair,
1434
+ activeCallees,
1435
+ suspension,
1436
+ callstack: new Error(args.repairOutcome === 'repaired'
1437
+ ? 'kernel-driver missing immediate followup trigger repaired'
1438
+ : 'kernel-driver missing immediate followup trigger repair failed').stack ?? null,
1439
+ };
1440
+ await promises_1.default.mkdir(debugDir, { recursive: true });
1441
+ const debugPath = node_path_1.default.join(debugDir, fileName);
1442
+ await promises_1.default.writeFile(debugPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
1443
+ return debugPath;
1444
+ }
1350
1445
  async function maybeWriteIdleWithActiveReplyObligationDebugDump(args) {
1351
1446
  if (args.finalDisplayState.kind !== 'idle_waiting_user') {
1352
1447
  return;
@@ -1392,7 +1487,7 @@ async function maybeWriteIdleWithActiveReplyObligationDebugDump(args) {
1392
1487
  await promises_1.default.mkdir(debugDir, { recursive: true });
1393
1488
  await promises_1.default.writeFile(node_path_1.default.join(debugDir, fileName), `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
1394
1489
  }
1395
- async function upsertImmediateFollowupTrigger(args) {
1490
+ function buildImmediateFollowupTriggerExpectation(args) {
1396
1491
  const reasons = [];
1397
1492
  if (args.routed.immediateFollowupCallIds.length > 0) {
1398
1493
  reasons.push({
@@ -1407,22 +1502,30 @@ async function upsertImmediateFollowupTrigger(args) {
1407
1502
  replyCallId: args.routed.immediateTellaskOutputCallIds[0],
1408
1503
  });
1409
1504
  }
1505
+ const invalidRecoveryCallIds = new Set(args.routed.invalidTellaskCallIds);
1410
1506
  if (args.invalidFuncCallCount > 0) {
1411
- const callIds = args.streamedFuncCalls.map((call) => call.id);
1412
- // Invalid provider tool payloads are a same-turn recovery fact, not a generic retry hint.
1413
- // Keep them inside the follow-up trigger so the next generation can repair the current turn
1414
- // immediately, while the invalid payload itself stays loud in runtime guides and logs.
1507
+ for (const call of args.streamedFuncCalls) {
1508
+ invalidRecoveryCallIds.add(call.id);
1509
+ }
1510
+ if (args.streamedFuncCalls.length === 0) {
1511
+ invalidRecoveryCallIds.add(`invalid-tool:${args.invalidFuncCallCount}`);
1512
+ }
1513
+ }
1514
+ if (invalidRecoveryCallIds.size > 0) {
1515
+ // Invalid provider tool payloads and invalid tellask specials are same-turn recovery facts,
1516
+ // not generic retry hints. Keep them inside the follow-up trigger so the next generation can
1517
+ // repair the current turn immediately, while the invalid payload itself stays loud.
1415
1518
  reasons.push({
1416
1519
  kind: 'invalid_tool_recovery',
1417
- callIds: callIds.length > 0 ? callIds : [`invalid-tool:${args.invalidFuncCallCount}`],
1520
+ callIds: [...invalidRecoveryCallIds],
1418
1521
  });
1419
1522
  }
1420
1523
  if (reasons.length === 0) {
1421
- return;
1524
+ return undefined;
1422
1525
  }
1423
1526
  const course = args.dlg.activeGenCourseOrUndefined ?? args.dlg.currentCourse;
1424
1527
  const genseq = args.dlg.activeGenSeq;
1425
- await persistence_1.DialogPersistence.upsertNextStepTrigger(args.dlg.id, {
1528
+ const trigger = {
1426
1529
  triggerId: `followup:c${String(course)}:g${String(genseq)}`,
1427
1530
  kind: 'followup',
1428
1531
  sourceGeneration: {
@@ -1431,7 +1534,74 @@ async function upsertImmediateFollowupTrigger(args) {
1431
1534
  },
1432
1535
  reasons,
1433
1536
  continuation: args.continuation,
1537
+ };
1538
+ return {
1539
+ trigger,
1540
+ callIds: args.streamedFuncCalls.map((call) => call.id),
1541
+ callNames: args.streamedFuncCalls.map((call) => call.name),
1542
+ routed: summarizeRoutedFunctionResult(args.routed),
1543
+ continuation: args.continuation,
1544
+ invalidFuncCallCount: args.invalidFuncCallCount,
1545
+ };
1546
+ }
1547
+ async function upsertImmediateFollowupTrigger(dlg, expectation) {
1548
+ await persistence_1.DialogPersistence.upsertNextStepTrigger(dlg.id, expectation.trigger, dlg.status);
1549
+ }
1550
+ function hasExpectedImmediateFollowupTrigger(latest, expectation) {
1551
+ return (latest?.nextStep.triggers.some((trigger) => trigger.kind === 'followup' && trigger.triggerId === expectation.trigger.triggerId) === true);
1552
+ }
1553
+ async function repairMissingImmediateFollowupTrigger(args) {
1554
+ const expectation = args.expectation;
1555
+ if (expectation === undefined) {
1556
+ return;
1557
+ }
1558
+ const latestBeforeRepair = await persistence_1.DialogPersistence.loadDialogLatest(args.dlg.id, args.dlg.status);
1559
+ if (hasExpectedImmediateFollowupTrigger(latestBeforeRepair, expectation)) {
1560
+ return;
1561
+ }
1562
+ await persistence_1.DialogPersistence.upsertNextStepTrigger(args.dlg.id, expectation.trigger, args.dlg.status);
1563
+ const latestAfterRepair = await persistence_1.DialogPersistence.loadDialogLatest(args.dlg.id, args.dlg.status);
1564
+ const repairSucceeded = hasExpectedImmediateFollowupTrigger(latestAfterRepair, expectation);
1565
+ const repairOutcome = repairSucceeded
1566
+ ? 'repaired'
1567
+ : 'repair_failed';
1568
+ const debugPath = await writeMissingImmediateFollowupTriggerDebugDump({
1569
+ dlg: args.dlg,
1570
+ expectation,
1571
+ latestBeforeRepair,
1572
+ latestAfterRepair,
1573
+ checkPoint: args.checkPoint,
1574
+ repairOutcome,
1575
+ });
1576
+ const message = `${repairSucceeded ? 'Repaired' : 'Failed to repair'} missing immediate follow-up trigger after function results ` +
1577
+ `(triggerId=${expectation.trigger.triggerId}, checkPoint=${args.checkPoint})`;
1578
+ log_1.log.error(message, new Error(`kernel_driver_missing_immediate_followup_trigger_${repairOutcome}`), {
1579
+ rootId: args.dlg.id.rootId,
1580
+ selfId: args.dlg.id.selfId,
1581
+ dialogId: args.dlg.id.valueOf(),
1582
+ status: args.dlg.status,
1583
+ triggerId: expectation.trigger.triggerId,
1584
+ checkPoint: args.checkPoint,
1585
+ repairSucceeded,
1586
+ sourceGeneration: expectation.trigger.sourceGeneration,
1587
+ reasonKinds: expectation.trigger.reasons.map((reason) => reason.kind),
1588
+ callIds: expectation.callIds,
1589
+ callNames: expectation.callNames,
1590
+ invalidFuncCallCount: expectation.invalidFuncCallCount,
1591
+ continuation: expectation.continuation,
1592
+ routed: expectation.routed,
1593
+ latestBeforeRepairNextStep: latestBeforeRepair?.nextStep ?? null,
1594
+ latestBeforeRepairGenerationRunState: latestBeforeRepair?.generationRunState ?? null,
1595
+ latestBeforeRepairDisplayState: latestBeforeRepair?.displayState ?? null,
1596
+ latestAfterRepairNextStep: latestAfterRepair?.nextStep ?? null,
1597
+ latestAfterRepairGenerationRunState: latestAfterRepair?.generationRunState ?? null,
1598
+ latestAfterRepairDisplayState: latestAfterRepair?.displayState ?? null,
1599
+ debugPath,
1434
1600
  });
1601
+ await args.dlg.streamError(`${message}; debug=${debugPath}`);
1602
+ if (!repairSucceeded) {
1603
+ throw new Error(`${message}; debug=${debugPath}`);
1604
+ }
1435
1605
  }
1436
1606
  function shouldImmediatelyFollowUpSuccessfulToolResult(tool) {
1437
1607
  return resolveFuncToolFollowupMode(tool) === 'immediate';
@@ -1708,6 +1878,7 @@ async function executeFunctionRound(args) {
1708
1878
  hasImmediateTellaskOutputs: false,
1709
1879
  immediateFollowupCallIds: [],
1710
1880
  immediateTellaskOutputCallIds: [],
1881
+ invalidTellaskCallIds: [],
1711
1882
  shouldStopAfterReplyTool: false,
1712
1883
  shouldStopAfterPendingTellaskWait: false,
1713
1884
  pairedMessages: [],
@@ -1818,6 +1989,7 @@ async function executeFunctionRound(args) {
1818
1989
  hasImmediateTellaskOutputs: tellaskRound.hasImmediateTellaskOutputs,
1819
1990
  immediateFollowupCallIds,
1820
1991
  immediateTellaskOutputCallIds: tellaskRound.immediateTellaskOutputCallIds,
1992
+ invalidTellaskCallIds: tellaskRound.invalidTellaskCallIds,
1821
1993
  shouldStopAfterReplyTool: tellaskRound.shouldStopAfterReplyTool,
1822
1994
  shouldStopAfterPendingTellaskWait: tellaskRound.shouldStopAfterPendingTellaskWait,
1823
1995
  pairedMessages,
@@ -2269,6 +2441,7 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
2269
2441
  }
2270
2442
  lastBusinessContinuation = currentBusinessContinuation;
2271
2443
  let generationBodyError;
2444
+ let immediateFollowupTriggerExpectation;
2272
2445
  const q4hAnswerCallId = normalizeQ4HAnswerCallId(currentPrompt?.q4hAnswerCallId);
2273
2446
  const isQ4HAnswerPrompt = q4hAnswerCallId !== undefined;
2274
2447
  try {
@@ -3139,13 +3312,44 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3139
3312
  });
3140
3313
  break;
3141
3314
  }
3315
+ const queuedNewCoursePrompt = await consumeQueuedNewCourseRuntimePromptForSameDrive(dlg);
3316
+ if (queuedNewCoursePrompt !== undefined) {
3317
+ pendingPrompt = queuedNewCoursePrompt;
3318
+ skipTaskdocForThisDrive = false;
3319
+ continue;
3320
+ }
3321
+ // Start an immediate post-tool generation only when this round produced tool outputs that
3322
+ // warrant same-drive LLM reaction right away. Provider-native side-channel UI events are
3323
+ // meaningful output, but they are not transcript/context inputs and therefore must not
3324
+ // trigger another immediate generation round by themselves.
3325
+ const shouldStartImmediatePostToolGeneration = routed.hasImmediateFollowupToolCalls ||
3326
+ routed.hasImmediateTellaskOutputs ||
3327
+ invalidFuncCallCount > 0;
3328
+ if (shouldStartImmediatePostToolGeneration) {
3329
+ const expectation = buildImmediateFollowupTriggerExpectation({
3330
+ dlg,
3331
+ routed,
3332
+ invalidFuncCallCount,
3333
+ streamedFuncCalls,
3334
+ continuation: currentBusinessContinuation,
3335
+ });
3336
+ if (expectation === undefined) {
3337
+ throw new Error(`Immediate follow-up trigger invariant violation: expected trigger reasons missing ` +
3338
+ `(dialog=${dlg.id.valueOf()}, course=${String(dlg.activeGenCourseOrUndefined ?? dlg.currentCourse)}, ` +
3339
+ `genseq=${String(dlg.activeGenSeq)}, immediateToolCalls=${String(routed.hasImmediateFollowupToolCalls)}, ` +
3340
+ `immediateTellaskOutputs=${String(routed.hasImmediateTellaskOutputs)}, ` +
3341
+ `invalidFuncCallCount=${String(invalidFuncCallCount)})`);
3342
+ }
3343
+ immediateFollowupTriggerExpectation = expectation;
3344
+ await upsertImmediateFollowupTrigger(dlg, immediateFollowupTriggerExpectation);
3345
+ }
3142
3346
  if (dlg.hasQueuedPrompt()) {
3143
3347
  lastToolRoundStopDiagnostics = buildToolRoundStopDiagnostics({
3144
3348
  dlg,
3145
3349
  streamedFuncCalls,
3146
3350
  routed,
3147
3351
  lastBusinessContinuation,
3148
- shouldStartImmediatePostToolGeneration: false,
3352
+ shouldStartImmediatePostToolGeneration,
3149
3353
  stopReason: 'queued_prompt_after_tool_round',
3150
3354
  queuedPromptAfterToolRound: true,
3151
3355
  remindersVer: dlg.remindersVer,
@@ -3166,7 +3370,7 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3166
3370
  streamedFuncCalls,
3167
3371
  routed,
3168
3372
  lastBusinessContinuation,
3169
- shouldStartImmediatePostToolGeneration: false,
3373
+ shouldStartImmediatePostToolGeneration,
3170
3374
  stopReason: 'suspended_after_tool_round',
3171
3375
  suspensionAfterToolRound,
3172
3376
  queuedPromptAfterToolRound: dlg.hasQueuedPrompt(),
@@ -3176,13 +3380,6 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3176
3380
  await preserveDiligenceBudgetAcrossQ4H(dlg);
3177
3381
  break;
3178
3382
  }
3179
- // Start an immediate post-tool generation only when this round produced tool outputs that
3180
- // warrant same-drive LLM reaction right away. Provider-native side-channel UI events are
3181
- // meaningful output, but they are not transcript/context inputs and therefore must not
3182
- // trigger another immediate generation round by themselves.
3183
- const shouldStartImmediatePostToolGeneration = routed.hasImmediateFollowupToolCalls ||
3184
- routed.hasImmediateTellaskOutputs ||
3185
- invalidFuncCallCount > 0;
3186
3383
  if (!shouldStartImmediatePostToolGeneration) {
3187
3384
  if (routed.shouldStopAfterPendingTellaskWait) {
3188
3385
  lastToolRoundStopDiagnostics = buildToolRoundStopDiagnostics({
@@ -3239,23 +3436,19 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3239
3436
  });
3240
3437
  break;
3241
3438
  }
3242
- if (shouldStartImmediatePostToolGeneration) {
3243
- await upsertImmediateFollowupTrigger({
3244
- dlg,
3245
- routed,
3246
- invalidFuncCallCount,
3247
- streamedFuncCalls,
3248
- continuation: currentBusinessContinuation,
3249
- });
3250
- resolvingImmediateToolResultForUserPrompt =
3251
- currentGenerationBelongsToUserPrompt ||
3252
- currentGenerationBelongsToUserToolChain ||
3253
- isUserOriginPrompt(currentPrompt);
3254
- resolvingImmediateToolResultUserPromptMsgId = resolvingImmediateToolResultForUserPrompt
3255
- ? currentUserPromptMsgId
3256
- : undefined;
3257
- continue;
3258
- }
3439
+ await repairMissingImmediateFollowupTrigger({
3440
+ dlg,
3441
+ expectation: immediateFollowupTriggerExpectation,
3442
+ checkPoint: 'before_immediate_post_tool_generation_continue',
3443
+ });
3444
+ resolvingImmediateToolResultForUserPrompt =
3445
+ currentGenerationBelongsToUserPrompt ||
3446
+ currentGenerationBelongsToUserToolChain ||
3447
+ isUserOriginPrompt(currentPrompt);
3448
+ resolvingImmediateToolResultUserPromptMsgId = resolvingImmediateToolResultForUserPrompt
3449
+ ? currentUserPromptMsgId
3450
+ : undefined;
3451
+ continue;
3259
3452
  }
3260
3453
  catch (err) {
3261
3454
  generationBodyError = err;
@@ -3263,6 +3456,13 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3263
3456
  }
3264
3457
  finally {
3265
3458
  try {
3459
+ if (generationBodyError === undefined) {
3460
+ await repairMissingImmediateFollowupTrigger({
3461
+ dlg,
3462
+ expectation: immediateFollowupTriggerExpectation,
3463
+ checkPoint: 'before_notify_generating_finish',
3464
+ });
3465
+ }
3266
3466
  await dlg.notifyGeneratingFinish(contextHealthForGen, llmGenModelForGen);
3267
3467
  }
3268
3468
  catch (finishErr) {
@@ -48,6 +48,7 @@ function buildFbrConclusionTools(language) {
48
48
  {
49
49
  type: 'func',
50
50
  name: exports.FBR_LOW_NOISE_CONCLUSION_TOOL_NAME,
51
+ followupMode: 'deferred',
51
52
  description: buildFbrConclusionToolDescription({ language, kind: 'low_noise' }),
52
53
  parameters: {
53
54
  type: 'object',
@@ -67,6 +68,7 @@ function buildFbrConclusionTools(language) {
67
68
  {
68
69
  type: 'func',
69
70
  name: exports.FBR_UNREASONABLE_SITUATION_TOOL_NAME,
71
+ followupMode: 'deferred',
70
72
  description: buildFbrConclusionToolDescription({ language, kind: 'unreasonable' }),
71
73
  parameters: {
72
74
  type: 'object',
@@ -121,6 +121,7 @@ export type TellaskFunctionRoundResult = Readonly<{
121
121
  hasInvalidTellaskCalls: boolean;
122
122
  hasImmediateTellaskOutputs: boolean;
123
123
  immediateTellaskOutputCallIds: readonly string[];
124
+ invalidTellaskCallIds: readonly string[];
124
125
  shouldStopAfterReplyTool: boolean;
125
126
  shouldStopAfterPendingTellaskWait: boolean;
126
127
  }>;
@@ -2396,6 +2396,10 @@ async function processTellaskFunctionRound(args) {
2396
2396
  const result = output;
2397
2397
  tellaskFuncResultByCallId.set(result.id, result);
2398
2398
  tellaskFuncResults.push(result);
2399
+ const originatingCall = specialCallById.get(result.id);
2400
+ if (originatingCall !== undefined && isReplyTellaskCallName(originatingCall.callName)) {
2401
+ continue;
2402
+ }
2399
2403
  hasImmediateTellaskOutputs = true;
2400
2404
  immediateTellaskOutputCallIds.push(result.id);
2401
2405
  continue;
@@ -2460,7 +2464,8 @@ async function processTellaskFunctionRound(args) {
2460
2464
  hasInvalidTellaskCalls: orderedInvalidCalls.length > 0,
2461
2465
  hasImmediateTellaskOutputs,
2462
2466
  immediateTellaskOutputCallIds,
2463
- shouldStopAfterReplyTool: orderedInvalidCalls.length === 0 && tellaskExecution.successfulReplyCallIds.length > 0,
2467
+ invalidTellaskCallIds: orderedInvalidCalls.map((issue) => issue.originalCall.id),
2468
+ shouldStopAfterReplyTool: tellaskExecution.successfulReplyCallIds.length > 0,
2464
2469
  shouldStopAfterPendingTellaskWait,
2465
2470
  };
2466
2471
  }
@@ -5,6 +5,7 @@ export declare function configureDomindsSelfUpdate(params: {
5
5
  host: string;
6
6
  port: number;
7
7
  mode: ServerMode;
8
+ closeWebSocketClients: () => void;
8
9
  stopServer: () => Promise<void>;
9
10
  }): void;
10
11
  export declare function setDomindsSelfUpdateBroadcaster(next: ((status: DomindsSelfUpdateStatus) => void) | null): void;
@@ -20,6 +20,8 @@ const BACKGROUND_CHECK_INTERVAL_MS = 30 * 60 * 1000;
20
20
  const LATEST_VERSION_CHECK_TIMEOUT_MS = 60000;
21
21
  const RESTART_PORT_RELEASE_TIMEOUT_MS = 15000;
22
22
  const RESTART_PORT_PROBE_INTERVAL_MS = 150;
23
+ const RESTART_EXIT_GRACE_MS = 1000;
24
+ const RESTART_FORCE_KILL_AFTER_MS = 3000;
23
25
  const COMMAND_OUTPUT_LOG_LIMIT = 2000;
24
26
  const PROXY_URL_ENV_KEYS = new Set([
25
27
  'HTTP_PROXY',
@@ -97,6 +99,9 @@ function truncateCommandOutput(value) {
97
99
  return raw;
98
100
  return `${raw.slice(0, COMMAND_OUTPUT_LOG_LIMIT)}...[truncated ${raw.length - COMMAND_OUTPUT_LOG_LIMIT} chars]`;
99
101
  }
102
+ function delayMs(ms) {
103
+ return new Promise((resolve) => setTimeout(resolve, ms));
104
+ }
100
105
  function formatPathEnvExcerpt(pathEnv) {
101
106
  if (pathEnv === null || pathEnv.trim() === '')
102
107
  return null;
@@ -620,6 +625,7 @@ function configureDomindsSelfUpdate(params) {
620
625
  host: params.host,
621
626
  port: params.port,
622
627
  mode: params.mode,
628
+ closeWebSocketClients: params.closeWebSocketClients,
623
629
  stopServer: params.stopServer,
624
630
  };
625
631
  latestObservation = { kind: 'unknown' };
@@ -860,6 +866,8 @@ function spawnDetachedRestartHelper(params) {
860
866
  cwd: params.cwd,
861
867
  host: getRestartPortProbeHost(params.host),
862
868
  port: params.port,
869
+ retiringPid: process.pid,
870
+ forceKillAfterMs: RESTART_FORCE_KILL_AFTER_MS,
863
871
  probeIntervalMs: RESTART_PORT_PROBE_INTERVAL_MS,
864
872
  portReleaseTimeoutMs: RESTART_PORT_RELEASE_TIMEOUT_MS,
865
873
  stdioMode,
@@ -884,8 +892,8 @@ function spawnDetachedRestartHelper(params) {
884
892
  ' socket.setTimeout(1000, () => finish(true));',
885
893
  ' });',
886
894
  '}',
887
- 'async function waitForPortRelease() {',
888
- ' const deadline = Date.now() + payload.portReleaseTimeoutMs;',
895
+ 'async function waitForPortRelease(timeoutMs) {',
896
+ ' const deadline = Date.now() + timeoutMs;',
889
897
  ' let consecutiveIdle = 0;',
890
898
  ' while (Date.now() < deadline) {',
891
899
  ' if (await isPortBusy()) {',
@@ -894,13 +902,42 @@ function spawnDetachedRestartHelper(params) {
894
902
  ' continue;',
895
903
  ' }',
896
904
  ' consecutiveIdle += 1;',
897
- ' if (consecutiveIdle >= 2) return;',
905
+ ' if (consecutiveIdle >= 2) return true;',
898
906
  ' await new Promise((resolve) => setTimeout(resolve, payload.probeIntervalMs));',
899
907
  ' }',
908
+ ' return false;',
909
+ '}',
910
+ 'function forceKillRetiringProcess() {',
911
+ ' if (!Number.isInteger(payload.retiringPid) || payload.retiringPid <= 0) {',
912
+ ' throw new Error(`Invalid retiring Dominds pid for restart: ${String(payload.retiringPid)}`);',
913
+ ' }',
914
+ ' if (payload.retiringPid === process.pid) {',
915
+ ' throw new Error(`Refusing to kill restart helper pid ${String(process.pid)}`);',
916
+ ' }',
917
+ " const killer = process.platform === 'win32'",
918
+ " ? spawn('taskkill.exe', ['/PID', String(payload.retiringPid), '/F'], { stdio: payload.stdioMode })",
919
+ " : spawn('kill', ['-KILL', String(payload.retiringPid)], { stdio: payload.stdioMode });",
920
+ ' return new Promise((resolve, reject) => {',
921
+ " killer.once('error', reject);",
922
+ " killer.once('exit', (code) => {",
923
+ ' if (code === 0) {',
924
+ ' resolve();',
925
+ ' return;',
926
+ ' }',
927
+ ' resolve();',
928
+ ' });',
929
+ ' });',
900
930
  '}',
901
931
  '(async () => {',
902
932
  ' try {',
903
- ' await waitForPortRelease();',
933
+ ' const releasedGracefully = await waitForPortRelease(payload.forceKillAfterMs);',
934
+ ' if (!releasedGracefully) {',
935
+ ' await forceKillRetiringProcess();',
936
+ ' const releasedAfterKill = await waitForPortRelease(payload.portReleaseTimeoutMs);',
937
+ ' if (!releasedAfterKill) {',
938
+ ' throw new Error(`Dominds restart port ${String(payload.host)}:${String(payload.port)} is still busy after force-killing pid ${String(payload.retiringPid)}`);',
939
+ ' }',
940
+ ' }',
904
941
  " const child = spawn(payload.command, payload.args, { cwd: payload.cwd, env: process.env, detached, stdio: payload.stdioMode, shell: process.platform === 'win32' });",
905
942
  ' if (detached) child.unref();',
906
943
  ' process.exit(0);',
@@ -922,7 +959,28 @@ function spawnDetachedRestartHelper(params) {
922
959
  }
923
960
  async function stopAndExitForRestart() {
924
961
  const cfg = assertRuntimeConfig();
925
- await cfg.stopServer();
962
+ let stopSettled = false;
963
+ void cfg
964
+ .stopServer()
965
+ .then(() => {
966
+ stopSettled = true;
967
+ })
968
+ .catch((error) => {
969
+ stopSettled = true;
970
+ log.error('Failed to stop Dominds HTTP server during restart grace window', error, {
971
+ host: cfg.host,
972
+ port: cfg.port,
973
+ });
974
+ });
975
+ cfg.closeWebSocketClients();
976
+ await delayMs(RESTART_EXIT_GRACE_MS);
977
+ if (!stopSettled) {
978
+ log.warn('Exiting Dominds process before graceful HTTP server stop completed during restart', undefined, {
979
+ host: cfg.host,
980
+ port: cfg.port,
981
+ graceMs: RESTART_EXIT_GRACE_MS,
982
+ });
983
+ }
926
984
  process.exit(0);
927
985
  }
928
986
  async function restartDomindsIntoLatest() {
@@ -1035,8 +1035,23 @@ async function handleDisplayDialog(ws, packet) {
1035
1035
  }
1036
1036
  const rootPrimingConfig = dialogIdObj.selfId === dialogIdObj.rootId ? (0, priming_1.getMainDialogPrimingConfig)(metadata) : undefined;
1037
1037
  const showPrimingEventsInUi = rootPrimingConfig?.showInUi !== false;
1038
- const decidedCourse = (await persistence_1.DialogPersistence.getCurrentCourseNumber(dialogIdObj, requestedStatus)) ||
1038
+ if (packet.course !== undefined && (!Number.isInteger(packet.course) || packet.course <= 0)) {
1039
+ ws.send(JSON.stringify({
1040
+ type: 'error',
1041
+ message: 'display_dialog course must be a positive integer when provided',
1042
+ }));
1043
+ return;
1044
+ }
1045
+ const latestCourse = (await persistence_1.DialogPersistence.getCurrentCourseNumber(dialogIdObj, requestedStatus)) ||
1039
1046
  (dialogState.currentCourse ?? 1);
1047
+ const decidedCourse = packet.course ?? latestCourse;
1048
+ if (decidedCourse > latestCourse) {
1049
+ ws.send(JSON.stringify({
1050
+ type: 'error',
1051
+ message: `display_dialog course ${String(decidedCourse)} exceeds latest course ${String(latestCourse)}`,
1052
+ }));
1053
+ return;
1054
+ }
1040
1055
  const enableLive = requestedStatus === 'running';
1041
1056
  const mainDialog = await (0, dialog_instance_registry_1.getOrRestoreMainDialog)(dialogIdObj.rootId, requestedStatus);
1042
1057
  if (!mainDialog) {
@@ -1068,7 +1083,7 @@ async function handleDisplayDialog(ws, packet) {
1068
1083
  try {
1069
1084
  const dialogStore = dialog.dlgStore;
1070
1085
  if (dialogStore instanceof persistence_1.DiskFileDialogStore) {
1071
- await dialogStore.sendDialogEventsDirectly(ws, dialog, decidedCourse, decidedCourse, requestedStatus, { showPrimingEventsInUi });
1086
+ await dialogStore.sendDialogEventsDirectly(ws, dialog, decidedCourse, latestCourse, requestedStatus, { showPrimingEventsInUi });
1072
1087
  }
1073
1088
  else {
1074
1089
  throw new Error('Unexpected dialog store type for sendDialogEventsDirectly');
@@ -1340,6 +1355,13 @@ async function handleDisplayCourse(ws, packet) {
1340
1355
  if (!dialog || typeof course !== 'number') {
1341
1356
  throw new Error('dialog and course are required');
1342
1357
  }
1358
+ if (!Number.isInteger(course) || course <= 0) {
1359
+ ws.send(JSON.stringify({
1360
+ type: 'error',
1361
+ message: 'display_course course must be a positive integer',
1362
+ }));
1363
+ return;
1364
+ }
1343
1365
  // Extract dialog ID from DialogIdent
1344
1366
  let dialogIdStr = dialog.selfId;
1345
1367
  let mainDialogIdStr = dialog.rootId;
@@ -1371,6 +1393,13 @@ async function handleDisplayCourse(ws, packet) {
1371
1393
  return;
1372
1394
  }
1373
1395
  const totalCourses = (await persistence_1.DialogPersistence.getCurrentCourseNumber(dialogId, requestedStatus)) || course;
1396
+ if (course > totalCourses) {
1397
+ ws.send(JSON.stringify({
1398
+ type: 'error',
1399
+ message: `display_course course ${String(course)} exceeds latest course ${String(totalCourses)}`,
1400
+ }));
1401
+ return;
1402
+ }
1374
1403
  const mainDialog = await (0, dialog_instance_registry_1.getOrRestoreMainDialog)(dialogId.rootId, requestedStatus);
1375
1404
  if (!mainDialog)
1376
1405
  return;
package/dist/server.js CHANGED
@@ -233,6 +233,19 @@ async function startServer(opts = {}) {
233
233
  host,
234
234
  port: boundPort,
235
235
  mode: serverMode,
236
+ closeWebSocketClients: () => {
237
+ if (clients.size === 0)
238
+ return;
239
+ log.info(`Closing ${clients.size} WebSocket client(s) for Dominds restart`);
240
+ for (const ws of clients) {
241
+ try {
242
+ ws.close(1012, 'server_restart');
243
+ }
244
+ catch (error) {
245
+ log.warn('Failed to close WebSocket client for Dominds restart', error);
246
+ }
247
+ }
248
+ },
236
249
  stopServer: async () => {
237
250
  await httpServer.stop();
238
251
  },
@@ -612,6 +612,7 @@ exports.updateReminderTool = {
612
612
  exports.clearMindTool = {
613
613
  type: 'func',
614
614
  name: 'clear_mind',
615
+ followupMode: 'deferred',
615
616
  description: 'Start a new dialog course, optionally carrying one extra continuation reminder.',
616
617
  descriptionI18n: {
617
618
  en: 'Start a new dialog course, optionally carrying one extra continuation reminder.',