dominds 1.25.9 → 1.25.10

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.
@@ -1347,6 +1347,54 @@ async function maybeWriteUnexpectedIdleAfterToolRoundDebugDump(args) {
1347
1347
  await promises_1.default.mkdir(debugDir, { recursive: true });
1348
1348
  await promises_1.default.writeFile(node_path_1.default.join(debugDir, fileName), `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
1349
1349
  }
1350
+ async function writeMissingImmediateFollowupTriggerDebugDump(args) {
1351
+ const capturedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
1352
+ const debugDir = node_path_1.default.resolve(process.cwd(), '.dialogs', 'debug');
1353
+ const trigger = args.expectation.trigger;
1354
+ const fileName = [
1355
+ 'kernel-driver-missing-immediate-followup-trigger',
1356
+ sanitizeDebugFileSegment(capturedAt),
1357
+ sanitizeDebugFileSegment(args.dlg.id.rootId),
1358
+ sanitizeDebugFileSegment(args.dlg.id.selfId),
1359
+ sanitizeDebugFileSegment(trigger.triggerId),
1360
+ `${(0, id_1.generateShortId)()}.json`,
1361
+ ].join('-');
1362
+ const activeCallees = await persistence_1.DialogPersistence.loadActiveCallees(args.dlg.id, args.dlg.status);
1363
+ const suspension = await args.dlg.getSuspensionStatus();
1364
+ const payload = {
1365
+ kind: args.repairOutcome === 'repaired'
1366
+ ? 'kernel_driver_missing_immediate_followup_trigger_repaired'
1367
+ : 'kernel_driver_missing_immediate_followup_trigger_repair_failed',
1368
+ capturedAt,
1369
+ rtwsRootAbs: process.cwd(),
1370
+ repairOutcome: args.repairOutcome,
1371
+ checkPoint: args.checkPoint,
1372
+ dialog: {
1373
+ rootId: args.dlg.id.rootId,
1374
+ selfId: args.dlg.id.selfId,
1375
+ value: args.dlg.id.valueOf(),
1376
+ agentId: args.dlg.agentId,
1377
+ status: args.dlg.status,
1378
+ currentCourse: args.dlg.currentCourse,
1379
+ activeGenCourse: args.dlg.activeGenCourseOrUndefined ?? null,
1380
+ activeGenSeq: args.dlg.activeGenSeqOrUndefined ?? null,
1381
+ hasQueuedPrompt: args.dlg.hasQueuedPrompt(),
1382
+ queuedPrompt: args.dlg.peekQueuedPrompt() ?? null,
1383
+ },
1384
+ expectation: args.expectation,
1385
+ latestBeforeRepair: args.latestBeforeRepair,
1386
+ latestAfterRepair: args.latestAfterRepair,
1387
+ activeCallees,
1388
+ suspension,
1389
+ callstack: new Error(args.repairOutcome === 'repaired'
1390
+ ? 'kernel-driver missing immediate followup trigger repaired'
1391
+ : 'kernel-driver missing immediate followup trigger repair failed').stack ?? null,
1392
+ };
1393
+ await promises_1.default.mkdir(debugDir, { recursive: true });
1394
+ const debugPath = node_path_1.default.join(debugDir, fileName);
1395
+ await promises_1.default.writeFile(debugPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
1396
+ return debugPath;
1397
+ }
1350
1398
  async function maybeWriteIdleWithActiveReplyObligationDebugDump(args) {
1351
1399
  if (args.finalDisplayState.kind !== 'idle_waiting_user') {
1352
1400
  return;
@@ -1392,7 +1440,7 @@ async function maybeWriteIdleWithActiveReplyObligationDebugDump(args) {
1392
1440
  await promises_1.default.mkdir(debugDir, { recursive: true });
1393
1441
  await promises_1.default.writeFile(node_path_1.default.join(debugDir, fileName), `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
1394
1442
  }
1395
- async function upsertImmediateFollowupTrigger(args) {
1443
+ function buildImmediateFollowupTriggerExpectation(args) {
1396
1444
  const reasons = [];
1397
1445
  if (args.routed.immediateFollowupCallIds.length > 0) {
1398
1446
  reasons.push({
@@ -1418,11 +1466,11 @@ async function upsertImmediateFollowupTrigger(args) {
1418
1466
  });
1419
1467
  }
1420
1468
  if (reasons.length === 0) {
1421
- return;
1469
+ return undefined;
1422
1470
  }
1423
1471
  const course = args.dlg.activeGenCourseOrUndefined ?? args.dlg.currentCourse;
1424
1472
  const genseq = args.dlg.activeGenSeq;
1425
- await persistence_1.DialogPersistence.upsertNextStepTrigger(args.dlg.id, {
1473
+ const trigger = {
1426
1474
  triggerId: `followup:c${String(course)}:g${String(genseq)}`,
1427
1475
  kind: 'followup',
1428
1476
  sourceGeneration: {
@@ -1431,7 +1479,74 @@ async function upsertImmediateFollowupTrigger(args) {
1431
1479
  },
1432
1480
  reasons,
1433
1481
  continuation: args.continuation,
1482
+ };
1483
+ return {
1484
+ trigger,
1485
+ callIds: args.streamedFuncCalls.map((call) => call.id),
1486
+ callNames: args.streamedFuncCalls.map((call) => call.name),
1487
+ routed: summarizeRoutedFunctionResult(args.routed),
1488
+ continuation: args.continuation,
1489
+ invalidFuncCallCount: args.invalidFuncCallCount,
1490
+ };
1491
+ }
1492
+ async function upsertImmediateFollowupTrigger(dlg, expectation) {
1493
+ await persistence_1.DialogPersistence.upsertNextStepTrigger(dlg.id, expectation.trigger, dlg.status);
1494
+ }
1495
+ function hasExpectedImmediateFollowupTrigger(latest, expectation) {
1496
+ return (latest?.nextStep.triggers.some((trigger) => trigger.kind === 'followup' && trigger.triggerId === expectation.trigger.triggerId) === true);
1497
+ }
1498
+ async function repairMissingImmediateFollowupTrigger(args) {
1499
+ const expectation = args.expectation;
1500
+ if (expectation === undefined) {
1501
+ return;
1502
+ }
1503
+ const latestBeforeRepair = await persistence_1.DialogPersistence.loadDialogLatest(args.dlg.id, args.dlg.status);
1504
+ if (hasExpectedImmediateFollowupTrigger(latestBeforeRepair, expectation)) {
1505
+ return;
1506
+ }
1507
+ await persistence_1.DialogPersistence.upsertNextStepTrigger(args.dlg.id, expectation.trigger, args.dlg.status);
1508
+ const latestAfterRepair = await persistence_1.DialogPersistence.loadDialogLatest(args.dlg.id, args.dlg.status);
1509
+ const repairSucceeded = hasExpectedImmediateFollowupTrigger(latestAfterRepair, expectation);
1510
+ const repairOutcome = repairSucceeded
1511
+ ? 'repaired'
1512
+ : 'repair_failed';
1513
+ const debugPath = await writeMissingImmediateFollowupTriggerDebugDump({
1514
+ dlg: args.dlg,
1515
+ expectation,
1516
+ latestBeforeRepair,
1517
+ latestAfterRepair,
1518
+ checkPoint: args.checkPoint,
1519
+ repairOutcome,
1434
1520
  });
1521
+ const message = `${repairSucceeded ? 'Repaired' : 'Failed to repair'} missing immediate follow-up trigger after function results ` +
1522
+ `(triggerId=${expectation.trigger.triggerId}, checkPoint=${args.checkPoint})`;
1523
+ log_1.log.error(message, new Error(`kernel_driver_missing_immediate_followup_trigger_${repairOutcome}`), {
1524
+ rootId: args.dlg.id.rootId,
1525
+ selfId: args.dlg.id.selfId,
1526
+ dialogId: args.dlg.id.valueOf(),
1527
+ status: args.dlg.status,
1528
+ triggerId: expectation.trigger.triggerId,
1529
+ checkPoint: args.checkPoint,
1530
+ repairSucceeded,
1531
+ sourceGeneration: expectation.trigger.sourceGeneration,
1532
+ reasonKinds: expectation.trigger.reasons.map((reason) => reason.kind),
1533
+ callIds: expectation.callIds,
1534
+ callNames: expectation.callNames,
1535
+ invalidFuncCallCount: expectation.invalidFuncCallCount,
1536
+ continuation: expectation.continuation,
1537
+ routed: expectation.routed,
1538
+ latestBeforeRepairNextStep: latestBeforeRepair?.nextStep ?? null,
1539
+ latestBeforeRepairGenerationRunState: latestBeforeRepair?.generationRunState ?? null,
1540
+ latestBeforeRepairDisplayState: latestBeforeRepair?.displayState ?? null,
1541
+ latestAfterRepairNextStep: latestAfterRepair?.nextStep ?? null,
1542
+ latestAfterRepairGenerationRunState: latestAfterRepair?.generationRunState ?? null,
1543
+ latestAfterRepairDisplayState: latestAfterRepair?.displayState ?? null,
1544
+ debugPath,
1545
+ });
1546
+ await args.dlg.streamError(`${message}; debug=${debugPath}`);
1547
+ if (!repairSucceeded) {
1548
+ throw new Error(`${message}; debug=${debugPath}`);
1549
+ }
1435
1550
  }
1436
1551
  function shouldImmediatelyFollowUpSuccessfulToolResult(tool) {
1437
1552
  return resolveFuncToolFollowupMode(tool) === 'immediate';
@@ -2269,6 +2384,7 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
2269
2384
  }
2270
2385
  lastBusinessContinuation = currentBusinessContinuation;
2271
2386
  let generationBodyError;
2387
+ let immediateFollowupTriggerExpectation;
2272
2388
  const q4hAnswerCallId = normalizeQ4HAnswerCallId(currentPrompt?.q4hAnswerCallId);
2273
2389
  const isQ4HAnswerPrompt = q4hAnswerCallId !== undefined;
2274
2390
  try {
@@ -3139,13 +3255,38 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3139
3255
  });
3140
3256
  break;
3141
3257
  }
3258
+ // Start an immediate post-tool generation only when this round produced tool outputs that
3259
+ // warrant same-drive LLM reaction right away. Provider-native side-channel UI events are
3260
+ // meaningful output, but they are not transcript/context inputs and therefore must not
3261
+ // trigger another immediate generation round by themselves.
3262
+ const shouldStartImmediatePostToolGeneration = routed.hasImmediateFollowupToolCalls ||
3263
+ routed.hasImmediateTellaskOutputs ||
3264
+ invalidFuncCallCount > 0;
3265
+ if (shouldStartImmediatePostToolGeneration) {
3266
+ const expectation = buildImmediateFollowupTriggerExpectation({
3267
+ dlg,
3268
+ routed,
3269
+ invalidFuncCallCount,
3270
+ streamedFuncCalls,
3271
+ continuation: currentBusinessContinuation,
3272
+ });
3273
+ if (expectation === undefined) {
3274
+ throw new Error(`Immediate follow-up trigger invariant violation: expected trigger reasons missing ` +
3275
+ `(dialog=${dlg.id.valueOf()}, course=${String(dlg.activeGenCourseOrUndefined ?? dlg.currentCourse)}, ` +
3276
+ `genseq=${String(dlg.activeGenSeq)}, immediateToolCalls=${String(routed.hasImmediateFollowupToolCalls)}, ` +
3277
+ `immediateTellaskOutputs=${String(routed.hasImmediateTellaskOutputs)}, ` +
3278
+ `invalidFuncCallCount=${String(invalidFuncCallCount)})`);
3279
+ }
3280
+ immediateFollowupTriggerExpectation = expectation;
3281
+ await upsertImmediateFollowupTrigger(dlg, immediateFollowupTriggerExpectation);
3282
+ }
3142
3283
  if (dlg.hasQueuedPrompt()) {
3143
3284
  lastToolRoundStopDiagnostics = buildToolRoundStopDiagnostics({
3144
3285
  dlg,
3145
3286
  streamedFuncCalls,
3146
3287
  routed,
3147
3288
  lastBusinessContinuation,
3148
- shouldStartImmediatePostToolGeneration: false,
3289
+ shouldStartImmediatePostToolGeneration,
3149
3290
  stopReason: 'queued_prompt_after_tool_round',
3150
3291
  queuedPromptAfterToolRound: true,
3151
3292
  remindersVer: dlg.remindersVer,
@@ -3166,7 +3307,7 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3166
3307
  streamedFuncCalls,
3167
3308
  routed,
3168
3309
  lastBusinessContinuation,
3169
- shouldStartImmediatePostToolGeneration: false,
3310
+ shouldStartImmediatePostToolGeneration,
3170
3311
  stopReason: 'suspended_after_tool_round',
3171
3312
  suspensionAfterToolRound,
3172
3313
  queuedPromptAfterToolRound: dlg.hasQueuedPrompt(),
@@ -3176,13 +3317,6 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3176
3317
  await preserveDiligenceBudgetAcrossQ4H(dlg);
3177
3318
  break;
3178
3319
  }
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
3320
  if (!shouldStartImmediatePostToolGeneration) {
3187
3321
  if (routed.shouldStopAfterPendingTellaskWait) {
3188
3322
  lastToolRoundStopDiagnostics = buildToolRoundStopDiagnostics({
@@ -3239,23 +3373,19 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3239
3373
  });
3240
3374
  break;
3241
3375
  }
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
- }
3376
+ await repairMissingImmediateFollowupTrigger({
3377
+ dlg,
3378
+ expectation: immediateFollowupTriggerExpectation,
3379
+ checkPoint: 'before_immediate_post_tool_generation_continue',
3380
+ });
3381
+ resolvingImmediateToolResultForUserPrompt =
3382
+ currentGenerationBelongsToUserPrompt ||
3383
+ currentGenerationBelongsToUserToolChain ||
3384
+ isUserOriginPrompt(currentPrompt);
3385
+ resolvingImmediateToolResultUserPromptMsgId = resolvingImmediateToolResultForUserPrompt
3386
+ ? currentUserPromptMsgId
3387
+ : undefined;
3388
+ continue;
3259
3389
  }
3260
3390
  catch (err) {
3261
3391
  generationBodyError = err;
@@ -3263,6 +3393,13 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3263
3393
  }
3264
3394
  finally {
3265
3395
  try {
3396
+ if (generationBodyError === undefined) {
3397
+ await repairMissingImmediateFollowupTrigger({
3398
+ dlg,
3399
+ expectation: immediateFollowupTriggerExpectation,
3400
+ checkPoint: 'before_notify_generating_finish',
3401
+ });
3402
+ }
3266
3403
  await dlg.notifyGeneratingFinish(contextHealthForGen, llmGenModelForGen);
3267
3404
  }
3268
3405
  catch (finishErr) {
@@ -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,7 @@ 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;
23
24
  const COMMAND_OUTPUT_LOG_LIMIT = 2000;
24
25
  const PROXY_URL_ENV_KEYS = new Set([
25
26
  'HTTP_PROXY',
@@ -97,6 +98,9 @@ function truncateCommandOutput(value) {
97
98
  return raw;
98
99
  return `${raw.slice(0, COMMAND_OUTPUT_LOG_LIMIT)}...[truncated ${raw.length - COMMAND_OUTPUT_LOG_LIMIT} chars]`;
99
100
  }
101
+ function delayMs(ms) {
102
+ return new Promise((resolve) => setTimeout(resolve, ms));
103
+ }
100
104
  function formatPathEnvExcerpt(pathEnv) {
101
105
  if (pathEnv === null || pathEnv.trim() === '')
102
106
  return null;
@@ -620,6 +624,7 @@ function configureDomindsSelfUpdate(params) {
620
624
  host: params.host,
621
625
  port: params.port,
622
626
  mode: params.mode,
627
+ closeWebSocketClients: params.closeWebSocketClients,
623
628
  stopServer: params.stopServer,
624
629
  };
625
630
  latestObservation = { kind: 'unknown' };
@@ -922,7 +927,28 @@ function spawnDetachedRestartHelper(params) {
922
927
  }
923
928
  async function stopAndExitForRestart() {
924
929
  const cfg = assertRuntimeConfig();
925
- await cfg.stopServer();
930
+ let stopSettled = false;
931
+ void cfg
932
+ .stopServer()
933
+ .then(() => {
934
+ stopSettled = true;
935
+ })
936
+ .catch((error) => {
937
+ stopSettled = true;
938
+ log.error('Failed to stop Dominds HTTP server during restart grace window', error, {
939
+ host: cfg.host,
940
+ port: cfg.port,
941
+ });
942
+ });
943
+ cfg.closeWebSocketClients();
944
+ await delayMs(RESTART_EXIT_GRACE_MS);
945
+ if (!stopSettled) {
946
+ log.warn('Exiting Dominds process before graceful HTTP server stop completed during restart', undefined, {
947
+ host: cfg.host,
948
+ port: cfg.port,
949
+ graceMs: RESTART_EXIT_GRACE_MS,
950
+ });
951
+ }
926
952
  process.exit(0);
927
953
  }
928
954
  async function restartDomindsIntoLatest() {
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
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dominds",
3
- "version": "1.25.9",
3
+ "version": "1.25.10",
4
4
  "description": "Dominds CLI and aggregation shell for the LongRun AI kernel/runtime packages.",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {