dominds 1.19.2 → 1.20.1

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 (84) hide show
  1. package/README.md +1 -0
  2. package/dist/access-control.js +2 -2
  3. package/dist/cli/team-definition-audit.d.ts +2 -1
  4. package/dist/cli/team-definition-audit.js +20 -8
  5. package/dist/cli/validate-team-def.js +3 -3
  6. package/dist/dialog.d.ts +6 -1
  7. package/dist/dialog.js +13 -4
  8. package/dist/docs/context-health.md +39 -13
  9. package/dist/docs/context-health.zh.md +14 -7
  10. package/dist/docs/idle-reminder-wake.md +227 -0
  11. package/dist/docs/idle-reminder-wake.zh.md +227 -0
  12. package/dist/docs/mcp-support.md +11 -0
  13. package/dist/docs/mcp-support.zh.md +7 -0
  14. package/dist/docs/team_mgmt-toolset.md +5 -2
  15. package/dist/docs/team_mgmt-toolset.zh.md +4 -2
  16. package/dist/docs/tool-availability-protocol.md +4 -2
  17. package/dist/llm/client.d.ts +2 -0
  18. package/dist/llm/client.js +32 -2
  19. package/dist/llm/defaults.yaml +51 -10
  20. package/dist/llm/gen/codex.js +15 -0
  21. package/dist/llm/kernel-driver/drive.js +6 -0
  22. package/dist/llm/kernel-driver/flow.js +9 -0
  23. package/dist/llm/kernel-driver/idle-reminder-wake.d.ts +4 -0
  24. package/dist/llm/kernel-driver/idle-reminder-wake.js +351 -0
  25. package/dist/llm/kernel-driver/types.d.ts +1 -1
  26. package/dist/mcp/config.d.ts +1 -0
  27. package/dist/mcp/config.js +16 -0
  28. package/dist/mcp/supervisor.d.ts +7 -1
  29. package/dist/mcp/supervisor.js +298 -16
  30. package/dist/minds/load.js +7 -1
  31. package/dist/minds/minds-i18n.js +2 -2
  32. package/dist/minds/system-prompt-parts.js +56 -20
  33. package/dist/minds/system-prompt.d.ts +1 -0
  34. package/dist/minds/system-prompt.js +24 -16
  35. package/dist/runtime/driver-messages.d.ts +3 -0
  36. package/dist/runtime/driver-messages.js +91 -8
  37. package/dist/server/setup-routes.js +5 -5
  38. package/dist/tool.d.ts +8 -0
  39. package/dist/tools/builtins.js +6 -2
  40. package/dist/tools/ctrl.d.ts +2 -0
  41. package/dist/tools/ctrl.js +253 -67
  42. package/dist/tools/env.js +10 -58
  43. package/dist/tools/manual/render.js +4 -0
  44. package/dist/tools/mcp.d.ts +1 -0
  45. package/dist/tools/mcp.js +55 -5
  46. package/dist/tools/os.js +198 -0
  47. package/dist/tools/prompts/control/en/errors.md +2 -2
  48. package/dist/tools/prompts/control/en/index.md +1 -1
  49. package/dist/tools/prompts/control/en/principles.md +15 -14
  50. package/dist/tools/prompts/control/en/scenarios.md +6 -0
  51. package/dist/tools/prompts/control/en/tools.md +37 -4
  52. package/dist/tools/prompts/control/zh/errors.md +2 -2
  53. package/dist/tools/prompts/control/zh/index.md +1 -1
  54. package/dist/tools/prompts/control/zh/principles.md +15 -14
  55. package/dist/tools/prompts/control/zh/scenarios.md +6 -0
  56. package/dist/tools/prompts/control/zh/tools.md +37 -4
  57. package/dist/tools/prompts/mcp_admin/en/errors.md +0 -14
  58. package/dist/tools/prompts/mcp_admin/en/index.md +5 -3
  59. package/dist/tools/prompts/mcp_admin/en/principles.md +11 -5
  60. package/dist/tools/prompts/mcp_admin/en/scenarios.md +19 -3
  61. package/dist/tools/prompts/mcp_admin/en/tools.md +85 -21
  62. package/dist/tools/prompts/mcp_admin/zh/errors.md +0 -14
  63. package/dist/tools/prompts/mcp_admin/zh/index.md +5 -3
  64. package/dist/tools/prompts/mcp_admin/zh/principles.md +11 -5
  65. package/dist/tools/prompts/mcp_admin/zh/scenarios.md +19 -3
  66. package/dist/tools/prompts/mcp_admin/zh/tools.md +85 -21
  67. package/dist/tools/prompts/os/en/errors.md +0 -28
  68. package/dist/tools/prompts/os/en/scenarios.md +1 -1
  69. package/dist/tools/prompts/os/en/tools.md +8 -17
  70. package/dist/tools/prompts/os/zh/errors.md +0 -28
  71. package/dist/tools/prompts/os/zh/scenarios.md +1 -1
  72. package/dist/tools/prompts/os/zh/tools.md +8 -17
  73. package/dist/tools/prompts/personal_memory/en/principles.md +1 -1
  74. package/dist/tools/prompts/personal_memory/zh/principles.md +1 -1
  75. package/dist/tools/prompts/team_memory/en/principles.md +1 -1
  76. package/dist/tools/prompts/team_memory/zh/principles.md +1 -1
  77. package/dist/tools/registry.d.ts +5 -0
  78. package/dist/tools/team_mgmt-mcp-manual.d.ts +2 -2
  79. package/dist/tools/team_mgmt-mcp-manual.js +57 -16
  80. package/dist/tools/team_mgmt.js +9 -12
  81. package/dist/utils/task-package.d.ts +7 -0
  82. package/dist/utils/task-package.js +46 -23
  83. package/dist/utils/taskdoc.js +21 -17
  84. package/package.json +4 -4
package/README.md CHANGED
@@ -295,6 +295,7 @@ Result: fewer bad side effects, higher plan fidelity, and more first‑try succe
295
295
  - **[FBR](docs/fbr.md)** — Fresh Boots Reasoning (`freshBootsReasoning`) design and enhancements
296
296
  - **[Context Health](docs/context-health.md)** — Measuring/maintaining context quality
297
297
  - **[Diligence Push](docs/diligence-push.md)** — Auto-continue (diligence) mechanism
298
+ - **[Idle Reminder Wake](docs/idle-reminder-wake.md)** — Runtime wakeups from owner-managed reminder events
298
299
  - **[Showing-by-Doing](docs/showing-by-doing.md)** — A dialog-creation prelude that makes Tellask feel real
299
300
  - **[Design](docs/design.md)** — Architecture and key abstractions
300
301
  - **[Roadmap](docs/roadmap.md)** — Major-version plan and evolution
@@ -312,12 +312,12 @@ function getAccessDeniedMessage(operation, targetPath, language = 'en') {
312
312
  lines.push('');
313
313
  if (language === 'zh') {
314
314
  lines.push(`- 说明:\`*.tsk/\` 是封装差遣牒。通用文件工具无法读/写/列目录/删除其中内容(硬编码无条件拒绝)。`);
315
- lines.push(`- 提示:写入/更新请使用函数工具 \`change_mind\`(顶层:\`change_mind({\"selector\":\"goals|constraints|progress\",\"content\":\"...\"})\`;额外章节:\`change_mind({\"category\":\"<category>\",\"selector\":\"<selector>\",\"content\":\"...\"})\`)。`);
315
+ lines.push(`- 提示:少量追加请使用 \`mind_more\`(默认 progress:\`mind_more({\"items\":[\"...\"]})\`);整章替换请使用 \`change_mind\`(顶层:\`change_mind({\"selector\":\"goals|constraints|progress\",\"content\":\"...\"})\`;额外章节:\`change_mind({\"category\":\"<category>\",\"selector\":\"<selector>\",\"content\":\"...\"})\`)。`);
316
316
  lines.push(`- 提示:读取额外章节请使用函数工具 \`recall_taskdoc\`:\`recall_taskdoc({\"category\":\"<category>\",\"selector\":\"<selector>\"})\`。`);
317
317
  }
318
318
  else {
319
319
  lines.push(`- Note: \`*.tsk/\` is an encapsulated Taskdoc. It is hard-denied for all general file tools.`);
320
- lines.push(`- Hint: For updates, use the function tool \`change_mind\` (top-level: \`change_mind({\"selector\":\"goals|constraints|progress\",\"content\":\"...\"})\`; extra sections: \`change_mind({\"category\":\"<category>\",\"selector\":\"<selector>\",\"content\":\"...\"})\`).`);
320
+ lines.push(`- Hint: For small append-only updates, use \`mind_more\` (defaults to progress: \`mind_more({\"items\":[\"...\"]})\`); for full-section replacements, use \`change_mind\` (top-level: \`change_mind({\"selector\":\"goals|constraints|progress\",\"content\":\"...\"})\`; extra sections: \`change_mind({\"category\":\"<category>\",\"selector\":\"<selector>\",\"content\":\"...\"})\`).`);
321
321
  lines.push(`- Hint: To read extra sections, use \`recall_taskdoc({\"category\":\"<category>\",\"selector\":\"<selector>\"})\`.`);
322
322
  }
323
323
  }
@@ -8,10 +8,11 @@ export type McpDeclaredToolsets = Readonly<{
8
8
  kind: 'loaded';
9
9
  declaredServerIds: ReadonlySet<string>;
10
10
  invalidServerIds: ReadonlySet<string>;
11
+ disabledServerIds: ReadonlySet<string>;
11
12
  }>;
12
13
  export type ToolsetAuditItem = Readonly<{
13
14
  toolsetName: string;
14
- status: 'registered' | 'mcp_declared_unloaded' | 'mcp_declared_invalid' | 'missing';
15
+ status: 'registered' | 'mcp_declared_unloaded' | 'mcp_declared_invalid' | 'mcp_declared_disabled' | 'missing';
15
16
  }>;
16
17
  export type ToolsetAuditReport = Readonly<{
17
18
  mcp: McpDeclaredToolsets;
@@ -45,6 +45,7 @@ async function readMcpDeclaredToolsets() {
45
45
  kind: 'loaded',
46
46
  declaredServerIds: new Set(parsed.serverIdsInYamlOrder),
47
47
  invalidServerIds: new Set(parsed.invalidServers.map((s) => s.serverId)),
48
+ disabledServerIds: new Set(parsed.disabledServerIdsInYamlOrder),
48
49
  };
49
50
  }
50
51
  function buildToolsetAuditReport(params) {
@@ -58,20 +59,28 @@ function buildToolsetAuditReport(params) {
58
59
  const explicitToolsets = listExplicitToolsets(member);
59
60
  const items = [];
60
61
  for (const toolsetName of explicitToolsets) {
61
- if (registeredToolsets.has(toolsetName)) {
62
- items.push({ toolsetName, status: 'registered' });
63
- continue;
64
- }
65
62
  if (params.mcp.kind === 'loaded' && params.mcp.declaredServerIds.has(toolsetName)) {
66
63
  if (params.mcp.invalidServerIds.has(toolsetName)) {
67
64
  items.push({ toolsetName, status: 'mcp_declared_invalid' });
68
65
  warnings.push(`@${member.id}: toolset '${toolsetName}' is declared in mcp.yaml but server config is invalid.`);
69
66
  }
67
+ else if (params.mcp.disabledServerIds.has(toolsetName)) {
68
+ items.push({ toolsetName, status: 'mcp_declared_disabled' });
69
+ }
70
70
  else {
71
- items.push({ toolsetName, status: 'mcp_declared_unloaded' });
71
+ if (registeredToolsets.has(toolsetName)) {
72
+ items.push({ toolsetName, status: 'registered' });
73
+ }
74
+ else {
75
+ items.push({ toolsetName, status: 'mcp_declared_unloaded' });
76
+ }
72
77
  }
73
78
  continue;
74
79
  }
80
+ if (registeredToolsets.has(toolsetName)) {
81
+ items.push({ toolsetName, status: 'registered' });
82
+ continue;
83
+ }
75
84
  items.push({ toolsetName, status: 'missing' });
76
85
  warnings.push(`@${member.id}: toolset '${toolsetName}' is neither registered nor declared in mcp.yaml.`);
77
86
  }
@@ -91,6 +100,7 @@ function countByStatus(report) {
91
100
  registered: 0,
92
101
  mcp_declared_unloaded: 0,
93
102
  mcp_declared_invalid: 0,
103
+ mcp_declared_disabled: 0,
94
104
  missing: 0,
95
105
  };
96
106
  for (const memberReport of report.byMember) {
@@ -107,6 +117,8 @@ function statusLabel(status) {
107
117
  return 'DEFERRED';
108
118
  if (status === 'mcp_declared_invalid')
109
119
  return 'INVALID';
120
+ if (status === 'mcp_declared_disabled')
121
+ return 'DISABLED';
110
122
  return 'MISS';
111
123
  }
112
124
  function hasHardToolsetAuditFailures(report) {
@@ -125,12 +137,12 @@ function printToolsetAudit(report, options) {
125
137
  process.stdout.write('- MCP config: invalid (`.minds/mcp.yaml` parse/read failed)\n');
126
138
  }
127
139
  else {
128
- process.stdout.write(`- MCP config: loaded (declared servers: ${report.mcp.declaredServerIds.size}, invalid server configs: ${report.mcp.invalidServerIds.size})\n`);
140
+ process.stdout.write(`- MCP config: loaded (declared servers: ${report.mcp.declaredServerIds.size}, invalid server configs: ${report.mcp.invalidServerIds.size}, disabled servers: ${report.mcp.disabledServerIds.size})\n`);
129
141
  }
130
- process.stdout.write(`- Summary: ${counts.registered} OK, ${counts.mcp_declared_unloaded} DEFERRED, ${counts.mcp_declared_invalid} INVALID, ${counts.missing} MISS\n`);
142
+ process.stdout.write(`- Summary: ${counts.registered} OK, ${counts.mcp_declared_unloaded} DEFERRED, ${counts.mcp_declared_disabled} DISABLED, ${counts.mcp_declared_invalid} INVALID, ${counts.missing} MISS\n`);
131
143
  if (options?.includeTransientLegend !== false) {
132
144
  process.stdout.write('- Status notes: `DEFERRED` means the toolset is declared via `.minds/mcp.yaml` but is not currently loaded into the registry. This is often temporary (for example MCP server down/unreachable); if the MCP service recovers, rerun validation and it may clear without editing `team.yaml`.\n');
133
- process.stdout.write('- Status notes: `INVALID` means the MCP server declaration itself is invalid and needs a config fix. `MISS` means the toolset is neither registered nor declared in `.minds/mcp.yaml`.\n');
145
+ process.stdout.write('- Status notes: `DISABLED` means the MCP server has `enabled: false`; it remains declared but intentionally exposes zero tools until `mcp_restart` enables it. `INVALID` means the MCP server declaration itself is invalid and needs a config fix. `MISS` means the toolset is neither registered nor declared in `.minds/mcp.yaml`.\n');
134
146
  }
135
147
  if (report.byMember.length === 0) {
136
148
  process.stdout.write('- Members: none\n');
@@ -8,10 +8,10 @@ function printUsage() {
8
8
  console.log('Usage: dominds validate_team_def [<member-id>]');
9
9
  console.log('');
10
10
  console.log('Validate explicit toolset declarations in `.minds/team.yaml` against the current toolset registry and `.minds/mcp.yaml` declarations.');
11
- console.log('MCP-declared but currently unloaded toolsets are reported as `DEFERRED` because they are often transient runtime availability issues rather than permanent team-definition errors.');
11
+ console.log('MCP-declared but currently unloaded toolsets are reported as `DEFERRED`; disabled MCP toolsets are reported as `DISABLED` when `enabled: false` is set in `.minds/mcp.yaml`.');
12
12
  console.log('');
13
13
  console.log('Exit codes:');
14
- console.log(' 0 No hard definition errors (`OK` / `DEFERRED` only)');
14
+ console.log(' 0 No hard definition errors (`OK` / `DEFERRED` / `DISABLED` only)');
15
15
  console.log(' 2 Hard definition errors found (`INVALID` / `MISS`)');
16
16
  console.log('');
17
17
  console.log('Examples:');
@@ -70,7 +70,7 @@ async function main() {
70
70
  mcp: await (0, team_definition_audit_1.readMcpDeclaredToolsets)(),
71
71
  });
72
72
  process.stdout.write('# Team Definition Validation\n');
73
- process.stdout.write('This command checks explicit toolset references in `.minds/team.yaml`. `DEFERRED` usually means the toolset is declared through MCP but is not currently loaded; if the MCP service recovers, rerun this command before editing `team.yaml`.\n');
73
+ process.stdout.write('This command checks explicit toolset references in `.minds/team.yaml`. `DEFERRED` usually means the toolset is declared through MCP but is not currently loaded; `DISABLED` means the server is intentionally `enabled: false` and exposes zero tools until `mcp_restart` enables it.\n');
74
74
  (0, team_definition_audit_1.printToolsetAudit)(report, { heading: '## Definition Audit' });
75
75
  if ((0, team_definition_audit_1.hasHardToolsetAuditFailures)(report)) {
76
76
  process.exit(2);
package/dist/dialog.d.ts CHANGED
@@ -21,6 +21,11 @@ import { ChatMessage, FuncResultMsg, TellaskCarryoverMsg, TellaskResultMsg } fro
21
21
  import type { ToolResultImageIngest, UserImageIngest } from './llm/gen';
22
22
  import type { JsonValue } from './tool';
23
23
  import { Reminder, ReminderOptions, ReminderOwner } from './tool';
24
+ export declare class InvalidReminderIndexError extends Error {
25
+ readonly index: number;
26
+ readonly total: number;
27
+ constructor(index: number, total: number);
28
+ }
24
29
  type NewCourseHookResult = {
25
30
  kind: 'continue';
26
31
  prompt: DialogRuntimePrompt;
@@ -357,7 +362,7 @@ export declare abstract class Dialog {
357
362
  /**
358
363
  * Start a new course - clears conversational noise, Q4H, and increments course counter.
359
364
  * Queues a new-course prompt for the driver to consume on the next drive cycle.
360
- * This is the single entry point for mental clarity operations (clear_mind, change_mind).
365
+ * This is the single entry point for mental clarity operations that start a new course.
361
366
  */
362
367
  startNewCourse(newCoursePrompt: string, options?: {
363
368
  runControl?: DialogRunControlSpec;
package/dist/dialog.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DialogStore = exports.MainDialog = exports.SideDialog = exports.Dialog = exports.DialogID = void 0;
3
+ exports.DialogStore = exports.MainDialog = exports.SideDialog = exports.Dialog = exports.DialogID = exports.InvalidReminderIndexError = void 0;
4
4
  exports.buildSideDialogAskerStack = buildSideDialogAskerStack;
5
5
  const id_1 = require("@longrun-ai/kernel/utils/id");
6
6
  const time_1 = require("@longrun-ai/kernel/utils/time");
@@ -14,6 +14,15 @@ const work_language_1 = require("./runtime/work-language");
14
14
  const shared_reminders_1 = require("./shared-reminders");
15
15
  const tool_1 = require("./tool");
16
16
  const id_2 = require("./utils/id");
17
+ class InvalidReminderIndexError extends Error {
18
+ constructor(index, total) {
19
+ super(`Reminder target index ${index} is stale; current reminder count is ${total}`);
20
+ this.name = 'InvalidReminderIndexError';
21
+ this.index = index;
22
+ this.total = total;
23
+ }
24
+ }
25
+ exports.InvalidReminderIndexError = InvalidReminderIndexError;
17
26
  class DialogID {
18
27
  constructor(selfId, rootId) {
19
28
  this.selfId = selfId;
@@ -423,7 +432,7 @@ class Dialog {
423
432
  }
424
433
  deleteReminder(index) {
425
434
  if (index < 0 || index >= this.reminders.length) {
426
- throw new Error(`Reminder index ${index} does not exist. Available reminders: 0-${this.reminders.length - 1}`);
435
+ throw new InvalidReminderIndexError(index, this.reminders.length);
427
436
  }
428
437
  const deleted = this.reminders.splice(index, 1)[0];
429
438
  this.touchReminders();
@@ -431,7 +440,7 @@ class Dialog {
431
440
  }
432
441
  updateReminder(index, content, meta, options) {
433
442
  if (index < 0 || index >= this.reminders.length) {
434
- throw new Error(`Reminder index ${index} does not exist. Available reminders: 0-${this.reminders.length - 1}`);
443
+ throw new InvalidReminderIndexError(index, this.reminders.length);
435
444
  }
436
445
  const oldReminder = this.reminders[index];
437
446
  const updatedReminder = (0, tool_1.materializeReminder)({
@@ -1035,7 +1044,7 @@ class Dialog {
1035
1044
  /**
1036
1045
  * Start a new course - clears conversational noise, Q4H, and increments course counter.
1037
1046
  * Queues a new-course prompt for the driver to consume on the next drive cycle.
1038
- * This is the single entry point for mental clarity operations (clear_mind, change_mind).
1047
+ * This is the single entry point for mental clarity operations that start a new course.
1039
1048
  */
1040
1049
  async startNewCourse(newCoursePrompt, options) {
1041
1050
  const trimmedPrompt = newCoursePrompt.trim();
@@ -25,7 +25,11 @@ Dominds already has:
25
25
  (UI-visible and rendered as a normal user instruction).
26
26
  - In **critical**, enforce stability via a **countdown remediation** (max 5 turns):
27
27
  - Each turn injects a **recorded role=user prompt** (UI-visible as a user prompt) that instructs
28
- the agent to curate reminders (`update_reminder`/`add_reminder`) and then `clear_mind`.
28
+ Main Dialogs to first record current-dialog discussion details that are not yet documented but the
29
+ next course needs to know into the appropriate Taskdoc sections, then curate continuation-package
30
+ reminders (`update_reminder`/`add_reminder`) and `clear_mind`. Side Dialogs are instructed not to
31
+ maintain Taskdoc or draft update proposals; they maintain sufficiently detailed continuation-package
32
+ reminders instead, with no technical length limit, then `clear_mind`.
29
33
  - The prompt includes a countdown signal (how many reminders remain before auto-`clear_mind`).
30
34
  - When the countdown reaches 0, Dominds **automatically** executes `clear_mind` (no Q4H; no
31
35
  suspension) to keep long-running autonomy stable.
@@ -135,10 +139,16 @@ Rules:
135
139
  - Do not let the agent branch on subjective self-assessment such as “clear-headed” vs “muddled”.
136
140
  - When the current course is not under `caution` / `critical` remediation, prefer compressing into
137
141
  one structured continuation-package reminder.
138
- - When the system has put the current course into `caution` / `critical` remediation, prioritize
139
- preserving volatile information even via multiple rough reminders; in the current course, only
140
- preserve info and `clear_mind`. Once the system actually starts the next course, the first step
141
- is to review, merge, and delete redundancy.
142
+ - When the system has put the current course into `caution` / `critical` remediation, the driver splits
143
+ prompts by dialog scope:
144
+ - Main Dialog: first fill Taskdoc with current-dialog discussion details that are not yet documented
145
+ but the next course needs to know; then preserve details still not covered by Taskdoc but easy to
146
+ lose during resume.
147
+ - Side Dialog: do not maintain Taskdoc and do not draft Taskdoc update proposals; directly maintain
148
+ sufficiently detailed continuation-package reminders. Reminder length has no technical limit, so
149
+ prefer being complete.
150
+ - Multiple rough bridge reminders are acceptable. Once the system actually starts the next course,
151
+ the first step is to review, merge, and delete redundancy.
142
152
  - Do not duplicate Taskdoc content except for a short bridge when strictly needed.
143
153
  - Do not paste long raw logs/tool outputs into the continuation package.
144
154
 
@@ -157,7 +167,10 @@ Current behavior:
157
167
  - On entering `caution`, Dominds inserts the prompt once (entry injection).
158
168
  - While still `caution`, Dominds reinserts the prompt on a cadence (default: every **10**
159
169
  generations; configurable per model).
160
- - Each inserted prompt requires the agent to **curate reminders** (at least one call):
170
+ - Each inserted prompt is split by the program according to dialog scope, so the agent does not decide
171
+ whether it is in the Main Dialog or a Side Dialog:
172
+ - Main Dialog: update Taskdoc first with `mind_more` / `change_mind`, then curate reminders (at least one call)
173
+ - Side Dialog: do not maintain Taskdoc and do not draft Taskdoc update proposals; directly curate sufficiently detailed reminders (at least one call)
161
174
  - `update_reminder` (preferred) / `add_reminder`
162
175
  - Default to one structured continuation-package reminder; if the current course is already under remediation and one structured reminder cannot be produced directly from already observed facts, rough multi-reminder carry-over is acceptable
163
176
  - Then `clear_mind` when it becomes scannable/actionable
@@ -167,9 +180,13 @@ Current behavior:
167
180
  When `level === 'critical'`, the driver enters a **countdown remediation** (max **5** turns):
168
181
 
169
182
  - On each turn, the driver records a **role=user prompt** (persisted as a user message) that is
170
- visible in the UI as a user prompt. This prompt tells the agent to:
171
- - curate reminders via `update_reminder` / `add_reminder`, preferably into a continuation-package reminder but allowing rough multi-reminder carry-over when the current course is already under remediation and a single structured reminder cannot be produced directly from already observed facts, and
172
- - then call `clear_mind` to start a new course.
183
+ visible in the UI as a user prompt. The prompt is scope-specific:
184
+ - Main Dialog prompt: first write undocumented discussion details that the next course needs to know
185
+ into the appropriate Taskdoc sections with `mind_more` / `change_mind`, then curate reminders via
186
+ `update_reminder` / `add_reminder`, and call `clear_mind`.
187
+ - Side Dialog prompt: do not maintain Taskdoc and do not draft Taskdoc update proposals; directly
188
+ maintain sufficiently detailed continuation-package reminders with no technical length limit, then
189
+ call `clear_mind`.
173
190
  - The prompt includes a countdown: after **N** turns the system will automatically clear.
174
191
  - When the countdown reaches 0, the driver **automatically calls** `clear_mind` (with empty args; no
175
192
  requirement on `reminder_content`), starting a new course without suspending.
@@ -234,9 +251,18 @@ Additional constraints:
234
251
  - v3 remediation:
235
252
  - `caution`: driver inserts a persisted role=user prompt (UI-visible user instruction).
236
253
  On entering `caution` it inserts once; while still `caution` it reinserts on a cadence (default: every
237
- 10 generations; configurable per model). Each time, the agent must call at least one of
238
- `update_reminder` / `add_reminder` and preserve a continuation package. A single structured reminder is preferred when the current course is not under remediation pressure; during remediation, multiple rough reminders are acceptable if they can be written directly from already observed facts without further reading/analysis. In the new course the agent should reconcile them first, then `clear_mind` when ready.
254
+ 10 generations; configurable per model). Each time, the agent must first record undocumented
255
+ discussion details the next course needs to know into Taskdoc, then call at least one of
256
+ `update_reminder` / `add_reminder` and preserve a continuation package. In a Side Dialog, the
257
+ prompt says not to maintain Taskdoc or draft Taskdoc update proposals; instead, maintain sufficiently
258
+ detailed continuation-package reminders directly.
259
+ A single structured reminder is preferred when the current course is not under remediation pressure;
260
+ during remediation, multiple rough reminders are acceptable if they can be written directly from
261
+ already observed facts without further reading/analysis. In the new course the agent should
262
+ reconcile them first, then `clear_mind` when ready.
239
263
  - `critical`: driver runs a countdown remediation (max 5 turns) using **recorded role=user prompts**.
240
- Each prompt includes a countdown and instructs reminder curation + `clear_mind`. When the countdown
241
- reaches 0, the driver auto-executes `clear_mind` and starts a new course (no Q4H, no suspension).
264
+ Each prompt includes a countdown. Main Dialog prompts instruct Taskdoc update, reminder curation,
265
+ then `clear_mind`; Side Dialog prompts instruct detailed reminder curation only, then `clear_mind`.
266
+ When the countdown reaches 0, the driver auto-executes `clear_mind` and starts a new course (no Q4H,
267
+ no suspension).
242
268
  - UI shows context health with green/yellow/red (and “unknown” handling when usage is unavailable).
@@ -19,7 +19,7 @@ Dominds 已具备以下功能:
19
19
  - 当对话上下文"过大"时,执行简短的、可执行的、可回归测试的 **v3 恢复**工作流:
20
20
  - 在 **caution(警告)** 级别,记录一条自动插入的 **role=user prompt** 作为正常的、持久化的用户消息(UI 可见并渲染为正常的用户指令)。
21
21
  - 在 **critical(严重)** 级别,通过**倒计时恢复**(最多 5 轮)强制执行稳定性:
22
- - 每轮注入一条**记录的角色为 user 的 prompt**(UI 可见为用户 prompt),指示智能体整理提醒项(`update_reminder`/`add_reminder`),然后执行 `clear_mind`。
22
+ - 每轮注入一条**记录的角色为 user 的 prompt**(UI 可见为用户 prompt)。主线对话提示智能体先把当前对话历史中尚未落实到文档、且下一程需要知会的讨论细节落到差遣牒合适章节,再整理接续包提醒项(`update_reminder`/`add_reminder`),然后执行 `clear_mind`;支线对话提示智能体不要维护差遣牒、也不要整理更新提案,只维护足够详尽的接续包提醒项(长度无技术限制),然后执行 `clear_mind`。
23
23
  - prompt 包含倒计时信号(在进行自动 `clear_mind` 之前还剩多少轮)。
24
24
  - 当倒计时归零时,Dominds **自动**执行 `clear_mind`(无需 Q4H;无需暂停)以保持长期运行的自主性。
25
25
 
@@ -112,7 +112,10 @@ Dominds 计算比率:
112
112
  - 普通提醒项保持简短且少量。
113
113
  - 不允许智能体靠主观感觉判断自己“头脑清楚”还是“已经发乱”;这两种说法没有可靠、可审计的客观判据。
114
114
  - 机制性分流规则只有一个:看**系统是否已将当前程置于上下文健康处置态**。若当前没有 `caution/critical` 处置指令,则默认优先压缩成一个结构化接续包提醒项。
115
- - 一旦系统已将当前程置于 `caution/critical` 处置态,当前程的目标就切换为“保住易丢信息并尽快 `clear_mind`”,此时允许多条粗略提醒项过桥;系统真正开启新一程后,第一步再复核、合并并删除冗余。
115
+ - 一旦系统已将当前程置于 `caution/critical` 处置态,驱动程序按对话范围分流提示:
116
+ - 主线对话:先把当前对话历史中尚未落实到文档、且下一程需要知会的讨论细节写入差遣牒合适章节;随后只把差遣牒仍未覆盖、但恢复工作容易丢的细节放入接续包。
117
+ - 支线对话:不要维护差遣牒,也不要整理差遣牒更新提案;直接维护足够详尽的接续包提醒项,提醒项长度没有技术限制,宁可完整一些。
118
+ - 此时允许多条粗略提醒项过桥;系统真正开启新一程后,第一步再复核、合并并删除冗余。
116
119
  - 除非确有必要,不要重复差遣牒已覆盖的内容。
117
120
  - 不要把长原始日志/大段 tool output 直接塞进接续包。
118
121
 
@@ -126,7 +129,9 @@ Dominds 计算比率:
126
129
 
127
130
  - 进入 `caution` 时,Dominds 插入一次提示(入口注入)。
128
131
  - 保持在 `caution` 状态时,Dominds 按节奏重新插入(默认:每 **10** 次生成;可按模型配置)。
129
- - 每次插入的提示都要求智能体**整理提醒项**(至少一次调用):
132
+ - 每次插入的提示都由程序按范围分流,不要求智能体自己判断主线/支线:
133
+ - 主线对话:先使用 `mind_more` / `change_mind` 补齐差遣牒,再整理提醒项(至少一次调用)
134
+ - 支线对话:不维护差遣牒,也不整理差遣牒更新提案;直接整理足够详尽的提醒项(至少一次调用)
130
135
  - `update_reminder`(首选)/ `add_reminder`
131
136
  - 在提醒项内维护接续包草稿
132
137
  - 当可扫描/可操作时执行 `clear_mind`
@@ -135,14 +140,16 @@ Dominds 计算比率:
135
140
 
136
141
  当 `level === 'critical'` 时,驱动程序进入**倒计时恢复**(最多 **5** 轮):
137
142
 
138
- - 每轮,驱动程序记录一条 **role=user prompt**(持久化为用户消息),在 UI 中作为用户 prompt 可见。此提示告诉智能体:
139
- - 通过 `update_reminder` / `add_reminder` 整理提醒项(尽力而为的接续包),然后调用 `clear_mind` 开始新一程。
143
+ - 每轮,驱动程序记录一条 **role=user prompt**(持久化为用户消息),在 UI 中作为用户 prompt 可见。提示按范围分开:
144
+ - 主线对话提示:先用 `mind_more` / `change_mind` 把未落文档、且下一程需要知会的讨论细节写入差遣牒合适章节,再通过 `update_reminder` / `add_reminder` 整理提醒项并调用 `clear_mind`。
145
+ - 支线对话提示:不要维护差遣牒,也不要整理差遣牒更新提案;直接维护足够详尽的接续包提醒项,提醒项长度没有技术限制,然后调用 `clear_mind`。
140
146
  - 提示包含倒计时:经过 **N** 轮后系统将自动清空。
141
147
  - 当倒计时归零时,驱动程序**自动调用** `clear_mind`(带空参数;不要求 `reminder_content`),开始新一程且无需暂停。
142
148
 
143
149
  理由:
144
150
 
145
151
  - `caution` 已经在提醒项中尽力推动接续包草稿的编写。
152
+ - 告急提示仍要求主线先补差遣牒;支线则以详尽提醒项保存讨论细节,避免维护人提案在上下文吃紧时额外增加心智负担。
146
153
  - 在 `critical` 状态下,我们更倾向于保持对话长期运行而无需人工干预。
147
154
 
148
155
  ## UI(Webapp)预期
@@ -193,6 +200,6 @@ Dominds 计算比率:
193
200
  - 未配置时 `optimal_max_tokens` 默认为 `100_000`。
194
201
  - 未配置时 `critical_max_tokens` 默认为 `floor(modelContextLimitTokens * 0.9)`。
195
202
  - v3 恢复:
196
- - `caution`:驱动程序插入持久化的 role=user prompt(UI 可见的用户指令)。进入 `caution` 时插入一次;保持在 `caution` 状态时按节奏重新插入(默认:每 10 次生成;可按模型配置)。每次智能体必须至少调用 `update_reminder` / `add_reminder` 之一并维护接续包草稿,然后在就绪时执行 `clear_mind`。
197
- - `critical`:驱动程序使用**记录的角色为 user 的 prompt** 运行倒计时恢复(最多 5 轮)。每次提示包含倒计时并指示提醒整理 + `clear_mind`。当倒计时归零时,驱动程序自动执行 `clear_mind` 并开始新一程(无 Q4H,无暂停)。
203
+ - `caution`:驱动程序插入持久化的 role=user prompt(UI 可见的用户指令)。进入 `caution` 时插入一次;保持在 `caution` 状态时按节奏重新插入(默认:每 10 次生成;可按模型配置)。主线提示要求先把未落文档、且下一程需要知会的讨论细节补进差遣牒,再至少调用 `update_reminder` / `add_reminder` 之一维护接续包草稿;支线提示要求不要维护差遣牒/更新提案,直接维护足够详尽的接续包提醒项;然后在就绪时执行 `clear_mind`。
204
+ - `critical`:驱动程序使用**记录的角色为 user 的 prompt** 运行倒计时恢复(最多 5 轮)。每次提示包含倒计时;主线提示指示先补差遣牒,再整理提醒项并 `clear_mind`;支线提示指示只维护详尽提醒项并 `clear_mind`。当倒计时归零时,驱动程序自动执行 `clear_mind` 并开始新一程(无 Q4H,无暂停)。
198
205
  - UI 显示上下文健康状态:绿色/黄色/红色(以及使用情况不可用时的"未知"处理)。
@@ -0,0 +1,227 @@
1
+ # Idle Reminder Wake Design
2
+
3
+ Chinese version: [中文版](./idle-reminder-wake.zh.md)
4
+
5
+ This document defines a driver-level background coroutine mechanism: after a dialog enters `idle_waiting_user`, the runtime may wait for wake-worthy events from reminder owners. If an event arrives while the dialog is still idle, the runtime packages the event into a system-notice `role=user` message and continues driving the dialog.
6
+
7
+ This is a design document. It defines semantics, owner interfaces, cancellation, and the current implementation target; it does not prescribe the final code split.
8
+
9
+ ---
10
+
11
+ ## Background
12
+
13
+ Reminder owners already own the meaning of their reminders. For example, the `shell_cmd` daemon reminder can discover that a daemon has exited during the next reminder update and turn the reminder into a terminal snapshot.
14
+
15
+ The missing link is idle-time wakeup. If a daemon exits while the dialog is idle, Dominds currently notices only when the dialog is opened, reminders are displayed, or a later drive happens. From the user's perspective, the long-running command has completed, but the dialog does not automatically wake up or explain that the process exited.
16
+
17
+ The `shell_cmd` tool should not directly drive dialogs to solve this. The driver is the only component that should decide whether a dialog continues running. A reminder owner should only explain when one of its reminders has produced an environment event worth waking the model for.
18
+
19
+ ---
20
+
21
+ ## Goals
22
+
23
+ - Start a cancelable background await task only after the dialog truly enters `idle_waiting_user`.
24
+ - Let reminder owners expose wake-worthy events such as daemon exit.
25
+ - After an event arrives, briefly aggregate nearby events before forming one runtime `role=user` system notice.
26
+ - If the dialog is still idle after aggregation, continue through the normal driver path.
27
+ - Cancel the existing idle await task whenever any new drive starts.
28
+ - Preserve owner metadata encapsulation: framework code routes by owner but does not reinterpret owner meta.
29
+ - Guarantee idempotence: the same environment event must not insert duplicate system notices.
30
+
31
+ ## Non-Goals
32
+
33
+ - Do not treat every reminder content change as a wake signal.
34
+ - Do not let reminder owners call `driveDialogStream` directly.
35
+ - Do not represent the idle await task as an `activeRun`, because the UI must not see environment waiting as proceeding.
36
+ - Do not auto-wake blocked, stopped, dead, completed, or archived dialogs.
37
+ - Do not wake on every daemon stdout/stderr growth; the current scenario only covers daemon lifecycle exit.
38
+
39
+ ---
40
+
41
+ ## Core Decisions
42
+
43
+ ### 1. The driver owns the idle wake lifecycle
44
+
45
+ After `driveDialogStreamCore` finally sets display state to `idle_waiting_user`, the outer driver starts a dialog-scoped idle wake task.
46
+
47
+ The task:
48
+
49
+ - does not hold the dialog mutex
50
+ - does not create an active run
51
+ - does not change display state
52
+ - only waits for owner-provided wake events
53
+ - is canceled before a new drive begins
54
+
55
+ This keeps "waiting for an environment event" separate from "actively working" and avoids confusing user-visible run-control semantics.
56
+
57
+ ### 2. Reminder owners only report wake events
58
+
59
+ `ReminderOwner` gains an optional interface:
60
+
61
+ ```ts
62
+ export type ReminderWakeEvent = Readonly<{
63
+ eventId: string;
64
+ reminderId: string;
65
+ content: string;
66
+ updatedContent?: string;
67
+ updatedMeta?: JsonValue;
68
+ }>;
69
+
70
+ export interface ReminderOwner {
71
+ readonly name: string;
72
+ updateReminder(dlg: Dialog, reminder: Reminder): Promise<ReminderUpdateResult>;
73
+ renderReminder(dlg: Dialog, reminder: Reminder): Promise<ChatMessage>;
74
+
75
+ waitForReminderWakeEvent?(
76
+ dlg: Dialog,
77
+ reminders: readonly Reminder[],
78
+ signal: AbortSignal,
79
+ ): Promise<ReminderWakeEvent | readonly ReminderWakeEvent[] | null>;
80
+ }
81
+ ```
82
+
83
+ `content` is owner-formatted system-notice text and must start with `【系统提示】` or `[System notice]`. It is not real user input, but because providers commonly lack a dedicated environment role, it is persisted through the runtime prompt path as a `role=user` message.
84
+
85
+ `eventId` is a stable idempotence key in the owner's domain. The owner must be able to record, in reminder meta or owner-owned state, that the event has already been delivered.
86
+
87
+ ### 3. The first event opens a short aggregation window
88
+
89
+ The driver should not drive immediately after the first wake event. It should:
90
+
91
+ 1. await the first wake event
92
+ 2. open an aggregation window of about 500 ms
93
+ 3. collect other wake events that arrive in the same idle wake task
94
+ 4. stably sort and deduplicate the events
95
+ 5. package them into one runtime prompt
96
+
97
+ This prevents several daemons that exit close together from causing several consecutive drive rounds. The user and model see one combined environment-status message instead of fragmented notices.
98
+
99
+ ### 4. Wake must re-check fresh state
100
+
101
+ After the aggregation window, the driver must re-read persistence and verify:
102
+
103
+ - the dialog still exists and is running
104
+ - display state is still `idle_waiting_user`
105
+ - execution marker is not dead and not an interrupted state requiring human resume
106
+ - no pending Q4H exists
107
+ - no blocking pending sideDialog exists
108
+ - no active run exists
109
+
110
+ Only then may the driver continue with the wake prompt. Otherwise the wake is dropped, while owner idempotence/state updates may still be retained.
111
+
112
+ ### 5. Any drive cancels the idle wake task
113
+
114
+ Before any new drive begins, runtime must cancel the dialog's existing idle wake task. This includes:
115
+
116
+ - user messages
117
+ - manual Continue / Resume All
118
+ - Q4H-answer resume
119
+ - sideDialog-response resume
120
+ - Diligence Push / other runtime auto-drive
121
+ - the wake drive from this mechanism
122
+
123
+ After cancellation, the old task must not produce side effects even if one of its promises resolves.
124
+
125
+ ---
126
+
127
+ ## Wake Message Format
128
+
129
+ Owner event `content` should state facts without inventing a user request.
130
+
131
+ Daemon exit example:
132
+
133
+ ```text
134
+ 【系统提示】
135
+ 后台进程已退出。这是 runtime 环境事件,不是新的用户指令。
136
+
137
+ - PID: 12345
138
+ - 命令: pnpm run build
139
+ - 退出状态: code 0, signal null
140
+
141
+ 请根据当前任务上下文判断是否需要查看最终 stdout/stderr 或向用户汇报结果;不要只回复“收到”。
142
+ ```
143
+
144
+ When the driver aggregates multiple events, it should preserve each factual block and add a shared prefix:
145
+
146
+ ```text
147
+ 【系统提示】
148
+ 以下是对话空闲期间发生的 runtime 环境事件。这些事件不是新的用户指令。
149
+
150
+ 1. 后台进程已退出 ...
151
+ 2. 后台进程已退出 ...
152
+
153
+ 请结合当前任务上下文继续推进;若这些事件不影响当前工作,不要发送占位式确认。
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Current Target: Shell Daemon Exit
159
+
160
+ `shellCmdReminderOwner` implements `waitForReminderWakeEvent`.
161
+
162
+ Semantics:
163
+
164
+ - It only watches daemon reminders owned by this owner.
165
+ - It only emits a wake event when a daemon transitions from running to exited/gone.
166
+ - stdout/stderr growth does not emit a wake event.
167
+ - If reminder meta already marks the corresponding exit event as delivered, it returns `null`.
168
+ - When the event arrives, the owner also provides terminal reminder `updatedContent` / `updatedMeta`, and the driver persists them before delivering the wake prompt.
169
+
170
+ Required meta additions:
171
+
172
+ - `originRootId`: needed to restore the origin dialog.
173
+ - `originDialogId`: existing field; continues to mean self id.
174
+ - `exitWakeEventId`: stable event id, for example `shellCmd:daemonExited:<pid>:<startTime>`.
175
+ - `exitWakeNotifiedAt`: timestamp when runtime accepted and delivered the event.
176
+
177
+ If the daemon runner can provide an awaitable exit signal, use that as the primary path. If the current implementation can only use local IPC status checks, polling must stay encapsulated inside the owner; the driver must not gain daemon-specific scanning logic.
178
+
179
+ ---
180
+
181
+ ## Cancellation And Concurrency
182
+
183
+ There is at most one idle wake task per dialog.
184
+
185
+ Suggested runtime state:
186
+
187
+ ```ts
188
+ type IdleReminderWakeTask = Readonly<{
189
+ dialogKey: string;
190
+ controller: AbortController;
191
+ startedAt: string;
192
+ }>;
193
+ ```
194
+
195
+ The first preflight step of a new drive cancels the old task. Cancellation is idempotent.
196
+
197
+ After an idle wake task resolves, it must also verify that it is still the current task. If it has been replaced or canceled, it returns without side effects.
198
+
199
+ ---
200
+
201
+ ## Crash Recovery
202
+
203
+ The idle wake task itself is not persisted. After backend restart, Dominds does not recover in-flight waiting promises.
204
+
205
+ The first normal driver/display/reminder update after restart still corrects reminder terminal state. If restart-time proactive waking is needed, Dominds can add bootstrap logic that reinstalls idle wake tasks for running idle dialogs. That capability is not required for the current mechanism to be complete.
206
+
207
+ ---
208
+
209
+ ## Observability And Error Handling
210
+
211
+ Owner wait interfaces are loud by default:
212
+
213
+ - Non-cancel errors should be structured logs with `rootId`, `selfId`, `ownerName`, `reminderId`, and `eventId` when available.
214
+ - Owners must not swallow unreasonable states, such as the same event id mapping to conflicting content.
215
+ - If the driver finds that an aggregated wake can no longer revive the dialog, it should record debug/warn diagnostics, but it must not surface the dropped wake as a user-visible message.
216
+
217
+ ---
218
+
219
+ ## Implementation Order
220
+
221
+ 1. Add `ReminderWakeEvent` and optional `ReminderOwner.waitForReminderWakeEvent?` types.
222
+ 2. Add driver-side idle wake task management: start/cancel/race/500 ms aggregation/fresh state checks.
223
+ 3. Cancel the dialog's idle wake task in every drive preflight.
224
+ 4. Start an idle wake task after the driver finally lands on `idle_waiting_user`.
225
+ 5. Implement daemon-exit wake events in `shellCmdReminderOwner`.
226
+ 6. Add `originRootId` and wake idempotence fields to daemon reminder meta.
227
+ 7. Add tests: single daemon exit wake, multiple daemon exits aggregated within 500 ms, user message cancellation, blocked dialogs not waking, and idempotence.