dominds 1.27.2 → 1.27.4

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 (47) hide show
  1. package/dist/apps/runtime.js +3 -1
  2. package/dist/dialog-fork.js +2 -1
  3. package/dist/dialog-global-registry.d.ts +11 -1
  4. package/dist/dialog-global-registry.js +45 -0
  5. package/dist/dialog.d.ts +14 -5
  6. package/dist/dialog.js +114 -21
  7. package/dist/docs/daemon-cmd-runner.md +5 -0
  8. package/dist/docs/daemon-cmd-runner.zh.md +5 -0
  9. package/dist/llm/kernel-driver/drive.js +228 -49
  10. package/dist/llm/kernel-driver/fbr.d.ts +9 -0
  11. package/dist/llm/kernel-driver/fbr.js +186 -59
  12. package/dist/mcp/supervisor.js +4 -1
  13. package/dist/minds/load.js +1 -0
  14. package/dist/minds/system-prompt-parts.js +30 -30
  15. package/dist/persistence.js +83 -17
  16. package/dist/priming.js +2 -3
  17. package/dist/runtime/driver-messages.d.ts +9 -0
  18. package/dist/runtime/driver-messages.js +103 -33
  19. package/dist/runtime/shared-reminder-update-impact.d.ts +20 -0
  20. package/dist/runtime/shared-reminder-update-impact.js +110 -0
  21. package/dist/shared-reminders.js +2 -2
  22. package/dist/tool-availability.js +1 -0
  23. package/dist/tool.d.ts +7 -4
  24. package/dist/tool.js +10 -4
  25. package/dist/tools/app-reminders.js +4 -3
  26. package/dist/tools/builtins.js +2 -0
  27. package/dist/tools/cmd-runner-protocol.d.ts +6 -0
  28. package/dist/tools/cmd-runner-protocol.js +57 -2
  29. package/dist/tools/cmd-runner.js +83 -2
  30. package/dist/tools/ctrl.d.ts +2 -0
  31. package/dist/tools/ctrl.js +183 -6
  32. package/dist/tools/os.js +115 -14
  33. package/dist/tools/pending-tellask-reminder.js +1 -0
  34. package/dist/tools/process-kill.js +49 -0
  35. package/dist/tools/prompts/control/en/errors.md +1 -1
  36. package/dist/tools/prompts/control/en/index.md +4 -4
  37. package/dist/tools/prompts/control/en/principles.md +22 -21
  38. package/dist/tools/prompts/control/en/scenarios.md +10 -3
  39. package/dist/tools/prompts/control/en/tools.md +28 -5
  40. package/dist/tools/prompts/control/zh/errors.md +1 -1
  41. package/dist/tools/prompts/control/zh/index.md +4 -4
  42. package/dist/tools/prompts/control/zh/principles.md +21 -20
  43. package/dist/tools/prompts/control/zh/scenarios.md +10 -3
  44. package/dist/tools/prompts/control/zh/tools.md +28 -5
  45. package/dist/tools/prompts/os/en/tools.md +2 -0
  46. package/dist/tools/prompts/os/zh/tools.md +2 -0
  47. package/package.json +4 -4
@@ -13,6 +13,7 @@
13
13
  * - add_reminder: Add a reminder
14
14
  * - delete_reminder: Delete a reminder by id
15
15
  * - update_reminder: Update reminder content
16
+ * - migrate_reminder: Move a visible shared reminder back into the current dialog
16
17
  * - clear_mind: Start a new course, optionally add a reminder
17
18
  * - do_mind: Main Dialog only; create a new `.tsk/` Taskdoc section without starting a new course
18
19
  * - change_mind: Main Dialog only; update a `.tsk/` Taskdoc section without starting a new course
@@ -63,12 +64,13 @@ var __importStar = (this && this.__importStar) || (function () {
63
64
  };
64
65
  })();
65
66
  Object.defineProperty(exports, "__esModule", { value: true });
66
- exports.recallTaskdocTool = exports.mindMoreTool = exports.neverMindTool = exports.doMindTool = exports.changeMindTool = exports.clearMindTool = exports.updateReminderTool = exports.addReminderTool = exports.deleteReminderTool = void 0;
67
+ exports.recallTaskdocTool = exports.mindMoreTool = exports.neverMindTool = exports.doMindTool = exports.changeMindTool = exports.clearMindTool = exports.migrateReminderTool = exports.updateReminderTool = exports.addReminderTool = exports.deleteReminderTool = void 0;
67
68
  const fs = __importStar(require("fs"));
68
69
  const path = __importStar(require("path"));
69
70
  const dialog_1 = require("../dialog");
70
71
  const rtws_1 = require("../rtws");
71
72
  const driver_messages_1 = require("../runtime/driver-messages");
73
+ const shared_reminder_update_impact_1 = require("../runtime/shared-reminder-update-impact");
72
74
  const tool_result_messages_1 = require("../runtime/tool-result-messages");
73
75
  const work_language_1 = require("../runtime/work-language");
74
76
  const shared_reminders_1 = require("../shared-reminders");
@@ -276,6 +278,87 @@ function replaceReminderContent(reminder, content, meta, renderMode) {
276
278
  renderMode: renderMode ?? reminder.renderMode,
277
279
  });
278
280
  }
281
+ function resolveSharedReminderUpdateImpactScope(target) {
282
+ if (target.source !== 'runtime') {
283
+ return undefined;
284
+ }
285
+ if (target.target.kind === 'task') {
286
+ return 'task';
287
+ }
288
+ return target.reminder.scope === 'runtime' ? 'runtime' : 'agent';
289
+ }
290
+ function appendSharedReminderUpdateImpactToToolResult(output, language, reminderId, dispatch) {
291
+ if (dispatch === undefined) {
292
+ return output;
293
+ }
294
+ const notice = (0, driver_messages_1.formatSharedReminderUpdateImpactNotice)(language, {
295
+ reminderId,
296
+ scope: dispatch.scope,
297
+ audience: 'updater',
298
+ });
299
+ const dispatchLine = language === 'zh'
300
+ ? `已向 ${dispatch.dispatchedDialogCount}/${dispatch.peerDialogCount} 个受影响的并行对话派发提醒。`
301
+ : `Dispatched notices to ${dispatch.dispatchedDialogCount}/${dispatch.peerDialogCount} affected parallel dialog(s).`;
302
+ return {
303
+ ...output,
304
+ content: `${output.content}\n\n${notice}\n${dispatchLine}`,
305
+ };
306
+ }
307
+ function appendSharedReminderMigrationImpactToToolResult(output, language, dispatch) {
308
+ if (dispatch === undefined) {
309
+ return output;
310
+ }
311
+ const dispatchLine = language === 'zh'
312
+ ? `已向 ${dispatch.dispatchedDialogCount}/${dispatch.peerDialogCount} 个受影响的并行对话派发撤下提醒。`
313
+ : `Dispatched withdrawal notices to ${dispatch.dispatchedDialogCount}/${dispatch.peerDialogCount} affected parallel dialog(s).`;
314
+ return {
315
+ ...output,
316
+ content: `${output.content}\n${dispatchLine}`,
317
+ };
318
+ }
319
+ async function formatUpdateReminderSuccessResult(args) {
320
+ const scope = resolveSharedReminderUpdateImpactScope(args.target);
321
+ const dispatch = scope === undefined
322
+ ? undefined
323
+ : await (0, shared_reminder_update_impact_1.dispatchSharedReminderUpdateImpact)({
324
+ updater: args.dlg,
325
+ reminderId: args.target.reminder.id,
326
+ scope,
327
+ language: args.language,
328
+ });
329
+ return appendSharedReminderUpdateImpactToToolResult((0, tool_result_messages_1.formatToolActionResult)(args.language, 'updated'), args.language, args.target.reminder.id, dispatch);
330
+ }
331
+ async function migrateSharedReminderTargetToDialog(args) {
332
+ const reminderId = args.target.reminder.id;
333
+ const existingDialogIndex = findReminderIndexById('dialog', args.dlg.reminders, reminderId);
334
+ if (existingDialogIndex !== null) {
335
+ throw new Error(`Duplicate visible reminder_id before migration: ${reminderId}`);
336
+ }
337
+ let migratedReminder;
338
+ await (0, shared_reminders_1.mutateSharedReminders)(args.target.target, (sharedReminders) => {
339
+ const index = findReminderIndexById('shared', sharedReminders, reminderId);
340
+ if (index === null)
341
+ return;
342
+ migratedReminder = sharedReminders[index];
343
+ sharedReminders.splice(index, 1);
344
+ });
345
+ if (migratedReminder === undefined) {
346
+ return false;
347
+ }
348
+ args.dlg.reminders.push((0, tool_1.materializeReminder)({
349
+ id: migratedReminder.id,
350
+ content: migratedReminder.content,
351
+ owner: migratedReminder.owner,
352
+ meta: migratedReminder.meta,
353
+ echoback: migratedReminder.echoback,
354
+ scope: 'dialog',
355
+ createdAt: migratedReminder.createdAt,
356
+ priority: migratedReminder.priority,
357
+ renderMode: migratedReminder.renderMode,
358
+ }));
359
+ args.dlg.touchReminders();
360
+ return true;
361
+ }
279
362
  async function deleteResolvedReminderTarget(dlg, target) {
280
363
  switch (target.source) {
281
364
  case 'dialog': {
@@ -325,6 +408,9 @@ function getCtrlMessages(language) {
325
408
  invalidFormatAdd: '参数格式不对。用法:add_reminder({ content: string, scope?: "dialog" | "task" | "agent" })(省略 scope 表示 task)',
326
409
  reminderContentEmpty: '提醒内容不能为空',
327
410
  invalidFormatUpdate: '参数格式不对。用法:update_reminder({ reminder_id: string, content: string })',
411
+ invalidFormatMigrate: '参数格式不对。用法:migrate_reminder({ reminder_id: string, scope: "dialog" })',
412
+ reminderAlreadyDialogScope: (reminderId) => `reminder_id=${reminderId} 已经是当前对话范围提醒项,不需要迁移。`,
413
+ reminderMigrateManagedBlocked: (managerTool) => `错误:该提醒项由工具 ${managerTool} 管理,不能用 migrate_reminder 迁移;请使用 ${managerTool} 更新。`,
328
414
  invalidFormatDoMind: '参数格式不对。用法:do_mind({ selector: string, category?: string, content: string })',
329
415
  invalidFormatChangeMind: '参数格式不对。用法:change_mind({ selector: string, category?: string, content: string, previous_content_hash: string })',
330
416
  tooManyArgsChangeMind: '参数格式不对。用法:change_mind({ selector: string, category?: string, content: string, previous_content_hash: string })',
@@ -372,6 +458,9 @@ function getCtrlMessages(language) {
372
458
  invalidFormatAdd: 'Error: Invalid args. Use: add_reminder({ content: string, scope?: "dialog" | "task" | "agent" }) (omitting scope means task).',
373
459
  reminderContentEmpty: 'Error: Reminder content cannot be empty',
374
460
  invalidFormatUpdate: 'Error: Invalid args. Use: update_reminder({ reminder_id: string, content: string })',
461
+ invalidFormatMigrate: 'Error: Invalid args. Use: migrate_reminder({ reminder_id: string, scope: "dialog" })',
462
+ reminderAlreadyDialogScope: (reminderId) => `reminder_id=${reminderId} is already dialog-scope in the current dialog; no migration is needed.`,
463
+ reminderMigrateManagedBlocked: (managerTool) => `Error: This reminder is managed by tool ${managerTool}. Do not migrate it via migrate_reminder; use ${managerTool} instead.`,
375
464
  invalidFormatDoMind: 'Error: Invalid args. Use: do_mind({ selector: string, category?: string, content: string })',
376
465
  invalidFormatChangeMind: 'Error: Invalid args. Use: change_mind({ selector: string, category?: string, content: string, previous_content_hash: string })',
377
466
  tooManyArgsChangeMind: 'Error: Invalid args. Use: change_mind({ selector: string, category?: string, content: string, previous_content_hash: string })',
@@ -456,7 +545,7 @@ exports.addReminderTool = {
456
545
  description: 'Add a manually maintained reminder for current work. Scope defaults to task so the reminder survives continuing the same Taskdoc in another dialog; dialog is only for truly dialog-local notes; agent is visible to this agent across dialogs and should be reserved for urgent short-lived global cues. Do not manually record runtime-maintained environment state such as background process status or in-flight background asks.',
457
546
  descriptionI18n: {
458
547
  en: 'Add a manually maintained reminder for current work. Scope defaults to task so the reminder survives continuing the same Taskdoc in another dialog; dialog is only for truly dialog-local notes; agent is visible to this agent across dialogs and should be reserved for urgent short-lived global cues. Do not manually record runtime-maintained environment state such as background process status or in-flight background asks.',
459
- zh: '添加手工维护的手头工作提醒。scope 默认 task,以便同一差遣牒任务换新对话继续时仍可见;dialog 只用于真正对话局部的事项;agent 会在本智能体所有对话中可见,仅用于紧急、短期、全局刺眼提醒。不要手工记录后台进程状态、后台进行中诉请等 runtime 会自动维护的环境状态。',
548
+ zh: '添加手工维护的手头工作提醒。scope 默认 task,以便同一差遣牒任务换新对话继续时仍可见;dialog 只用于真正对话局部的事项;agent 会在本智能体后续对话中继续可见,仅用于紧急、短期、全局刺眼提醒。不要手工记录后台进程状态、后台进行中诉请等 runtime 会自动维护的环境状态。',
460
549
  },
461
550
  parameters: {
462
551
  type: 'object',
@@ -592,12 +681,20 @@ exports.updateReminderTool = {
592
681
  const updated = await updateResolvedReminderTarget(dlg, resolved.target, reminderContent, stripResult.nextMeta, reminderRenderMode);
593
682
  if (!updated)
594
683
  return (0, tool_1.toolFailure)(t.reminderTargetChanged);
595
- return (0, tool_result_messages_1.formatToolActionResult)(language, 'updated');
684
+ return await formatUpdateReminderSuccessResult({
685
+ dlg,
686
+ target: resolved.target,
687
+ language,
688
+ });
596
689
  }
597
690
  const updated = await updateResolvedReminderTarget(dlg, resolved.target, reminderContent, undefined, reminderRenderMode);
598
691
  if (!updated)
599
692
  return (0, tool_1.toolFailure)(t.reminderTargetChanged);
600
- return (0, tool_result_messages_1.formatToolActionResult)(language, 'updated');
693
+ return await formatUpdateReminderSuccessResult({
694
+ dlg,
695
+ target: resolved.target,
696
+ language,
697
+ });
601
698
  }
602
699
  const reminderMeta = buildContinuationPackageReminderMeta({
603
700
  existingMeta: reminder?.meta,
@@ -607,7 +704,84 @@ exports.updateReminderTool = {
607
704
  const updated = await updateResolvedReminderTarget(dlg, resolved.target, reminderContent, reminderMeta, reminderRenderMode);
608
705
  if (!updated)
609
706
  return (0, tool_1.toolFailure)(t.reminderTargetChanged);
610
- return (0, tool_result_messages_1.formatToolActionResult)(language, 'updated');
707
+ return await formatUpdateReminderSuccessResult({
708
+ dlg,
709
+ target: resolved.target,
710
+ language,
711
+ });
712
+ },
713
+ };
714
+ exports.migrateReminderTool = {
715
+ type: 'func',
716
+ name: 'migrate_reminder',
717
+ description: 'Move a visible shared reminder back into the current dialog scope. Use this after a task/agent shared reminder update turns out to belong only to the updater dialog, so Dominds withdraws it from affected parallel dialogs.',
718
+ descriptionI18n: {
719
+ en: 'Move a visible shared reminder back into the current dialog scope. Use this after a task/agent shared reminder update turns out to belong only to the updater dialog, so Dominds withdraws it from affected parallel dialogs.',
720
+ zh: '把当前可见的共享提醒项迁回当前对话范围。用于 task/agent 共享提醒更新后发现内容只属于更新者对话时,将它从受影响并行对话中撤下。',
721
+ },
722
+ parameters: {
723
+ type: 'object',
724
+ additionalProperties: false,
725
+ required: ['reminder_id', 'scope'],
726
+ properties: {
727
+ reminder_id: { type: 'string', description: 'Stable reminder id.' },
728
+ scope: {
729
+ type: 'string',
730
+ enum: ['dialog'],
731
+ description: 'Target scope. Currently only dialog is supported.',
732
+ },
733
+ },
734
+ },
735
+ argsValidation: 'dominds',
736
+ async call(dlg, _caller, args) {
737
+ const language = (0, work_language_1.getWorkLanguage)();
738
+ const t = getCtrlMessages(language);
739
+ if (args['scope'] !== 'dialog') {
740
+ return (0, tool_1.toolFailure)(t.invalidFormatMigrate);
741
+ }
742
+ const resolved = await resolveReminderTarget(dlg, args['reminder_id']);
743
+ if (!resolved.ok) {
744
+ const reminderId = resolved.reminderId.trim();
745
+ if (reminderId === '')
746
+ return (0, tool_1.toolFailure)(t.invalidFormatMigrate);
747
+ return (0, tool_1.toolFailure)(t.reminderDoesNotExist(reminderId));
748
+ }
749
+ const targetReminder = resolved.target.reminder;
750
+ if (resolved.target.source === 'dialog') {
751
+ return (0, tool_1.toolSuccess)(t.reminderAlreadyDialogScope(targetReminder.id));
752
+ }
753
+ const deleteAltInstruction = getDeleteAltInstruction(targetReminder.meta);
754
+ if (deleteAltInstruction !== undefined) {
755
+ return (0, tool_1.toolFailure)(formatManualDeleteBlockedError(language, deleteAltInstruction));
756
+ }
757
+ const managerTool = getManagerTool(targetReminder.meta);
758
+ if (managerTool !== undefined) {
759
+ return (0, tool_1.toolFailure)(t.reminderMigrateManagedBlocked(managerTool));
760
+ }
761
+ if (targetReminder.owner?.updateReminder !== undefined) {
762
+ return (0, tool_1.toolFailure)(language === 'zh'
763
+ ? '错误:该提醒项由 reminder owner 自动维护,不能用 migrate_reminder 迁移。'
764
+ : 'Error: This reminder is automatically maintained by a reminder owner and cannot be migrated via migrate_reminder.');
765
+ }
766
+ const scope = resolveSharedReminderUpdateImpactScope(resolved.target);
767
+ const migrated = await migrateSharedReminderTargetToDialog({
768
+ dlg,
769
+ target: resolved.target,
770
+ });
771
+ if (!migrated)
772
+ return (0, tool_1.toolFailure)(t.reminderTargetChanged);
773
+ const baseOutput = (0, tool_1.toolSuccess)(language === 'zh'
774
+ ? `已迁移:reminder_id=${targetReminder.id} 已从共享范围撤下,并保留为当前对话范围提醒项。`
775
+ : `Migrated: reminder_id=${targetReminder.id} has been withdrawn from shared scope and kept as a current-dialog reminder.`);
776
+ const dispatch = scope === undefined
777
+ ? undefined
778
+ : await (0, shared_reminder_update_impact_1.dispatchSharedReminderMigrationImpact)({
779
+ updater: dlg,
780
+ reminderId: targetReminder.id,
781
+ scope,
782
+ language,
783
+ });
784
+ return appendSharedReminderMigrationImpactToToolResult(baseOutput, language, dispatch);
611
785
  },
612
786
  };
613
787
  exports.clearMindTool = {
@@ -641,7 +815,10 @@ exports.clearMindTool = {
641
815
  createdBy: 'clear_mind',
642
816
  contextHealthLevel,
643
817
  });
644
- dlg.addReminder(reminderContent, undefined, continuationMeta);
818
+ dlg.addReminder(reminderContent, undefined, continuationMeta, undefined, {
819
+ scope: 'dialog',
820
+ renderMode: 'markdown',
821
+ });
645
822
  }
646
823
  await dlg.startNewCourse(t.clearedCoursePrompt(dlg.currentCourse + 1));
647
824
  // Context health snapshot is inherently tied to the previous prompt/context.
package/dist/tools/os.js CHANGED
@@ -24,6 +24,7 @@ const fs_1 = __importDefault(require("fs"));
24
24
  const module_1 = require("module");
25
25
  const net_1 = __importDefault(require("net"));
26
26
  const path_1 = __importDefault(require("path"));
27
+ const url_1 = require("url");
27
28
  const util_1 = require("util");
28
29
  const rtws_1 = require("../rtws");
29
30
  const driver_messages_1 = require("../runtime/driver-messages");
@@ -572,6 +573,8 @@ function getOsToolMessages(language) {
572
573
  daemonOutputHeader: (pid, streamLabel) => `📤 守护进程 ${pid} ${streamLabel} 输出:\n`,
573
574
  noOutput: '(无输出)',
574
575
  scrolledOutNotice: (lines) => `\n\n⚠️ 有 ${lines} 行已滚出可视范围`,
576
+ daemonOutputWaitTimedOut: (pid, streamLabel, timeoutMs) => `⏱️ 等待守护进程 ${pid} 的 ${streamLabel} 新输出已超时(timeout_ms=${timeoutMs})。以下是当前快照。\n\n`,
577
+ daemonOutputWaitExited: (pid, streamLabel) => `⏹️ 等待守护进程 ${pid} 的 ${streamLabel} 新输出期间进程已退出;未观察到新的所选流输出。以下是最终快照。\n\n`,
575
578
  };
576
579
  }
577
580
  return {
@@ -587,6 +590,8 @@ function getOsToolMessages(language) {
587
590
  daemonOutputHeader: (pid, streamLabel) => `📤 Daemon ${pid} ${streamLabel} output:\n`,
588
591
  noOutput: '(no output)',
589
592
  scrolledOutNotice: (lines) => `\n\n⚠️ ${lines} lines have scrolled out of view`,
593
+ daemonOutputWaitTimedOut: (pid, streamLabel, timeoutMs) => `⏱️ Timed out waiting for new ${streamLabel} output from daemon ${pid} (timeout_ms=${timeoutMs}). Current snapshot follows.\n\n`,
594
+ daemonOutputWaitExited: (pid, streamLabel) => `⏹️ Daemon ${pid} exited while waiting for new ${streamLabel} output; no new requested-stream output was observed. Final snapshot follows.\n\n`,
590
595
  };
591
596
  }
592
597
  function disconnectRunnerProcess(runnerProcess) {
@@ -818,12 +823,38 @@ function parseGetDaemonOutputArgs(args) {
818
823
  if (stderrRaw !== undefined && typeof stderrRaw !== 'boolean') {
819
824
  throw new Error('get_daemon_output.stderr must be a boolean if provided');
820
825
  }
826
+ const waitForNewOutputRaw = args.wait_for_new_output;
827
+ if (waitForNewOutputRaw !== undefined && typeof waitForNewOutputRaw !== 'boolean') {
828
+ throw new Error('get_daemon_output.wait_for_new_output must be a boolean if provided');
829
+ }
830
+ const timeoutMsRaw = args.timeout_ms;
831
+ if (timeoutMsRaw !== undefined && typeof timeoutMsRaw !== 'number') {
832
+ throw new Error('get_daemon_output.timeout_ms must be a number if provided');
833
+ }
834
+ const timeoutMs = timeoutMsRaw === undefined
835
+ ? undefined
836
+ : Number.isInteger(timeoutMsRaw) &&
837
+ timeoutMsRaw >= 0 &&
838
+ timeoutMsRaw <= cmd_runner_protocol_1.MAX_CMD_RUNNER_OUTPUT_WAIT_TIMEOUT_MS
839
+ ? timeoutMsRaw
840
+ : (() => {
841
+ throw new Error(`get_daemon_output.timeout_ms must be a non-negative integer <= ${String(cmd_runner_protocol_1.MAX_CMD_RUNNER_OUTPUT_WAIT_TIMEOUT_MS)}`);
842
+ })();
821
843
  const stdout = stdoutRaw ?? true;
822
844
  const stderr = stderrRaw ?? true;
823
845
  if (!stdout && !stderr) {
824
846
  throw new Error('get_daemon_output requires at least one of stdout/stderr to be true');
825
847
  }
826
- return { pid, stdout, stderr };
848
+ if (waitForNewOutputRaw === false && timeoutMs !== undefined) {
849
+ throw new Error('get_daemon_output.timeout_ms cannot be provided when wait_for_new_output is false');
850
+ }
851
+ return {
852
+ pid,
853
+ stdout,
854
+ stderr,
855
+ waitForNewOutput: waitForNewOutputRaw ?? timeoutMs !== undefined,
856
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
857
+ };
827
858
  }
828
859
  function getWindowsShellLabel(shell) {
829
860
  if (typeof shell !== 'string') {
@@ -945,8 +976,8 @@ function resolveCmdRunnerEntrypointAbs() {
945
976
  }
946
977
  const tsCandidate = path_1.default.resolve(__dirname, 'cmd-runner.ts');
947
978
  if (fs_1.default.existsSync(tsCandidate)) {
948
- const tsxLoaderAbs = requireFn.resolve('tsx');
949
- return { ok: true, scriptAbs: tsCandidate, execArgv: ['--import', tsxLoaderAbs] };
979
+ const tsxLoaderUrl = (0, url_1.pathToFileURL)(requireFn.resolve('tsx')).href;
980
+ return { ok: true, scriptAbs: tsCandidate, execArgv: ['--import', tsxLoaderUrl] };
950
981
  }
951
982
  return {
952
983
  ok: false,
@@ -991,21 +1022,30 @@ async function callRunner(endpoint, request, timeoutMs = 5_000) {
991
1022
  if (settled)
992
1023
  return;
993
1024
  settled = true;
994
- clearTimeout(timeoutHandle);
1025
+ if (timeoutHandle !== undefined) {
1026
+ clearTimeout(timeoutHandle);
1027
+ }
995
1028
  socket.destroy();
996
1029
  fn();
997
1030
  };
998
- const timeoutHandle = setTimeout(() => {
999
- finalize(() => {
1000
- reject(new Error(`cmd_runner request timed out for endpoint ${endpoint}`));
1001
- });
1002
- }, timeoutMs);
1031
+ const timeoutHandle = timeoutMs === null
1032
+ ? undefined
1033
+ : setTimeout(() => {
1034
+ finalize(() => {
1035
+ reject(new Error(`cmd_runner request timed out for endpoint ${endpoint}`));
1036
+ });
1037
+ }, timeoutMs);
1003
1038
  socket.setEncoding('utf8');
1004
1039
  socket.once('error', (error) => {
1005
1040
  finalize(() => {
1006
1041
  reject(error);
1007
1042
  });
1008
1043
  });
1044
+ socket.once('close', () => {
1045
+ finalize(() => {
1046
+ reject(new Error(`cmd_runner connection closed before response for endpoint ${endpoint}`));
1047
+ });
1048
+ });
1009
1049
  socket.on('data', (chunk) => {
1010
1050
  buffer += chunk;
1011
1051
  const newlineIndex = buffer.indexOf('\n');
@@ -1101,6 +1141,45 @@ async function resolveDaemonFromMeta(meta) {
1101
1141
  };
1102
1142
  }
1103
1143
  }
1144
+ function getRunnerRequestTimeoutForDaemonOutput(args) {
1145
+ if (!args.waitForNewOutput) {
1146
+ return undefined;
1147
+ }
1148
+ if (args.timeoutMs !== undefined) {
1149
+ return Math.max(5_000, args.timeoutMs + 5_000);
1150
+ }
1151
+ return null;
1152
+ }
1153
+ async function resolveDaemonOutputFromMeta(meta, args) {
1154
+ if (meta.runnerEndpoint !== undefined && meta.runnerEndpoint.trim() !== '') {
1155
+ try {
1156
+ const response = await callRunner(meta.runnerEndpoint, {
1157
+ type: 'get_output',
1158
+ stdout: args.stdout,
1159
+ stderr: args.stderr,
1160
+ waitForNewOutput: args.waitForNewOutput,
1161
+ ...(args.timeoutMs === undefined ? {} : { timeoutMs: args.timeoutMs }),
1162
+ }, getRunnerRequestTimeoutForDaemonOutput(args));
1163
+ if (response.ok &&
1164
+ response.type === 'output' &&
1165
+ runnerResponseMatchesReminder(meta, response)) {
1166
+ return {
1167
+ kind: 'live',
1168
+ daemon: buildRunnerBackedDaemon(meta, response),
1169
+ ...(response.waitStatus === undefined ? {} : { waitStatus: response.waitStatus }),
1170
+ };
1171
+ }
1172
+ }
1173
+ catch {
1174
+ // Fall through to stale-or-gone detection.
1175
+ }
1176
+ }
1177
+ const resolved = await resolveDaemonFromMeta(meta);
1178
+ if (resolved.kind !== 'live') {
1179
+ return resolved;
1180
+ }
1181
+ return { kind: 'live', daemon: resolved.daemon };
1182
+ }
1104
1183
  function formatRunnerBackedDaemonStatusDetails(daemon, language) {
1105
1184
  const uptime = Math.floor((Date.now() - daemon.startTime.getTime()) / 1000);
1106
1185
  const status = language === 'zh'
@@ -1248,6 +1327,14 @@ const getDaemonOutputSchema = {
1248
1327
  type: 'boolean',
1249
1328
  description: 'Whether to include stderr output (default: true unless stdout is explicitly set)',
1250
1329
  },
1330
+ wait_for_new_output: {
1331
+ type: 'boolean',
1332
+ description: 'Whether to wait until at least one requested stream receives new output before returning (default: false, or true when timeout_ms is provided)',
1333
+ },
1334
+ timeout_ms: {
1335
+ type: 'integer',
1336
+ description: 'Optional non-negative maximum time in milliseconds to wait for new output, up to 86400000 (24h); providing this implies wait_for_new_output=true unless wait_for_new_output is explicitly false, which is rejected',
1337
+ },
1251
1338
  },
1252
1339
  required: ['pid'],
1253
1340
  additionalProperties: false,
@@ -2788,26 +2875,33 @@ exports.stopDaemonTool = {
2788
2875
  }
2789
2876
  },
2790
2877
  };
2878
+ function formatRequestedDaemonOutputStreams(stdout, stderr) {
2879
+ if (stdout && stderr) {
2880
+ return 'stdout/stderr';
2881
+ }
2882
+ return stdout ? 'stdout' : 'stderr';
2883
+ }
2791
2884
  // Get daemon output tool implementation
2792
2885
  exports.getDaemonOutputTool = {
2793
2886
  type: 'func',
2794
2887
  name: 'get_daemon_output',
2795
- description: 'Retrieve captured stdout/stderr output from a tracked daemon process by PID. By default both streams are returned together; you may disable either stream explicitly. Returns (no output) if a requested stream has not produced output yet.',
2888
+ description: 'Retrieve captured stdout/stderr output from a tracked daemon process by PID. By default both streams are returned together; you may disable either stream explicitly. Set wait_for_new_output=true to wait until a requested stream receives new output before returning; timeout_ms optionally bounds that wait. Returns (no output) if a requested stream has not produced output yet.',
2796
2889
  descriptionI18n: {
2797
- en: 'Retrieve captured stdout/stderr output from a tracked daemon process by PID. By default both streams are returned together; you may disable either stream explicitly. Returns (no output) if a requested stream has not produced output yet.',
2798
- zh: '根据 PID 获取已追踪守护进程的 stdout/stderr 输出。默认会同时返回两个流,也可显式关闭其中一个;若所请求的流尚无输出,则返回 (no output)。',
2890
+ en: 'Retrieve captured stdout/stderr output from a tracked daemon process by PID. By default both streams are returned together; you may disable either stream explicitly. Set wait_for_new_output=true to wait until a requested stream receives new output before returning; timeout_ms optionally bounds that wait. Returns (no output) if a requested stream has not produced output yet.',
2891
+ zh: '根据 PID 获取已追踪守护进程的 stdout/stderr 输出。默认会同时返回两个流,也可显式关闭其中一个;设置 wait_for_new_output=true 时会等到所请求流出现新输出后再返回,timeout_ms 可选用于限制等待毫秒数;若所请求的流尚无输出,则返回 (no output)。',
2799
2892
  },
2800
2893
  parameters: getDaemonOutputSchema,
2801
2894
  async call(dlg, caller, args) {
2802
2895
  const language = (0, work_language_1.getWorkLanguage)();
2803
2896
  const t = getOsToolMessages(language);
2804
- const { pid, stdout, stderr } = parseGetDaemonOutputArgs(args);
2897
+ const parsedArgs = parseGetDaemonOutputArgs(args);
2898
+ const { pid, stdout, stderr } = parsedArgs;
2805
2899
  const reminders = await (0, shared_reminders_1.loadSharedReminders)({ kind: 'agent', agentId: dlg.agentId });
2806
2900
  const reminder = reminders.find((candidate) => isShellCmdReminder(candidate) && candidate.meta.pid === pid);
2807
2901
  if (!reminder || !isShellCmdReminder(reminder)) {
2808
2902
  return (0, tool_1.toolFailure)(t.noDaemonFound(pid));
2809
2903
  }
2810
- const resolved = await resolveDaemonFromMeta(reminder.meta);
2904
+ const resolved = await resolveDaemonOutputFromMeta(reminder.meta, parsedArgs);
2811
2905
  if (resolved.kind === 'gone') {
2812
2906
  await removeDaemonRemindersForPid(dlg, pid);
2813
2907
  return (0, tool_1.toolFailure)(t.noDaemonFound(pid));
@@ -2819,6 +2913,13 @@ exports.getDaemonOutputTool = {
2819
2913
  let result = '';
2820
2914
  const fenceConsole = '```console';
2821
2915
  const fenceEnd = '```';
2916
+ const requestedStreams = formatRequestedDaemonOutputStreams(stdout, stderr);
2917
+ if (resolved.waitStatus === 'timeout') {
2918
+ result += t.daemonOutputWaitTimedOut(pid, requestedStreams, parsedArgs.timeoutMs ?? 0);
2919
+ }
2920
+ else if (resolved.waitStatus === 'exited') {
2921
+ result += t.daemonOutputWaitExited(pid, requestedStreams);
2922
+ }
2822
2923
  if (stdout) {
2823
2924
  const stdoutContent = (0, output_limit_1.truncateToolOutputText)(daemon.stdoutContent, {
2824
2925
  toolName: 'get_daemon_output_stdout',
@@ -193,6 +193,7 @@ async function syncPendingTellaskReminderState(dlg) {
193
193
  return false;
194
194
  }
195
195
  dlg.addReminder(content, exports.pendingTellaskReminderOwner, nextMeta, 0, {
196
+ scope: 'dialog',
196
197
  renderMode: 'markdown',
197
198
  });
198
199
  return true;
@@ -30,10 +30,59 @@ async function bestEffortTaskkill(pid, force) {
30
30
  // Best effort only.
31
31
  }
32
32
  }
33
+ async function bestEffortListWindowsDescendantPids(pid) {
34
+ assertValidPid(pid);
35
+ try {
36
+ const command = `
37
+ $processes = Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId
38
+ $frontier = @(${String(pid)})
39
+ $result = @()
40
+ while ($frontier.Count -gt 0) {
41
+ $next = @()
42
+ foreach ($parentPid in $frontier) {
43
+ foreach ($child in $processes | Where-Object { $_.ParentProcessId -eq $parentPid }) {
44
+ $childPid = [int]$child.ProcessId
45
+ $result += $childPid
46
+ $next += $childPid
47
+ }
48
+ }
49
+ $frontier = $next
50
+ }
51
+ $result | ForEach-Object { [Console]::Out.WriteLine($_) }
52
+ `;
53
+ const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-Command', command], {
54
+ windowsHide: true,
55
+ maxBuffer: 1024 * 1024,
56
+ });
57
+ const parsed = [];
58
+ for (const line of stdout.split(/\r?\n/)) {
59
+ const trimmed = line.trim();
60
+ if (trimmed === '') {
61
+ continue;
62
+ }
63
+ const parsedPid = Number(trimmed);
64
+ if (Number.isInteger(parsedPid) && parsedPid > 0) {
65
+ parsed.push(parsedPid);
66
+ }
67
+ }
68
+ return parsed;
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ }
33
74
  async function bestEffortKillWindowsProcessTree(pid) {
34
75
  assertValidPid(pid);
76
+ const descendantPids = await bestEffortListWindowsDescendantPids(pid);
77
+ for (const descendantPid of descendantPids.slice().reverse()) {
78
+ await bestEffortTaskkill(descendantPid, false);
79
+ }
35
80
  await bestEffortTaskkill(pid, false);
36
81
  await sleepMs(1_000);
82
+ for (const descendantPid of descendantPids.slice().reverse()) {
83
+ await bestEffortTaskkill(descendantPid, true);
84
+ bestEffortKillPid(descendantPid);
85
+ }
37
86
  await bestEffortTaskkill(pid, true);
38
87
  bestEffortKillPid(pid);
39
88
  }
@@ -73,7 +73,7 @@
73
73
 
74
74
  ### Q: What's the difference between dialog, task, agent reminders, and memory?
75
75
 
76
- A: `dialog` reminders are only for the current dialog's current work. `task` reminders are for current work under the same Taskdoc and are the default for `add_reminder`. `agent` reminders stay visible in all later dialogs you lead, but only for urgent, short-lived, globally visible cues. None of them are long-term knowledge. `personal_memory` is for durable facts and reusable knowledge saved to disk; if the information should synchronize the team's current effective state, key decisions, next step, or still-active blockers, write it to Taskdoc `progress` instead of a reminder.
76
+ A: `dialog` reminders are only for the current dialog's current work. `task` reminders are for current work under the same Taskdoc and are the default for `add_reminder`. `agent` reminders stay visible in later dialogs you lead, but only for urgent, short-lived, globally visible cues. None of them are long-term knowledge. `personal_memory` is for durable facts and reusable knowledge saved to disk; if the information should synchronize the team's current effective state, key decisions, next step, or still-active blockers, write it to Taskdoc `progress` instead of a reminder.
77
77
 
78
78
  ### Q: How do I choose `dialog`, `task`, and `agent`?
79
79
 
@@ -26,7 +26,7 @@
26
26
 
27
27
  control is Dominds' **dialog control toolset** for managing dialog state, reminders, taskdocs, and inter-dialog reply closure semantics:
28
28
 
29
- - **Reminder management**: Three reminder scopes: `dialog` / `task` / `agent`. Default to `task` for current work under the same Taskdoc; use `dialog` only for truly dialog-local notes; use `agent` only for urgent, short-lived, globally visible cues
29
+ - **Reminder management**: Three reminder scopes: `dialog` / `task` / `agent`. Default to `task` for ordinary current work under the same Taskdoc; use `dialog` for truly dialog-local notes, and continuation packages before `clear_mind` must explicitly use `scope=dialog` and state this dialog task goal; use `agent` only for urgent, short-lived, globally visible cues
30
30
  - **Taskdoc operations**: Append to, replace, or delete task contract sections (goals/constraints/progress); within Taskdoc, `progress` is the team-shared, quasi-real-time, scannable task bulletin board
31
31
  - **Context maintenance**: Reduce cognitive load without losing key resume state
32
32
  - **Reply routing**: Separate asking the tellasker back, sending the final reply, and ordinary plain text in Side Dialog / ask-back flows
@@ -56,15 +56,15 @@ Reminders are temporary current-work information for:
56
56
  - Marking pending tasks
57
57
  - Tracking current next steps / blockers
58
58
  - Recording blocking issues
59
- - Holding continuation-package bridge notes before `clear_mind`
59
+ - Holding current-dialog scoped continuation-package bridge notes before `clear_mind`
60
60
 
61
61
  Reminders are not for manually copying environment state automatically maintained by Dominds, such as background process status, in-flight background asks/collaboration, or browser/session attachment state. Dominds-managed reminders, panels, and tool outputs are the authoritative place for that state; manual notes go stale easily and create cognitive noise.
62
62
 
63
63
  Scope rule:
64
64
 
65
- - `dialog`: current-dialog current work
65
+ - `dialog`: current-dialog current work; continuation packages before `clear_mind` must explicitly use this scope and state this dialog task goal
66
66
  - `task`: current work under the current Taskdoc, and the default scope
67
- - `agent`: urgent, short-lived, globally visible cues you should keep seeing in all later dialogs you lead
67
+ - `agent`: urgent, short-lived, globally visible cues you should keep seeing across later dialogs you lead
68
68
 
69
69
  ### Taskdoc
70
70