dominds 1.23.1 → 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.
package/dist/cli/webui.js CHANGED
@@ -134,7 +134,14 @@ async function main() {
134
134
  const { language: resolvedLanguage, source } = (0, work_language_1.resolveWorkLanguage)({ env: process.env });
135
135
  (0, work_language_1.setWorkLanguage)(resolvedLanguage);
136
136
  log.info(`working language: ${(0, work_language_1.getWorkLanguage)()} (source: ${source})`);
137
- const started = await (0, server_1.startServer)({ port, host, mode, strictPort, portAutoDirection });
137
+ const started = await (0, server_1.startServer)({
138
+ port,
139
+ host,
140
+ mode,
141
+ strictPort,
142
+ portAutoDirection,
143
+ returnAfterListen: true,
144
+ });
138
145
  const httpServer = started.httpServer;
139
146
  const auth = started.auth;
140
147
  const baseUrl = `http://${started.host}:${started.port}`;
@@ -52,6 +52,7 @@ export declare function broadcastDisplayStateMarker(dialogId: DialogID, marker:
52
52
  }): void;
53
53
  export declare function computeIdleDisplayState(dlg: Dialog): Promise<DialogDisplayState>;
54
54
  export declare function refreshRunControlProjectionFromPersistenceFacts(dialogId: DialogID, trigger: 'resume_dialog' | 'resume_all' | 'run_control_snapshot' | 'pending_sideDialogs_changed' | 'q4h_changed'): Promise<DialogLatestFile | null>;
55
+ export declare function isRecoverableGeneratingLatest(latest: DialogLatestFile | null): boolean;
55
56
  export declare function reconcileDisplayStatesAfterRestart(): Promise<void>;
56
57
  export declare function requestInterruptDialog(dialogId: DialogID, reason: StopRequestedReason): Promise<{
57
58
  applied: boolean;
@@ -40,6 +40,7 @@ exports.setDialogDisplayState = setDialogDisplayState;
40
40
  exports.broadcastDisplayStateMarker = broadcastDisplayStateMarker;
41
41
  exports.computeIdleDisplayState = computeIdleDisplayState;
42
42
  exports.refreshRunControlProjectionFromPersistenceFacts = refreshRunControlProjectionFromPersistenceFacts;
43
+ exports.isRecoverableGeneratingLatest = isRecoverableGeneratingLatest;
43
44
  exports.reconcileDisplayStatesAfterRestart = reconcileDisplayStatesAfterRestart;
44
45
  exports.requestInterruptDialog = requestInterruptDialog;
45
46
  exports.requestEmergencyStopAll = requestEmergencyStopAll;
@@ -580,6 +581,19 @@ async function computeIdleDisplayStateForReconciliation(dialogId) {
580
581
  return null;
581
582
  }
582
583
  }
584
+ function isRecoverableGeneratingLatest(latest) {
585
+ if (latest?.generating !== true) {
586
+ return false;
587
+ }
588
+ const marker = latest.executionMarker;
589
+ if (!marker) {
590
+ return true;
591
+ }
592
+ if (marker.kind === 'dead') {
593
+ return false;
594
+ }
595
+ return marker.kind !== 'interrupted' || marker.reason.kind === 'pending_course_start';
596
+ }
583
597
  async function reconcileDisplayStatesAfterRestart() {
584
598
  const dialogIds = await persistence_1.DialogPersistence.listAllDialogIds('running');
585
599
  for (const dialogId of dialogIds) {
@@ -618,10 +632,28 @@ async function reconcileDisplayStatesAfterRestart() {
618
632
  }
619
633
  continue;
620
634
  }
621
- const wasProceeding = latest?.generating === true ||
622
- (existing !== undefined &&
623
- (existing.kind === 'proceeding' || existing.kind === 'proceeding_stop_requested'));
624
- if (wasProceeding) {
635
+ if (isRecoverableGeneratingLatest(latest)) {
636
+ try {
637
+ await persistence_1.DialogPersistence.mutateDialogLatest(dialogId, () => ({
638
+ kind: 'patch',
639
+ patch: {
640
+ needsDrive: true,
641
+ displayState: { kind: 'proceeding' },
642
+ executionMarker: existingMarker?.kind === 'interrupted' &&
643
+ existingMarker.reason.kind === 'pending_course_start'
644
+ ? undefined
645
+ : existingMarker,
646
+ },
647
+ }));
648
+ }
649
+ catch (err) {
650
+ log.warn('Failed to preserve proceeding dialog for auto-drive after restart', err, {
651
+ dialogId: dialogId.valueOf(),
652
+ });
653
+ }
654
+ continue;
655
+ }
656
+ if (latest?.generating === true || latest?.needsDrive === true) {
625
657
  const nextIdle = await computeIdleDisplayStateForReconciliation(dialogId);
626
658
  if (!nextIdle) {
627
659
  continue;
@@ -484,6 +484,7 @@ providers:
484
484
  models:
485
485
  kimi-for-coding:
486
486
  name: Kimi For Coding
487
+ optimal_max_tokens: 180000
487
488
  supports_thinking: true
488
489
  default_thinking: true
489
490
  supports_tool_choice: false
@@ -603,6 +604,7 @@ providers:
603
604
  context_window: '128K'
604
605
  kimi-k2.6:
605
606
  name: Kimi-K2.6
607
+ optimal_max_tokens: 180000
606
608
  supports_thinking: true
607
609
  default_thinking: true
608
610
  supports_tool_choice: false
@@ -613,6 +615,7 @@ providers:
613
615
  context_window: '200K'
614
616
  kimi-k2.5:
615
617
  name: Kimi-K2.5
618
+ optimal_max_tokens: 180000
616
619
  supports_thinking: true
617
620
  default_thinking: false
618
621
  supports_tool_choice: false
@@ -66,13 +66,11 @@ const log_1 = require("../../log");
66
66
  const reply_prompt_copy_1 = require("../../runtime/reply-prompt-copy");
67
67
  const gen_1 = require("../gen");
68
68
  const stop_reason_i18n_1 = require("../stop-reason-i18n");
69
- const REPLY_TOOL_REMINDER_PREFIXES = [
70
- '[Dominds replyTellask required]',
71
- '[Dominds 必须调用回复工具]',
72
- ];
73
69
  const RUNTIME_PROMPT_WRAPPER_PREFIXES = [
74
70
  reply_prompt_copy_1.ACTIVE_REPLY_TOOL_PREFIX_EN,
75
71
  reply_prompt_copy_1.ACTIVE_REPLY_TOOL_PREFIX_ZH,
72
+ reply_prompt_copy_1.NO_ACTIVE_REPLY_PREFIX_EN,
73
+ reply_prompt_copy_1.NO_ACTIVE_REPLY_PREFIX_ZH,
76
74
  reply_prompt_copy_1.REPLY_REASSERTION_PREFIX_EN,
77
75
  reply_prompt_copy_1.REPLY_REASSERTION_PREFIX_ZH,
78
76
  reply_prompt_copy_1.REPLY_SUPPRESSION_PREFIX_EN,
@@ -277,8 +275,7 @@ class MockGen {
277
275
  return null;
278
276
  }
279
277
  buildReplyToolReminderAutoResponse(input, role, context) {
280
- if (role !== 'user' ||
281
- !REPLY_TOOL_REMINDER_PREFIXES.some((prefix) => input.startsWith(prefix))) {
278
+ if (role !== 'user' || !(0, reply_prompt_copy_1.isReplyToolReminderPromptContent)(input)) {
282
279
  return null;
283
280
  }
284
281
  const toolMatch = input.match(/`(replyTellask(?:Sessionless|Back)?)`/);
@@ -23,12 +23,8 @@ const idle_reminder_wake_1 = require("./idle-reminder-wake");
23
23
  const reply_guidance_1 = require("./reply-guidance");
24
24
  const sideDialog_1 = require("./sideDialog");
25
25
  const tellask_special_1 = require("./tellask-special");
26
- const REPLY_TOOL_REMINDER_PREFIX_EN = '[Dominds replyTellask required]';
27
- const REPLY_TOOL_REMINDER_PREFIX_ZH = '[Dominds 必须调用回复工具]';
28
26
  function isReplyToolReminderPrompt(prompt) {
29
- return (typeof prompt?.content === 'string' &&
30
- (prompt.content.startsWith(REPLY_TOOL_REMINDER_PREFIX_EN) ||
31
- prompt.content.startsWith(REPLY_TOOL_REMINDER_PREFIX_ZH)));
27
+ return typeof prompt?.content === 'string' && (0, reply_prompt_copy_1.isReplyToolReminderPromptContent)(prompt.content);
32
28
  }
33
29
  function isIgnorablePostResponseAnchorTailEvent(type) {
34
30
  return type === 'tellask_reply_resolution_record' || type === 'gen_finish_record';
@@ -37,7 +33,6 @@ async function buildReplyToolReminderPrompt(args) {
37
33
  return (0, reply_prompt_copy_1.buildReplyToolReminderText)({
38
34
  language: args.language,
39
35
  directive: args.directive,
40
- prefix: args.language === 'zh' ? REPLY_TOOL_REMINDER_PREFIX_ZH : REPLY_TOOL_REMINDER_PREFIX_EN,
41
36
  replyTargetAgentId: await (0, reply_guidance_1.resolveReplyTargetAgentId)({
42
37
  dlg: args.dlg,
43
38
  directive: args.directive,
@@ -280,6 +275,7 @@ async function inspectNoPromptSideDialogDrive(args) {
280
275
  : undefined;
281
276
  const explicitInterruptedResumeAllowed = args.driveOptions?.allowResumeFromInterrupted === true &&
282
277
  latest?.executionMarker?.kind === 'interrupted';
278
+ const inProgressGenerationResumeAllowed = args.driveOptions?.resumeInProgressGeneration === true;
283
279
  const supplyResponseParentReviveAllowed = source === 'kernel_driver_supply_response_parent_revive' &&
284
280
  hasNoPromptSideDialogResumeEntitlement(args.dialog, args.driveOptions);
285
281
  if (lastEvent?.type === 'tellask_anchor_record' && lastEvent.anchorRole === 'response') {
@@ -292,7 +288,9 @@ async function inspectNoPromptSideDialogDrive(args) {
292
288
  lastEvent,
293
289
  };
294
290
  }
295
- if (!explicitInterruptedResumeAllowed && !supplyResponseParentReviveAllowed) {
291
+ if (!explicitInterruptedResumeAllowed &&
292
+ !inProgressGenerationResumeAllowed &&
293
+ !supplyResponseParentReviveAllowed) {
296
294
  return {
297
295
  shouldReject: true,
298
296
  source,
@@ -578,7 +576,7 @@ async function executeDriveRound(args) {
578
576
  : hasEntitledParentRevive
579
577
  ? await loadFreshSuspensionStatusFromPersistence(dialog, driveOptions)
580
578
  : await dialog.getSuspensionStatus({
581
- allowPendingSideDialogs: false,
579
+ allowPendingSideDialogs: driveOptions?.resumeInProgressGeneration === true,
582
580
  });
583
581
  const queuedPrompt = dialog.peekUpNext();
584
582
  const queuedSideDialogPromptCanResume = dialog instanceof dialog_1.SideDialog && queuedPrompt !== undefined;
@@ -810,8 +808,7 @@ async function executeDriveRound(args) {
810
808
  });
811
809
  }
812
810
  else {
813
- const shouldDirectFallbackAfterParentRevive = hasParentReviveEntitlement(dialog, driveOptions);
814
- if (!activePromptWasReplyToolReminder && !shouldDirectFallbackAfterParentRevive) {
811
+ if (!activePromptWasReplyToolReminder) {
815
812
  const language = (0, work_language_1.getWorkLanguage)();
816
813
  followUp =
817
814
  sideDialogReplyTarget === undefined
@@ -846,7 +843,6 @@ async function executeDriveRound(args) {
846
843
  dialogId: dialog.id.valueOf(),
847
844
  targetCallId: activeTellaskReplyDirective.targetCallId,
848
845
  targetOwnerDialogId: sideDialogReplyTarget?.ownerDialogId,
849
- directFallbackAfterParentRevive: shouldDirectFallbackAfterParentRevive,
850
846
  });
851
847
  }
852
848
  else {
@@ -1015,7 +1011,8 @@ async function executeDriveRound(args) {
1015
1011
  : { skipTaskdoc: followUp.skipTaskdoc }),
1016
1012
  };
1017
1013
  if (followUp.kind === 'registered_assignment_update' ||
1018
- followUp.kind === 'new_course_runtime_sideDialog') {
1014
+ followUp.kind === 'new_course_runtime_sideDialog' ||
1015
+ followUp.kind === 'runtime_sideDialog_reply_reminder') {
1019
1016
  const prompt = {
1020
1017
  ...runtimeCommon,
1021
1018
  tellaskReplyDirective: followUp.tellaskReplyDirective,
@@ -1023,7 +1020,8 @@ async function executeDriveRound(args) {
1023
1020
  };
1024
1021
  return prompt;
1025
1022
  }
1026
- if (followUp.kind === 'new_course_runtime_reply') {
1023
+ if (followUp.kind === 'new_course_runtime_reply' ||
1024
+ followUp.kind === 'runtime_reply_reminder') {
1027
1025
  const prompt = {
1028
1026
  ...runtimeCommon,
1029
1027
  tellaskReplyDirective: followUp.tellaskReplyDirective,
@@ -46,12 +46,14 @@ async function driveQueuedDialogsOnce() {
46
46
  dialog_global_registry_1.globalDialogRegistry.noteActiveRunBlockedQueuedDrive(mainDialog.id.rootId);
47
47
  continue;
48
48
  }
49
- if (!(await mainDialog.canDrive())) {
49
+ const resumeInProgressGeneration = latest?.generating === true;
50
+ if (!resumeInProgressGeneration && !(await mainDialog.canDrive())) {
50
51
  continue;
51
52
  }
52
53
  await (0, engine_1.driveDialogStream)(mainDialog, undefined, true, {
53
54
  source: 'kernel_driver_backend_loop',
54
55
  reason: 'global_dialog_registry_needs_drive',
56
+ ...(resumeInProgressGeneration ? { resumeInProgressGeneration: true } : {}),
55
57
  });
56
58
  const status = await mainDialog.getSuspensionStatus();
57
59
  const shouldStayQueued = mainDialog.hasUpNext() || !status.canDrive;
@@ -11,8 +11,6 @@ const interjection_pause_stop_1 = require("../../runtime/interjection-pause-stop
11
11
  const reply_prompt_copy_1 = require("../../runtime/reply-prompt-copy");
12
12
  const work_language_1 = require("../../runtime/work-language");
13
13
  const tellask_special_1 = require("./tellask-special");
14
- const REPLY_TOOL_REMINDER_PREFIX_EN = '[Dominds replyTellask required]';
15
- const REPLY_TOOL_REMINDER_PREFIX_ZH = '[Dominds 必须调用回复工具]';
16
14
  async function resolveReplyTargetAgentId(args) {
17
15
  const mainDialog = args.dlg instanceof dialog_1.MainDialog
18
16
  ? args.dlg
@@ -30,13 +28,11 @@ async function resolveReplyTargetAgentId(args) {
30
28
  function buildPromptContentWithExactReplyToolName(args) {
31
29
  const isFbrSideDialog = args.dlg instanceof dialog_1.SideDialog &&
32
30
  args.dlg.assignmentFromAsker.callName === 'freshBootsReasoning';
33
- const noActivePrefix = args.language === 'zh'
34
- ? '[Dominds 当前无跨对话回复义务]'
35
- : '[Dominds no active inter-dialog reply]';
31
+ const noActivePrefix = args.language === 'zh' ? reply_prompt_copy_1.NO_ACTIVE_REPLY_PREFIX_ZH : reply_prompt_copy_1.NO_ACTIVE_REPLY_PREFIX_EN;
36
32
  const activePrefix = args.language === 'zh' ? reply_prompt_copy_1.ACTIVE_REPLY_TOOL_PREFIX_ZH : reply_prompt_copy_1.ACTIVE_REPLY_TOOL_PREFIX_EN;
37
33
  const reminderPrefixes = [
38
- REPLY_TOOL_REMINDER_PREFIX_EN,
39
- REPLY_TOOL_REMINDER_PREFIX_ZH,
34
+ reply_prompt_copy_1.REPLY_TOOL_REMINDER_PREFIX_EN,
35
+ reply_prompt_copy_1.REPLY_TOOL_REMINDER_PREFIX_ZH,
40
36
  reply_prompt_copy_1.REPLY_REASSERTION_PREFIX_EN,
41
37
  reply_prompt_copy_1.REPLY_REASSERTION_PREFIX_ZH,
42
38
  ];
@@ -7,6 +7,7 @@ export type KernelDriverDriveSource = 'unspecified' | 'ws_user_message' | 'ws_us
7
7
  export type KernelDriverDriveOptions = Readonly<{
8
8
  suppressDiligencePush?: boolean;
9
9
  allowResumeFromInterrupted?: boolean;
10
+ resumeInProgressGeneration?: boolean;
10
11
  noPromptSideDialogResumeEntitlement?: Readonly<{
11
12
  ownerDialogId: string;
12
13
  reason: 'reply_tellask_back_delivered';
@@ -3764,6 +3764,7 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
3764
3764
  case 'pending_sideDialogs_reconciled_record':
3765
3765
  case 'sideDialog_registry_reconciled_record':
3766
3766
  case 'sideDialog_responses_reconciled_record':
3767
+ case 'tellask_anchor_record':
3767
3768
  break;
3768
3769
  case 'tellask_carryover_record': {
3769
3770
  const base = {
@@ -0,0 +1 @@
1
+ export declare function recoverProceedingDrivesAfterRestart(): Promise<void>;
@@ -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
+ }
@@ -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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dominds",
3
- "version": "1.23.1",
3
+ "version": "1.23.2",
4
4
  "description": "Dominds CLI and aggregation shell for the LongRun AI kernel/runtime packages.",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {
@@ -52,8 +52,8 @@
52
52
  "ws": "^8.19.0",
53
53
  "yaml": "^2.8.2",
54
54
  "zod": "^4.3.6",
55
- "@longrun-ai/codex-auth": "0.13.0",
56
55
  "@longrun-ai/kernel": "1.13.1",
56
+ "@longrun-ai/codex-auth": "0.13.0",
57
57
  "@longrun-ai/shell": "1.13.1"
58
58
  },
59
59
  "devDependencies": {