dominds 1.27.2 → 1.27.3

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 (36) hide show
  1. package/dist/apps/runtime.js +3 -1
  2. package/dist/dialog-global-registry.d.ts +11 -1
  3. package/dist/dialog-global-registry.js +45 -0
  4. package/dist/dialog.d.ts +11 -2
  5. package/dist/dialog.js +107 -17
  6. package/dist/docs/daemon-cmd-runner.md +5 -0
  7. package/dist/docs/daemon-cmd-runner.zh.md +5 -0
  8. package/dist/llm/kernel-driver/drive.js +163 -11
  9. package/dist/llm/kernel-driver/fbr.d.ts +9 -0
  10. package/dist/llm/kernel-driver/fbr.js +186 -59
  11. package/dist/minds/load.js +1 -0
  12. package/dist/persistence.js +18 -1
  13. package/dist/runtime/driver-messages.d.ts +9 -0
  14. package/dist/runtime/driver-messages.js +61 -5
  15. package/dist/runtime/shared-reminder-update-impact.d.ts +20 -0
  16. package/dist/runtime/shared-reminder-update-impact.js +110 -0
  17. package/dist/tool-availability.js +1 -0
  18. package/dist/tools/builtins.js +2 -0
  19. package/dist/tools/cmd-runner-protocol.d.ts +6 -0
  20. package/dist/tools/cmd-runner-protocol.js +57 -2
  21. package/dist/tools/cmd-runner.js +83 -2
  22. package/dist/tools/ctrl.d.ts +2 -0
  23. package/dist/tools/ctrl.js +179 -5
  24. package/dist/tools/os.js +115 -14
  25. package/dist/tools/process-kill.js +49 -0
  26. package/dist/tools/prompts/control/en/errors.md +1 -1
  27. package/dist/tools/prompts/control/en/index.md +1 -1
  28. package/dist/tools/prompts/control/en/principles.md +18 -17
  29. package/dist/tools/prompts/control/en/tools.md +24 -1
  30. package/dist/tools/prompts/control/zh/errors.md +1 -1
  31. package/dist/tools/prompts/control/zh/index.md +1 -1
  32. package/dist/tools/prompts/control/zh/principles.md +17 -16
  33. package/dist/tools/prompts/control/zh/tools.md +24 -1
  34. package/dist/tools/prompts/os/en/tools.md +2 -0
  35. package/dist/tools/prompts/os/zh/tools.md +2 -0
  36. package/package.json +4 -4
@@ -123,7 +123,9 @@ async function resolveTargetDialog(sourceDlg, target) {
123
123
  }
124
124
  return sideDialog;
125
125
  }
126
- const matches = targetRoot.getAllDialogs().filter((dialog) => dialog.agentId === target.agentId);
126
+ const matches = targetRoot
127
+ .getLoadedDialogTreeSnapshot()
128
+ .filter((dialog) => dialog.agentId === target.agentId);
127
129
  if (matches.length === 1) {
128
130
  return matches[0];
129
131
  }
@@ -1,4 +1,4 @@
1
- import { type MainDialog } from './dialog';
1
+ import { type Dialog, type MainDialog } from './dialog';
2
2
  export type DriveTriggerEvent = Readonly<{
3
3
  type: 'drive_trigger_evt';
4
4
  action: 'queue_root_drive' | 'clear_root_drive_queue' | 'active_run_cleared';
@@ -47,6 +47,16 @@ declare class GlobalDialogRegistry {
47
47
  noteActiveRunBlockedQueuedDrive(rootId: string): void;
48
48
  hasPendingActiveRunClearedDrive(rootId: string): boolean;
49
49
  isRootDriveQueued(rootId: string): boolean;
50
+ /**
51
+ * One-shot runtime snapshot for shared-reminder impact routing.
52
+ *
53
+ * This intentionally does not enumerate persisted "all running dialogs". It walks only the roots
54
+ * this runtime has already registered, and includes only dialogs with direct in-memory evidence
55
+ * of current work: locked running main dialogs and the loaded active-callee frontier under each
56
+ * root. Dialogs that merely remain in memory/history but have no current-work signal are not
57
+ * considered parallel work.
58
+ */
59
+ getLoadedInFlightDialogsForSharedReminderImpact(): Dialog[];
50
60
  getLastDriveTrigger(rootId: string): DriveTriggerEvent | undefined;
51
61
  consumeQueuedMainDialogs(): MainDialog[];
52
62
  get size(): number;
@@ -218,6 +218,51 @@ class GlobalDialogRegistry {
218
218
  isRootDriveQueued(rootId) {
219
219
  return this.entries.get(rootId)?.driveQueued === true;
220
220
  }
221
+ /**
222
+ * One-shot runtime snapshot for shared-reminder impact routing.
223
+ *
224
+ * This intentionally does not enumerate persisted "all running dialogs". It walks only the roots
225
+ * this runtime has already registered, and includes only dialogs with direct in-memory evidence
226
+ * of current work: locked running main dialogs and the loaded active-callee frontier under each
227
+ * root. Dialogs that merely remain in memory/history but have no current-work signal are not
228
+ * considered parallel work.
229
+ */
230
+ getLoadedInFlightDialogsForSharedReminderImpact() {
231
+ const dialogs = [];
232
+ for (const entry of this.entries.values()) {
233
+ const rootDialog = entry.mainDialog;
234
+ const visitedDialogIds = new Set();
235
+ const stack = [];
236
+ if (rootDialog.status === 'running' && rootDialog.isLocked()) {
237
+ dialogs.push(rootDialog);
238
+ visitedDialogIds.add(rootDialog.id.valueOf());
239
+ }
240
+ for (const calleeId of rootDialog.activeCalleeDialogIds) {
241
+ const loadedCallee = rootDialog.lookupDialog(calleeId.selfId);
242
+ if (loadedCallee && loadedCallee.status === 'running') {
243
+ stack.push(loadedCallee);
244
+ }
245
+ }
246
+ while (stack.length > 0) {
247
+ const current = stack.pop();
248
+ if (!current || visitedDialogIds.has(current.id.valueOf())) {
249
+ continue;
250
+ }
251
+ visitedDialogIds.add(current.id.valueOf());
252
+ if (current.status !== 'running') {
253
+ continue;
254
+ }
255
+ dialogs.push(current);
256
+ for (const calleeId of current.activeCalleeDialogIds) {
257
+ const loadedCallee = rootDialog.lookupDialog(calleeId.selfId);
258
+ if (loadedCallee && loadedCallee.status === 'running') {
259
+ stack.push(loadedCallee);
260
+ }
261
+ }
262
+ }
263
+ }
264
+ return dialogs;
265
+ }
221
266
  getLastDriveTrigger(rootId) {
222
267
  return this.lastDriveTriggerByRootId.get(rootId);
223
268
  }
package/dist/dialog.d.ts CHANGED
@@ -275,6 +275,8 @@ export declare abstract class Dialog {
275
275
  private processReminderCollection;
276
276
  private buildVisibleReminderContents;
277
277
  private emitFullRemindersUpdate;
278
+ private reportReminderRuntimeFailure;
279
+ private dispatchSharedReminderOwnerUpdateImpacts;
278
280
  /**
279
281
  * Emits current visible reminders without reconciling tool-owned state or writing persistence.
280
282
  * This is for read-only dialog display, especially completed/archived dialogs opened from old tabs.
@@ -371,6 +373,13 @@ export declare abstract class Dialog {
371
373
  tellaskReplyDirective: TellaskReplyDirective;
372
374
  skipTaskdoc?: boolean;
373
375
  }): Promise<DialogQueuedPromptState>;
376
+ queueRuntimeGuidePrompt(options: {
377
+ prompt: string;
378
+ msgId: string;
379
+ grammar: 'markdown';
380
+ userLanguageCode?: LanguageCode;
381
+ skipTaskdoc?: boolean;
382
+ }): Promise<DialogQueuedPromptState>;
374
383
  queueRuntimeSideDialogPrompt(options: {
375
384
  prompt: string;
376
385
  msgId: string;
@@ -529,9 +538,9 @@ export declare class MainDialog extends Dialog {
529
538
  */
530
539
  lookupDialog(selfId: string): Dialog | undefined;
531
540
  /**
532
- * Get all registered dialogs in this dialog tree.
541
+ * Snapshot of dialogs loaded in this root's in-memory dialog tree.
533
542
  */
534
- getAllDialogs(): Dialog[];
543
+ getLoadedDialogTreeSnapshot(): Dialog[];
535
544
  /**
536
545
  * Remove a dialog from the local registry.
537
546
  */
package/dist/dialog.js CHANGED
@@ -572,6 +572,7 @@ class Dialog {
572
572
  }
573
573
  async processReminderCollection(reminders) {
574
574
  let changed = false;
575
+ const updatedReminderIds = [];
575
576
  const indicesToRemove = [];
576
577
  for (let i = 0; i < reminders.length; i++) {
577
578
  const reminder = reminders[i];
@@ -599,26 +600,42 @@ class Dialog {
599
600
  priority: reminder.priority,
600
601
  renderMode: reminder.renderMode,
601
602
  });
602
- const contentChanged = updatedReminder.content !== reminder.content;
603
- const metaChanged = updatedReminder.meta !== reminder.meta;
604
- if (contentChanged || metaChanged) {
603
+ const reminderChanged = (0, tool_1.computeReminderRenderRevision)(updatedReminder) !==
604
+ (0, tool_1.computeReminderRenderRevision)(reminder);
605
+ if (reminderChanged) {
605
606
  reminders[i] = updatedReminder;
606
607
  changed = true;
608
+ updatedReminderIds.push(updatedReminder.id);
607
609
  }
608
610
  break;
609
611
  }
610
612
  case 'keep':
611
613
  break;
614
+ default: {
615
+ const _exhaustive = result.treatment;
616
+ return _exhaustive;
617
+ }
612
618
  }
613
619
  }
614
620
  catch (error) {
615
- log_1.log.error(`Error updating reminder from tool ${reminder.owner}:`, error);
621
+ const detail = `Reminder owner update failed ` +
622
+ `(rootId=${this.id.rootId}, selfId=${this.id.selfId}, course=${this.currentCourse}, ` +
623
+ `reminderId=${reminder.id}, owner=${reminder.owner.name}, taskDocPath=${this.taskDocPath})`;
624
+ await this.reportReminderRuntimeFailure(detail, error, {
625
+ rootId: this.id.rootId,
626
+ selfId: this.id.selfId,
627
+ course: this.currentCourse,
628
+ reminderId: reminder.id,
629
+ ownerName: reminder.owner.name,
630
+ taskDocPath: this.taskDocPath,
631
+ });
632
+ throw error;
616
633
  }
617
634
  }
618
635
  for (let i = indicesToRemove.length - 1; i >= 0; i--) {
619
636
  reminders.splice(indicesToRemove[i], 1);
620
637
  }
621
- return changed;
638
+ return { changed, updatedReminderIds };
622
639
  }
623
640
  buildVisibleReminderContents(taskSharedReminders, runtimeReminders) {
624
641
  const visibleReminders = [
@@ -644,6 +661,32 @@ class Dialog {
644
661
  };
645
662
  (0, evt_registry_1.postDialogEvent)(this, fullRemindersEvt);
646
663
  }
664
+ async reportReminderRuntimeFailure(detail, error, context) {
665
+ try {
666
+ await this.streamError(detail);
667
+ }
668
+ catch (streamError) {
669
+ log_1.log.warn('Failed to emit stream_error_evt for reminder runtime failure', streamError, {
670
+ ...context,
671
+ streamErrorMessage: streamError instanceof Error ? streamError.message : String(streamError),
672
+ });
673
+ }
674
+ log_1.log.error(detail, error, {
675
+ ...context,
676
+ errorMessage: error instanceof Error ? error.message : String(error),
677
+ });
678
+ }
679
+ async dispatchSharedReminderOwnerUpdateImpacts(reminderIds, scope) {
680
+ const { dispatchSharedReminderUpdateImpact } = await import('./runtime/shared-reminder-update-impact.js');
681
+ for (const reminderId of reminderIds) {
682
+ await dispatchSharedReminderUpdateImpact({
683
+ updater: this,
684
+ reminderId,
685
+ scope,
686
+ language: (0, work_language_1.getWorkLanguage)(),
687
+ });
688
+ }
689
+ }
647
690
  /**
648
691
  * Emits current visible reminders without reconciling tool-owned state or writing persistence.
649
692
  * This is for read-only dialog display, especially completed/archived dialogs opened from old tabs.
@@ -675,10 +718,10 @@ class Dialog {
675
718
  const runtimeTarget = { kind: 'agent', agentId: this.agentId };
676
719
  const taskSharedReminders = await (0, shared_reminders_1.loadSharedReminders)(taskSharedTarget);
677
720
  const runtimeReminders = await (0, shared_reminders_1.loadSharedReminders)(runtimeTarget);
678
- const localChanged = await this.processReminderCollection(this.reminders);
679
- const taskSharedChanged = await this.processReminderCollection(taskSharedReminders);
680
- const runtimeChanged = await this.processReminderCollection(runtimeReminders);
681
- if (localChanged || taskSharedChanged || runtimeChanged) {
721
+ const localResult = await this.processReminderCollection(this.reminders);
722
+ const taskSharedResult = await this.processReminderCollection(taskSharedReminders);
723
+ const runtimeResult = await this.processReminderCollection(runtimeReminders);
724
+ if (localResult.changed || taskSharedResult.changed || runtimeResult.changed) {
682
725
  this.touchReminders();
683
726
  }
684
727
  // Centralized persistence - called when emitting event.
@@ -687,26 +730,55 @@ class Dialog {
687
730
  await this.dlgStore.persistReminders(this, this.reminders);
688
731
  }
689
732
  catch (err) {
690
- log_1.log.warn('Failed to persist reminders', err, { dialogId: this.id.valueOf() });
733
+ const detail = `Failed to persist dialog-local reminders ` +
734
+ `(rootId=${this.id.rootId}, selfId=${this.id.selfId}, course=${this.currentCourse})`;
735
+ await this.reportReminderRuntimeFailure(detail, err, {
736
+ rootId: this.id.rootId,
737
+ selfId: this.id.selfId,
738
+ course: this.currentCourse,
739
+ taskDocPath: this.taskDocPath,
740
+ });
741
+ throw err;
691
742
  }
692
743
  try {
693
744
  await (0, shared_reminders_1.replaceSharedReminders)(taskSharedTarget, taskSharedReminders);
694
745
  }
695
746
  catch (err) {
696
- log_1.log.warn('Failed to persist task-scoped reminders', err, {
697
- dialogId: this.id.valueOf(),
747
+ const detail = `Failed to persist task-scoped reminders ` +
748
+ `(rootId=${this.id.rootId}, selfId=${this.id.selfId}, course=${this.currentCourse}, ` +
749
+ `agentId=${this.agentId}, taskDocPath=${this.taskDocPath})`;
750
+ await this.reportReminderRuntimeFailure(detail, err, {
751
+ rootId: this.id.rootId,
752
+ selfId: this.id.selfId,
753
+ course: this.currentCourse,
698
754
  agentId: this.agentId,
699
755
  taskDocPath: this.taskDocPath,
700
756
  });
757
+ throw err;
701
758
  }
759
+ await this.dispatchSharedReminderOwnerUpdateImpacts(taskSharedResult.updatedReminderIds, 'task');
702
760
  try {
703
761
  await (0, shared_reminders_1.replaceSharedReminders)(runtimeTarget, runtimeReminders);
704
762
  }
705
763
  catch (err) {
706
- log_1.log.warn('Failed to persist agent-scoped reminders', err, {
707
- dialogId: this.id.valueOf(),
764
+ const detail = `Failed to persist agent-scoped reminders ` +
765
+ `(rootId=${this.id.rootId}, selfId=${this.id.selfId}, course=${this.currentCourse}, ` +
766
+ `agentId=${this.agentId})`;
767
+ await this.reportReminderRuntimeFailure(detail, err, {
768
+ rootId: this.id.rootId,
769
+ selfId: this.id.selfId,
770
+ course: this.currentCourse,
708
771
  agentId: this.agentId,
772
+ taskDocPath: this.taskDocPath,
709
773
  });
774
+ throw err;
775
+ }
776
+ for (const reminderId of runtimeResult.updatedReminderIds) {
777
+ const reminder = runtimeReminders.find((candidate) => candidate.id === reminderId);
778
+ if (!reminder) {
779
+ throw new Error(`Updated shared reminder ${reminderId} disappeared before update impact dispatch`);
780
+ }
781
+ await this.dispatchSharedReminderOwnerUpdateImpacts([reminderId], reminder.scope === 'runtime' ? 'runtime' : 'agent');
710
782
  }
711
783
  const reminders = this.buildVisibleReminderContents(taskSharedReminders, runtimeReminders);
712
784
  this.emitFullRemindersUpdate(reminders);
@@ -1168,6 +1240,24 @@ class Dialog {
1168
1240
  });
1169
1241
  return created;
1170
1242
  }
1243
+ async queueRuntimeGuidePrompt(options) {
1244
+ const common = this.runtimePromptCommon(options);
1245
+ const created = {
1246
+ ...common,
1247
+ kind: 'new_course_runtime_guide',
1248
+ skipTaskdoc: options.skipTaskdoc,
1249
+ };
1250
+ this.enqueueQueuedPromptState(created);
1251
+ await this.persistPendingRuntimePrompt({
1252
+ content: created.prompt,
1253
+ msgId: created.msgId,
1254
+ grammar: created.grammar ?? 'markdown',
1255
+ userLanguageCode: created.userLanguageCode,
1256
+ origin: 'runtime',
1257
+ skipTaskdoc: created.skipTaskdoc,
1258
+ });
1259
+ return created;
1260
+ }
1171
1261
  async queueRuntimeSideDialogPrompt(options) {
1172
1262
  const common = this.runtimePromptCommon(options);
1173
1263
  const created = {
@@ -1860,7 +1950,7 @@ exports.SideDialog = SideDialog;
1860
1950
  */
1861
1951
  class MainDialog extends Dialog {
1862
1952
  _status = 'running';
1863
- // Tracks all dialogs in this dialog tree for O(1) lookup
1953
+ // Tracks dialogs loaded into this root's in-memory dialog tree for O(1) lookup
1864
1954
  _localRegistry = new Map();
1865
1955
  // Tracks Type-B registered sideDialogs by agentId!sessionSlug
1866
1956
  _sideDialogRegistry = new Map();
@@ -1887,9 +1977,9 @@ class MainDialog extends Dialog {
1887
1977
  return this._localRegistry.get(selfId);
1888
1978
  }
1889
1979
  /**
1890
- * Get all registered dialogs in this dialog tree.
1980
+ * Snapshot of dialogs loaded in this root's in-memory dialog tree.
1891
1981
  */
1892
- getAllDialogs() {
1982
+ getLoadedDialogTreeSnapshot() {
1893
1983
  return Array.from(this._localRegistry.values());
1894
1984
  }
1895
1985
  /**
@@ -115,6 +115,8 @@ Ownership boundaries are simple:
115
115
  - `pid: number`
116
116
  - `stdout?: boolean`
117
117
  - `stderr?: boolean`
118
+ - `wait_for_new_output?: boolean`
119
+ - `timeout_ms?: number`
118
120
 
119
121
  ### Semantics
120
122
 
@@ -123,6 +125,9 @@ Ownership boundaries are simple:
123
125
  - output order is always `stdout` first, then `stderr`
124
126
  - each stream gets its own heading, content block, and scroll notice
125
127
  - unrequested streams are omitted entirely
128
+ - when `wait_for_new_output=true`, the runner waits until at least one requested stream receives output after the request is accepted
129
+ - if `timeout_ms` is provided, the wait is bounded by that many milliseconds, capped at `86400000` (24h), and returns the current snapshot with an explicit timeout notice
130
+ - `timeout_ms` cannot be combined with `wait_for_new_output=false`; that contradictory argument set returns an error
126
131
 
127
132
  ### Why this is better
128
133
 
@@ -114,6 +114,8 @@ runner 自己作为进程组 leader,daemon 默认继承该 pgid。这样 `stop
114
114
  - `pid: number`
115
115
  - `stdout?: boolean`
116
116
  - `stderr?: boolean`
117
+ - `wait_for_new_output?: boolean`
118
+ - `timeout_ms?: number`
117
119
 
118
120
  ### 语义
119
121
 
@@ -122,6 +124,9 @@ runner 自己作为进程组 leader,daemon 默认继承该 pgid。这样 `stop
122
124
  - 返回顺序固定为 `stdout` 在前、`stderr` 在后
123
125
  - 每个流各自带自己的标题、内容、scroll notice
124
126
  - 未请求的流不展示
127
+ - `wait_for_new_output=true` 时,runner 会等到请求的流中至少一个在请求被接受后出现新输出再返回
128
+ - 提供 `timeout_ms` 时,等待最多持续指定毫秒数,上限为 `86400000`(24 小时);超时后返回当前快照,并显式提示等待超时
129
+ - `timeout_ms` 不可与 `wait_for_new_output=false` 同时使用;这种矛盾参数会直接报错
125
130
 
126
131
  ### 取舍理由
127
132
 
@@ -46,6 +46,7 @@ const KERNEL_DRIVER_DEFAULT_RETRY_POLICY = {
46
46
  backoffMultiplier: 1.5,
47
47
  maxDelayMs: 30 * 60 * 1000, // 30 minutes
48
48
  };
49
+ const CLEAR_MIND_TOOL_NAME = 'clear_mind';
49
50
  const KERNEL_DRIVER_EMPTY_LLM_RESPONSE_ERROR_CODE = 'DOMINDS_LLM_EMPTY_RESPONSE';
50
51
  // Wrapper isolation boundary:
51
52
  // - Wrappers emit provider-native web-search events.
@@ -246,6 +247,35 @@ function buildKernelDriverFbrPrompt(dlg, state) {
246
247
  origin: 'runtime',
247
248
  };
248
249
  }
250
+ function resolveLatestModelOutputGenseq(dlg) {
251
+ for (let index = dlg.msgs.length - 1; index >= 0; index -= 1) {
252
+ const msg = dlg.msgs[index];
253
+ if (msg === undefined) {
254
+ continue;
255
+ }
256
+ switch (msg.type) {
257
+ case 'saying_msg':
258
+ case 'thinking_msg':
259
+ case 'func_call_msg': {
260
+ const genseq = Math.floor(msg.genseq);
261
+ if (Number.isFinite(genseq) && genseq > 0) {
262
+ return genseq;
263
+ }
264
+ break;
265
+ }
266
+ default:
267
+ break;
268
+ }
269
+ }
270
+ return undefined;
271
+ }
272
+ function resolveProgrammaticFbrConclusionGenseq(args) {
273
+ return (args.lastAssistantSayingGenseq ??
274
+ args.lastFunctionCallGenseq ??
275
+ resolveLatestModelOutputGenseq(args.dlg) ??
276
+ args.dlg.activeGenSeqOrUndefined ??
277
+ 1);
278
+ }
249
279
  function normalizeQ4HAnswerCallId(raw) {
250
280
  if (typeof raw !== 'string')
251
281
  return undefined;
@@ -750,7 +780,7 @@ function formatContextHealthLargeToolReturnUnavailable(args) {
750
780
  '1. 把需要回传给主线对话的结论、证据定位和风险整理清楚。',
751
781
  '2. 对于下一程恢复工作需要的信息,写入提醒项。',
752
782
  '',
753
- '然后尽快完成当前支线回复;如果你有 clear_mind({}),再调用它开启新一程。',
783
+ '然后调用 clear_mind({}) 开启新一程,并尽快完成当前支线回复。',
754
784
  '',
755
785
  `详情:本次返回约 ${approxBytes} 字节。`,
756
786
  ].join('\n');
@@ -775,7 +805,7 @@ function formatContextHealthLargeToolReturnUnavailable(args) {
775
805
  '1. Organize the conclusions, evidence pointers, and risks that need to go back to the Mainline dialog.',
776
806
  '2. Write any details needed to resume the next course into reminders.',
777
807
  '',
778
- 'Then finish the current Sideline dialog reply as soon as possible; if you have clear_mind({}), call it to start a new course.',
808
+ 'Then call clear_mind({}) to start a new course, and finish the current Sideline dialog reply as soon as possible.',
779
809
  '',
780
810
  `Detail: this return was about ${approxBytes} bytes.`,
781
811
  ].join('\n');
@@ -814,6 +844,9 @@ function countToolResultVisibleBytes(output) {
814
844
  return bytes;
815
845
  }
816
846
  function applyContextHealthToolResultVisibilityLimit(args) {
847
+ if (isFbrSideDialog(args.dlg)) {
848
+ return { output: args.output, largeReturnUnavailable: false };
849
+ }
817
850
  if ((0, context_health_1.getContextHealthRemediationLevel)(args.contextHealth) === undefined) {
818
851
  return { output: args.output, largeReturnUnavailable: false };
819
852
  }
@@ -1961,6 +1994,30 @@ function shouldImmediatelyFollowUpToolOutcome(tool, outcome) {
1961
1994
  }
1962
1995
  return shouldImmediatelyFollowUpSuccessfulToolResult(tool);
1963
1996
  }
1997
+ function isFailedToolOutcome(outcome) {
1998
+ return outcome === 'failure' || outcome === 'partial_failure';
1999
+ }
2000
+ function formatClearMindBlockedByFailedSiblingTools(failedTools) {
2001
+ const language = (0, work_language_1.getWorkLanguage)();
2002
+ const details = failedTools
2003
+ .map((tool) => `- ${tool.toolName} (callId=${tool.callId}, outcome=${String(tool.outcome)})`)
2004
+ .join('\n');
2005
+ return language === 'zh'
2006
+ ? [
2007
+ '错误:本轮 clear_mind 与其它工具一起调用,但其它工具返回了失败结果。',
2008
+ '',
2009
+ details,
2010
+ '',
2011
+ 'clear_mind 已拒绝开启新一程。请先确保其它工具调用正常完成(必要时修正参数、重试或处理失败),然后再次调用 clear_mind。',
2012
+ ].join('\n')
2013
+ : [
2014
+ 'Error: clear_mind was called in the same round as other tools, and at least one other tool returned a failure result.',
2015
+ '',
2016
+ details,
2017
+ '',
2018
+ 'clear_mind refused to start a new course. Ensure the other tool calls complete normally first (fix arguments, retry, or handle the failure), then call clear_mind again.',
2019
+ ].join('\n');
2020
+ }
1964
2021
  function trimOptionalCallId(value) {
1965
2022
  if (typeof value !== 'string')
1966
2023
  return undefined;
@@ -2133,7 +2190,7 @@ async function executeFunctionCalls(args) {
2133
2190
  throwIfAborted(args.abortSignal, args.dlg);
2134
2191
  await args.dlg.persistFunctionCall(prepared.func.id, prepared.func.name, prepared.argsStr, prepared.callGenseq, prepared.func.rawId);
2135
2192
  }
2136
- const functionPromises = preparedCalls.map(async ({ func, originalFunc, callGenseq, argsStr, tool, preparedInvocationArgs, }) => {
2193
+ const executePreparedCall = async ({ func, originalFunc, callGenseq, argsStr, tool, preparedInvocationArgs, }) => {
2137
2194
  let result;
2138
2195
  let outcome = 'success';
2139
2196
  let forceImmediateFollowup = false;
@@ -2226,8 +2283,54 @@ async function executeFunctionCalls(args) {
2226
2283
  throw rethrowError;
2227
2284
  }
2228
2285
  return { func, originalFunc, outcome, forceImmediateFollowup, result };
2229
- });
2230
- return await Promise.all(functionPromises);
2286
+ };
2287
+ const blockClearMindCall = async (prepared, failedTools) => {
2288
+ const output = (0, tool_1.toolFailure)(formatClearMindBlockedByFailedSiblingTools(failedTools));
2289
+ const result = {
2290
+ type: 'func_result_msg',
2291
+ id: prepared.func.id,
2292
+ rawId: prepared.func.rawId,
2293
+ effectiveId: prepared.func.effectiveId,
2294
+ name: prepared.func.name,
2295
+ content: output.content,
2296
+ role: 'tool',
2297
+ genseq: prepared.callGenseq,
2298
+ };
2299
+ await args.dlg.receiveFuncResult(result);
2300
+ return {
2301
+ func: prepared.func,
2302
+ originalFunc: prepared.originalFunc,
2303
+ outcome: output.outcome,
2304
+ forceImmediateFollowup: false,
2305
+ result,
2306
+ };
2307
+ };
2308
+ const clearMindCalls = preparedCalls.filter((call) => call.func.name === CLEAR_MIND_TOOL_NAME);
2309
+ const siblingCalls = preparedCalls.filter((call) => call.func.name !== CLEAR_MIND_TOOL_NAME);
2310
+ const failedPriorToolResults = args.failedPriorToolResults ?? [];
2311
+ if (clearMindCalls.length === 0 || siblingCalls.length === 0) {
2312
+ if (clearMindCalls.length > 0 && failedPriorToolResults.length > 0) {
2313
+ return await Promise.all(clearMindCalls.map((call) => blockClearMindCall(call, failedPriorToolResults)));
2314
+ }
2315
+ return await Promise.all(preparedCalls.map((call) => executePreparedCall(call)));
2316
+ }
2317
+ const siblingExecutions = await Promise.all(siblingCalls.map((call) => executePreparedCall(call)));
2318
+ const failedSiblingToolResults = [
2319
+ ...failedPriorToolResults,
2320
+ ...siblingExecutions
2321
+ .filter((execution) => isFailedToolOutcome(execution.outcome))
2322
+ .map((execution) => ({
2323
+ callId: execution.result.id,
2324
+ toolName: execution.result.name,
2325
+ outcome: execution.outcome,
2326
+ })),
2327
+ ];
2328
+ if (failedSiblingToolResults.length > 0) {
2329
+ const clearMindExecutions = await Promise.all(clearMindCalls.map((call) => blockClearMindCall(call, failedSiblingToolResults)));
2330
+ return [...siblingExecutions, ...clearMindExecutions];
2331
+ }
2332
+ const clearMindExecutions = await Promise.all(clearMindCalls.map((call) => executePreparedCall(call)));
2333
+ return [...siblingExecutions, ...clearMindExecutions];
2231
2334
  }
2232
2335
  async function executeFunctionRound(args) {
2233
2336
  if (args.funcCalls.length === 0) {
@@ -2270,6 +2373,17 @@ async function executeFunctionRound(args) {
2270
2373
  activePromptReplyDirective: args.activePromptReplyDirective,
2271
2374
  });
2272
2375
  throwIfAborted(args.abortSignal, args.dlg);
2376
+ const failedTellaskToolResults = tellaskRound.invalidTellaskCallIds.map((callId) => {
2377
+ const call = args.funcCalls.find((candidate) => candidate.id === callId);
2378
+ if (call === undefined) {
2379
+ throw new Error(`kernel-driver tellask result invariant violation: missing invalid tellask call '${callId}'`);
2380
+ }
2381
+ return {
2382
+ callId,
2383
+ toolName: call.name,
2384
+ outcome: 'failure',
2385
+ };
2386
+ });
2273
2387
  const genericExecutions = await executeFunctionCalls({
2274
2388
  dlg: args.dlg,
2275
2389
  agent: args.agent,
@@ -2277,6 +2391,7 @@ async function executeFunctionRound(args) {
2277
2391
  funcCalls: tellaskRound.normalCalls,
2278
2392
  abortSignal: args.abortSignal,
2279
2393
  contextHealthForToolResultVisibility: args.contextHealthForToolResultVisibility,
2394
+ failedPriorToolResults: failedTellaskToolResults,
2280
2395
  });
2281
2396
  const genericExecutionByOriginalCall = new Map(genericExecutions.map((execution) => [execution.originalFunc, execution]));
2282
2397
  const funcToolByName = new Map(args.agentTools
@@ -2602,8 +2717,41 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
2602
2717
  for (;;) {
2603
2718
  genIterNo += 1;
2604
2719
  throwIfAborted(abortSignal, dlg);
2605
- const activeFbrState = await loadDialogFbrState(dlg);
2720
+ let activeFbrState = await loadDialogFbrState(dlg);
2606
2721
  if (isFbrSideDialog(dlg)) {
2722
+ const fbrContextHealthLevel = (0, context_health_1.getContextHealthRemediationLevel)(dlg.getLastContextHealth());
2723
+ if (activeFbrState !== undefined && fbrContextHealthLevel === 'critical') {
2724
+ log_1.log.warn('kernel-driver ending FBR with critical context fixed conclusion', undefined, {
2725
+ rootId: dlg.id.rootId,
2726
+ selfId: dlg.id.selfId,
2727
+ course: dlg.currentCourse,
2728
+ phase: activeFbrState.phase,
2729
+ iteration: activeFbrState.iteration,
2730
+ });
2731
+ fbrConclusion = {
2732
+ responseText: (0, fbr_1.buildProgrammaticFbrContextCriticalContent)({
2733
+ language: (0, work_language_1.getWorkLanguage)(),
2734
+ }),
2735
+ responseGenseq: resolveProgrammaticFbrConclusionGenseq({
2736
+ dlg,
2737
+ lastAssistantSayingGenseq,
2738
+ lastFunctionCallGenseq,
2739
+ }),
2740
+ replyResolutionCallId: `fbr-context-critical-${(0, id_1.generateShortId)()}`,
2741
+ };
2742
+ await persistDialogFbrState(dlg, undefined);
2743
+ dlg.setFbrConclusionToolsEnabled(false);
2744
+ finalDisplayState = await (0, dialog_display_state_1.computeIdleDisplayState)(dlg);
2745
+ break driveCoreLoop;
2746
+ }
2747
+ if (activeFbrState !== undefined &&
2748
+ fbrContextHealthLevel === 'caution' &&
2749
+ !(0, fbr_1.isFbrContextCautionFinalizationState)(activeFbrState) &&
2750
+ (pendingPrompt === undefined || pendingPrompt.origin === 'runtime')) {
2751
+ activeFbrState = (0, fbr_1.forceFbrContextCautionFinalizationState)(activeFbrState);
2752
+ await persistDialogFbrState(dlg, activeFbrState);
2753
+ pendingPrompt = buildKernelDriverFbrPrompt(dlg, activeFbrState);
2754
+ }
2607
2755
  dlg.setFbrConclusionToolsEnabled(activeFbrState !== undefined && (0, fbr_1.isFbrFinalizationState)(activeFbrState));
2608
2756
  if (pendingPrompt === undefined &&
2609
2757
  activeFbrState &&
@@ -2786,7 +2934,10 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
2786
2934
  let currentRuntimeGuideMsg;
2787
2935
  const currentPromptFromFbrState = currentPrompt !== undefined &&
2788
2936
  currentFbrState !== undefined &&
2789
- currentFbrState.promptDelivered !== true;
2937
+ currentFbrState.promptDelivered !== true &&
2938
+ isFbrSideDialog(dlg) &&
2939
+ currentPrompt.origin === 'runtime' &&
2940
+ currentPrompt.content === buildKernelDriverFbrPrompt(dlg, currentFbrState).content;
2790
2941
  pendingPrompt = undefined;
2791
2942
  if (currentPrompt) {
2792
2943
  const diligenceSkipReason = await shouldSkipDiligencePromptBeforeGeneration({
@@ -3780,10 +3931,11 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
3780
3931
  language: (0, work_language_1.getWorkLanguage)(),
3781
3932
  finalizationAttempts: persistedFbrState.effort,
3782
3933
  }),
3783
- responseGenseq: lastAssistantSayingGenseq ??
3784
- lastFunctionCallGenseq ??
3785
- dlg.activeGenSeqOrUndefined ??
3786
- 1,
3934
+ responseGenseq: resolveProgrammaticFbrConclusionGenseq({
3935
+ dlg,
3936
+ lastAssistantSayingGenseq,
3937
+ lastFunctionCallGenseq,
3938
+ }),
3787
3939
  replyResolutionCallId: `fbr-conclusion-${(0, id_1.generateShortId)()}`,
3788
3940
  };
3789
3941
  if (!isFbrSideDialog(dlg)) {
@@ -32,6 +32,13 @@ export declare function buildFbrFinalizationPrompt(args: {
32
32
  total: number;
33
33
  language: LanguageCode;
34
34
  }): string;
35
+ export declare function buildFbrContextCautionFinalizationPrompt(args: {
36
+ attempt: number;
37
+ language: LanguageCode;
38
+ }): string;
39
+ export declare function buildProgrammaticFbrContextCriticalContent(args: {
40
+ language: LanguageCode;
41
+ }): string;
35
42
  export declare function buildProgrammaticFbrUnreasonableSituationContent(args: {
36
43
  language: LanguageCode;
37
44
  finalizationAttempts: number;
@@ -40,6 +47,8 @@ export declare function inspectFbrConclusionAttempt(messages: readonly ChatMessa
40
47
  export declare function createInitialFbrState(effort: number): DialogFbrState;
41
48
  export declare function markFbrPromptDelivered(state: DialogFbrState): DialogFbrState;
42
49
  export declare function isFbrFinalizationState(state: DialogFbrState): boolean;
50
+ export declare function isFbrContextCautionFinalizationState(state: DialogFbrState): boolean;
51
+ export declare function forceFbrContextCautionFinalizationState(state: DialogFbrState): DialogFbrState;
43
52
  export declare function advanceFbrState(state: DialogFbrState): DialogFbrState | undefined;
44
53
  export declare function buildFbrPromptForState(args: {
45
54
  state: DialogFbrState;