dominds 1.16.6 → 1.16.8

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.
@@ -51,6 +51,7 @@ const evt_registry_1 = require("./evt-registry");
51
51
  const log_1 = require("./log");
52
52
  const persistence_1 = require("./persistence");
53
53
  const persistence_errors_1 = require("./persistence-errors");
54
+ const interjection_pause_stop_1 = require("./runtime/interjection-pause-stop");
54
55
  const log = (0, log_1.createLogger)('dialog-display-state');
55
56
  let broadcastToClients;
56
57
  const activeRunsByDialogKey = new Map();
@@ -128,11 +129,21 @@ async function getRunControlCountsSnapshot() {
128
129
  }
129
130
  else if (latest?.executionMarker?.kind === 'interrupted' &&
130
131
  isStoppedReasonResumable(latest.executionMarker.reason)) {
131
- const q4h = await persistence_1.DialogPersistence.loadQuestions4HumanState(dialogId, 'running');
132
- const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(dialogId, 'running');
133
- if (q4h.length === 0 && pendingSubdialogs.length === 0) {
132
+ // Keep run-control counts aligned with actual Continue affordance:
133
+ // - ordinary interrupted dialogs count as resumable only when no blocker remains
134
+ // - interjection-paused dialogs still count as resumable even if blocker facts remain,
135
+ // because the intended UX is that Continue exits the temporary paused projection
136
+ // and re-evaluates the original task from fresh facts
137
+ if ((0, interjection_pause_stop_1.isUserInterjectionPauseStopReason)(latest.executionMarker.reason)) {
134
138
  resumable++;
135
139
  }
140
+ else {
141
+ const q4h = await persistence_1.DialogPersistence.loadQuestions4HumanState(dialogId, 'running');
142
+ const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(dialogId, 'running');
143
+ if (q4h.length === 0 && pendingSubdialogs.length === 0) {
144
+ resumable++;
145
+ }
146
+ }
136
147
  }
137
148
  }
138
149
  catch (error) {
@@ -470,6 +481,27 @@ async function refreshRunControlProjectionFromPersistenceFacts(dialogId, trigger
470
481
  latest.executionMarker.kind === 'dead') {
471
482
  return { kind: 'dead', reason: latest.executionMarker.reason };
472
483
  }
484
+ if (latest.executionMarker?.kind === 'interrupted' &&
485
+ (0, interjection_pause_stop_1.isUserInterjectionPauseStopReason)(latest.executionMarker.reason)) {
486
+ // WARNING:
487
+ // This is the one place where the projection intentionally preserves the paused-interjection
488
+ // stopped state ahead of the current blocker facts. That is not a bug: after a user
489
+ // interjection we want the UI to keep showing "original task paused; click Continue" even if
490
+ // the underlying dialog is still waiting on Q4H/subdialogs.
491
+ //
492
+ // The true source-of-truth decision about what Continue should do next lives in
493
+ // `flow.ts`'s resume path, which performs a fresh fact scan at resume time and then either:
494
+ // - restores `blocked`, or
495
+ // - keeps driving immediately.
496
+ //
497
+ // Do not "heal" this branch away by prioritizing blocker facts here; that would collapse the
498
+ // temporary interjection UX and make repeated interjection turns revert too early.
499
+ return {
500
+ kind: 'stopped',
501
+ reason: latest.executionMarker.reason,
502
+ continueEnabled: isStoppedReasonResumable(latest.executionMarker.reason),
503
+ };
504
+ }
473
505
  const q4h = await persistence_1.DialogPersistence.loadQuestions4HumanState(dialogId, 'running');
474
506
  const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(dialogId, 'running');
475
507
  const hasQ4H = q4h.length > 0;
@@ -155,6 +155,51 @@ Dialog state is persisted to storage at key points:
155
155
 
156
156
  This ensures crash recovery and enables the backend to resume from any persisted state without depending on frontend state.
157
157
 
158
+ ### User Interjection Pause And Continue Semantics
159
+
160
+ When a dialog still carries an inter-dialog reply obligation, but the user temporarily interjects and asks it to handle a local question first, the system must distinguish between the **UI projection** and the **true driving source state**.
161
+
162
+ **Normative semantics**:
163
+
164
+ 1. Every user interjection message is driven as a complete normal round.
165
+ 2. If that round needs tools, the system MUST finish the full tool round and any post-tool follow-up before pausing.
166
+ 3. The system only projects the original task as resumable `stopped` when this interjection has actually parked an original task that still needs explicit restoration.
167
+ 4. If there is no parked original task to resume afterwards (for example, no inter-dialog reply obligation needs reassertion), the interjection round should simply finish and return to the true underlying state without showing this special `stopped` panel.
168
+ 5. As long as the user keeps sending new messages, the dialog stays in temporary interjection-chat handling, and that paused projection remains in place only if it was established in the first place.
169
+ 6. Only an explicit UI `Continue` attempts to restore the original task.
170
+
171
+ **Key point**: this `stopped` state is only a temporary run-control / UI projection. It is not the same as an ordinary system-stop failure, and it is not the final business source of truth. It also does not apply to every interjection; it exists only when there really is a parked original task to resume.
172
+
173
+ After the user clicks `Continue`, the backend MUST re-evaluate fresh persistence facts and decide which true-source case now applies. It must not infer the result purely from the visible `displayState`:
174
+
175
+ - **Case 1: the dialog no longer has a reply obligation**
176
+ If there is also no blocker, the dialog should simply continue driving. If it has already become ordinary idle-waiting-user, then `resume_dialog` is no longer actually resumable.
177
+ - **Case 2: the dialog still has a reply obligation and is still suspended**
178
+ Typical examples are pending Q4H or pending subdialogs. In this case, `Continue` should exit the interjection-paused projection and restore the true `blocked` state.
179
+ - **Case 3: the dialog still has a reply obligation but is no longer suspended and is eligible to proceed**
180
+ For example, the blocker has disappeared, or a queued prompt provides a valid continuation path. In this case, `Continue` must not first fall back to an intermediate placeholder `blocked/idle` state; it should keep driving immediately.
181
+
182
+ **This leads to two implementation constraints**:
183
+
184
+ - `refreshRunControlProjectionFromPersistenceFacts()` MUST preserve the special "interjection handled; original task paused" `stopped` projection until the user explicitly clicks `Continue`; otherwise the UI collapses back to ordinary `blocked` too early and breaks multi-turn interjection UX. Conversely, when there is no parked original task, this paused projection should not be created at all.
185
+ - The actual outcome of `Continue` MUST be decided in the resume drive path by re-reading fresh persistence facts. "Continue is clickable" does not mean "the dialog will definitely enter proceeding immediately".
186
+ - The run-control toolbar's `resumable` count should align with "manual Continue attempt is meaningful". Therefore an interjection-paused `stopped` dialog still counts as resumable even when underlying blocker facts remain, because the business meaning of `Continue` there is "exit the temporary paused projection and re-evaluate from source-of-truth facts".
187
+
188
+ **Mental-model warning**:
189
+
190
+ - Do not reason about this flow from `displayState.kind === 'stopped'` alone.
191
+ - Do not reason about it from blocker facts alone and then wonder why the UI still shows `stopped`.
192
+ - Do not reason about it from `resume_dialog` eligibility alone and assume resumption always means immediate running.
193
+
194
+ You need all of the following together to understand the behavior correctly:
195
+
196
+ - reply-guidance suppression / deferred reassertion for interjection turns
197
+ - flow logic for "pause after local interjection reply" plus "fresh-fact second decision after Continue"
198
+ - dialog-display-state projection preservation
199
+ - websocket resume entry semantics distinguishing "allowed to attempt Continue" from "actually re-entered driving"
200
+
201
+ This is an intentionally cross-module semantic contract. Do not locally "simplify" one piece based only on its surface meaning.
202
+
158
203
  ---
159
204
 
160
205
  ## 3-Type Teammate Tellask Taxonomy
@@ -154,6 +154,51 @@
154
154
 
155
155
  这确保了崩溃恢复,并使后端能够从任何持久化状态恢复,而不依赖于前端状态。
156
156
 
157
+ ### 用户插话暂停与 Continue 语义
158
+
159
+ 当某个对话仍带有跨对话回复义务,但用户临时插话要求它先处理本地问题时,系统必须区分**UI 投影**与**真实驱动源状态**。
160
+
161
+ **规范语义**:
162
+
163
+ 1. 每条用户插话消息都按正常驱动轮完整执行。
164
+ 2. 若该轮需要工具,则必须先完整跑完该工具轮及其 post-tool follow-up。
165
+ 3. 只有当这条插话确实打断了一个仍待恢复的“原任务”时,系统才把该原任务投影为可 `Continue` 的 `stopped`,让用户先看到最后一条回复。
166
+ 4. 若当前并不存在待恢复的原任务(例如没有待重申的跨对话回复义务),则插话轮结束后应直接回到真实 underlying state,而不显示这个特殊 `stopped` 面板。
167
+ 5. 只要用户继续发送新消息,就继续作为插话临时对话处理;这个 paused projection 仅在它已被建立时持续保持。
168
+ 6. 只有用户显式点击 UI `Continue`,系统才尝试恢复原任务。
169
+
170
+ **关键点**:这里的 `stopped` 只是一个临时 run-control / UI 投影,不等于普通 system-stop 失败,也不是最终的业务真源;并且它不是所有插话都会出现,只在“确有一个待恢复的原任务被临时停靠”时出现。
171
+
172
+ 点击 `Continue` 后,后端必须重新从 persistence 真源判定当前对话属于哪一种情况,而不能只根据表面的 `displayState` 做静态推断:
173
+
174
+ - **情况 1:当前对话没有回复义务**
175
+ 这时若也没有其他 blocker,就应直接继续 drive;若已回到普通待用户输入态,则 `resume_dialog` 不应再被视为可继续。
176
+ - **情况 2:当前对话仍有回复义务,但处于 suspend 状态**
177
+ 常见于仍在等待 Q4H / pending subdialogs。此时 `Continue` 应退出插话 paused projection,并恢复成真实的 `blocked`。
178
+ - **情况 3:当前对话仍有回复义务,但已不再 suspend,具备继续推进条件**
179
+ 例如 blocker 已消失,或存在允许继续的 queued prompt。此时 `Continue` 不应先落一个中间 `blocked/idle` 占位态,而应直接继续 drive。
180
+
181
+ **因此有两个实现约束**:
182
+
183
+ - `refreshRunControlProjectionFromPersistenceFacts()` 在用户尚未点击 `Continue` 前,必须保留这层“插话已处理;原任务已暂停”的 `stopped` 投影;否则 UI 会过早塌回普通 `blocked`,破坏多轮插话体验。反过来,如果当前其实没有待恢复原任务,则根本不应建立这层 paused projection。
184
+ - 真正决定 `Continue` 结果的逻辑,必须在恢复驱动路径中重新读取 fresh persistence facts;不能把“可点 Continue”误解为“必然立即 proceeding”。
185
+ - run-control 工具栏中的 `resumable` 计数,应与“是否允许手动 Continue 尝试”保持一致。因此,处于 interjection-paused `stopped` 的对话即便底层仍有 blocker,也应计入 `resumable`;因为 `Continue` 的业务语义正是“退出这层临时 paused projection,并从真源重判下一步”。
186
+
187
+ **心智模型提醒**:
188
+
189
+ - 不能只看 `displayState.kind === 'stopped'` 就理解这条链路。
190
+ - 不能只看 blocker facts 就理解为什么 UI 仍显示 `stopped`。
191
+ - 也不能只看 `resume_dialog` eligibility 就推断恢复后一定马上运行。
192
+
193
+ 必须把以下几块一起看,才能形成完整且精确的理解:
194
+
195
+ - reply-guidance 中对插话轮的回复义务 suppression / deferred reassertion
196
+ - flow 中“插话回复后停车”与“Continue 后 fresh fact 二次判定”
197
+ - dialog-display-state 中 paused projection 的保留策略
198
+ - websocket resume 入口对“可尝试 Continue”与“实际已恢复 drive”的区分
199
+
200
+ 这是一条跨模块协同语义,不允许在单点上做“表面看起来更简单”的局部简化。
201
+
157
202
  ---
158
203
 
159
204
  ## 三类队友诉请分类
@@ -55,6 +55,20 @@ Rationale:
55
55
  - The framework source tree should not be the “primary” place the team config format is explained.
56
56
  Each rtws may have different policies and defaults.
57
57
 
58
+ ### Scope Boundary of This Document (important)
59
+
60
+ - This document is responsible for stable design intent, conceptual boundaries, responsibility split,
61
+ and why the system is designed this way.
62
+ - This document should **not** become a home for overly detailed runtime specifications such as exact
63
+ injection order, current fallback rules, full topic inventories, dynamic enumeration results,
64
+ field-by-field rendering details, or authoring rules that are expected to evolve with the implementation.
65
+ - For “what the runtime does today”, use:
66
+ - `man({ "toolsetId": "team_mgmt" })`
67
+ - the corresponding implementation and runtime validators
68
+ - If the design document and the runtime handbook disagree about current behavior, the runtime handbook
69
+ wins and the design doc should be corrected afterward. Readers should not treat this design doc as a
70
+ runtime specification manual.
71
+
58
72
  ## Current Problem Statement
59
73
 
60
74
  In typical deployments we deny direct `.minds/` access via the general-purpose rtws tools:
@@ -166,7 +180,10 @@ reading source code.
166
180
  - `man({ "toolsetId": "team_mgmt", "topics": ["team"] })` → how to manage `.minds/team.yaml` (+ templates).
167
181
  - `man({ "toolsetId": "team_mgmt", "topics": ["team", "member-properties"] })` → list supported member fields and meanings.
168
182
  - `man({ "toolsetId": "team_mgmt", "topics": ["minds"] })` → how to manage `.minds/team/<id>/*.md` (persona/knowledge/lessons).
183
+ - `man({ "toolsetId": "team_mgmt", "topics": ["skills"] })` → how to manage `.minds/skills/*` (injection point, tone, heading levels, migration boundaries).
169
184
  - `man({ "toolsetId": "team_mgmt", "topics": ["priming"] })` → how to manage startup scripts under `.minds/priming/*`.
185
+ - `man({ "toolsetId": "team_mgmt", "topics": ["env"] })` → how to manage `.minds/env.*.md` (runtime-environment injection point, tone, heading levels).
186
+ - `man({ "toolsetId": "team_mgmt", "topics": ["toolsets"] })` → inspect the actually visible toolsets in the current installation/rtws and common grant patterns.
170
187
  - `man({ "toolsetId": "team_mgmt", "topics": ["permissions"] })` → how `read_dirs`/`write_dirs` and deny-lists work.
171
188
  - `man({ "toolsetId": "team_mgmt", "topics": ["troubleshooting"] })` → common failure modes and how to recover.
172
189
 
@@ -200,6 +217,20 @@ must cover (at minimum) the information that used to live there:
200
217
  - Explain tool exposure controls (whitelist/blacklist) and naming transforms (prefix/suffix).
201
218
  - Explain secret/env wiring patterns and operational troubleshooting (Problems + logs, restart,
202
219
  hot reload semantics).
220
+ - `!skills`:
221
+ - Explain `.minds/skills/*` as reusable team skill assets.
222
+ - Explain the boundary that a skill is prompt/guidance content, not a permission system.
223
+ - Explain when content should remain a skill versus being elevated into an app / toolset /
224
+ teammate contract.
225
+ - `!env`:
226
+ - Explain `.minds/env.*.md` as the place for current-rtws runtime-environment orientation, not
227
+ persona definition or a dump of repo-wide policy.
228
+ - Explain its boundary relative to `persona/knowledge/lessons`, skills, and priming.
229
+ - `!toolsets`:
230
+ - Explain that the visible toolsets include built-in toolsets, toolsets exposed by installed
231
+ apps, and MCP toolsets dynamically registered from `.minds/mcp.yaml`.
232
+ - Explain why this topic must be rendered from runtime state rather than maintained as a static
233
+ list in the design docs.
203
234
 
204
235
  ## Dynamic Loading from the Dominds Installation (Runtime Resources)
205
236
 
@@ -220,10 +251,29 @@ Recommended sources by topic:
220
251
  `dominds/main/llm/defaults.yaml` (via `__dirname` resolution in the backend build output).
221
252
  - Prefer reusing `LlmConfig.load()` and formatting its merged view, or adding a helper that returns
222
253
  both “defaults-only” and “merged” provider maps.
223
- - `man({ "toolsetId": "team_mgmt", "topics": ["toolsets"] })` (if added)
254
+ - `man({ "toolsetId": "team_mgmt", "topics": ["toolsets"] })`
224
255
  - Load from the in-memory registries at runtime (`listToolsets()` / `listTools()` in
225
256
  `dominds/main/tools/registry.ts`), rather than maintaining a separate list.
226
257
 
258
+ ### Why `toolsets` Must Stay a Dynamic Topic
259
+
260
+ - `toolsets` is not a stable inventory that should be hardcoded in a design document.
261
+ - The visible toolsets are jointly determined by:
262
+ - framework built-ins
263
+ - toolsets exposed by the currently installed Dominds apps
264
+ - MCP toolsets mapped at runtime from `.minds/mcp.yaml` `servers.<serverId>`
265
+ - The MCP portion is inherently **dynamic**: teams can add/remove servers, rename exposure, and
266
+ constrain tool exposure per rtws, so the resulting toolset set changes with the live installation
267
+ and workspace state.
268
+ - This is intentional. The design binds capability discovery to the actual runtime environment
269
+ instead of to a static document that will drift:
270
+ - it avoids maintaining a doomed-to-drift toolset list in the design docs
271
+ - it avoids misleading readers into treating MCP-derived toolsets as framework built-ins
272
+ - it ensures the team manager sees the capabilities that are truly available in this installation
273
+ and this rtws right now
274
+ - Therefore the design docs should explain the mechanism and rationale only; the current toolset list
275
+ belongs to runtime `man(...topics:["toolsets"])`.
276
+
227
277
  Keep these as **static/manual text** (not dynamically loaded):
228
278
 
229
279
  - High-level explanations, best practices, and “why” sections.
@@ -240,13 +290,17 @@ Startup script directories:
240
290
  Core principles:
241
291
 
242
292
  - Startup scripts are mapped into real dialog history; they are not read-only logs.
293
+ - Their runtime semantics are not “extra system prompt text”, but “restore reminders first, then replay records into dialog history where possible”.
294
+ - Therefore tone must follow the chosen record type: write `human_text_record` as what a user/requester says to the agent; write `agent_words_record` as what the agent has already said; use `agent_thought_record` only sparingly for internal reasoning traces.
295
+ - Some technical record types such as `ui_only_markdown_record` are persisted but do not become model-facing chat messages, so they should not be your main steering layer.
296
+ - The outer file structure should be “top-level frontmatter + repeated `### record <type>` blocks”; do not wrap the file in decorative `# Startup Script` / `## History` headings.
243
297
  - Team managers should treat them as editable startup playbooks.
244
298
  - Freely add/edit assistant or user messages, and fully rewrite scripts when workflows evolve.
245
299
 
246
300
  Recommended format:
247
301
 
248
302
  - Frontmatter (optional, recommended): metadata such as `title` and `applicableMemberIds`.
249
- - Message blocks (required): `### user` / `### assistant`, optionally fenced markdown blocks.
303
+ - Record blocks (required): `### record <type>` with the actual content inside the corresponding markdown/json block.
250
304
 
251
305
  Maintenance guidance:
252
306
 
@@ -488,14 +542,22 @@ Best practices:
488
542
 
489
543
  ## Managing `.minds/team/<member>/*.md` (agent minds)
490
544
 
491
- The runtime reads these on every dialog start:
545
+ At dialog start, the runtime reads that member’s `persona.*.md` / `knowledge.*.md` / `lessons.*.md`
546
+ assets.
492
547
 
493
- - `.minds/team/<id>/persona.md`
494
- - `.minds/team/<id>/knowledge.md`
495
- - `.minds/team/<id>/lessons.md`
548
+ - For exact language-file selection, fallback rules, injection order, and other current authoring
549
+ details, use `man({ "toolsetId": "team_mgmt", "topics": ["minds"] })`.
496
550
 
497
551
  See `dominds/main/minds/load.ts` (`readAgentMind()`).
498
552
 
553
+ Authoring rule (important):
554
+
555
+ - `persona.*.md` is spliced into that member's `role=system` prompt, so it should normally be written directly to the agent itself.
556
+ - In practice, prefer second-person "you" when defining responsibilities, boundaries, working style, and delivery expectations.
557
+ - Do not write `persona.*.md` as a third-person biography or as operator-facing documentation for a human/team manager.
558
+ - `knowledge.*.md` / `lessons.*.md` also end up in the member's system prompt, specifically under `## Knowledge` / `## Lessons`. `knowledge` is better for stable facts, indexes, conventions, and decision cues; `lessons` is better for reusable heuristics such as “if X happens, do Y, avoid Z”. Both should still be written to help that member act, not to narrate the member from the outside.
559
+ - Heading levels should follow the system-prompt wrapper too: the outer template already provides `## Persona` / `## Knowledge` / `## Lessons`, so bodies should usually start at `###` or plain bullets instead of repeating `#` / `##` or restating the wrapper title.
560
+
499
561
  Suggested structure:
500
562
 
501
563
  ```
@@ -513,6 +575,33 @@ Suggested structure:
513
575
  lessons.md
514
576
  ```
515
577
 
578
+ ## Managing `.minds/skills/*` (skill assets)
579
+
580
+ Design-level positioning:
581
+
582
+ - `.minds/skills/*` stores reusable team skill / operating-guidance assets.
583
+ - Its job is to capture “when to use this, how to do it, and where the boundary is”, not to grant
584
+ permissions.
585
+ - If a skill depends on scripts, privileged tools, MCP, external binaries, or reusable execution
586
+ capability, the design should usually elevate it into a Dominds app / toolset / teammate contract
587
+ rather than stopping at Markdown alone.
588
+ - This design doc intentionally limits itself to boundary and migration guidance. For current file
589
+ naming, injection semantics, heading levels, and language-file rules, use
590
+ `man({ "toolsetId": "team_mgmt", "topics": ["skills"] })`.
591
+
592
+ ## Managing `.minds/env.*.md` (runtime-environment notes)
593
+
594
+ Design-level positioning:
595
+
596
+ - `.minds/env.*.md` exists to describe stable facts about the current rtws runtime environment so
597
+ members can orient themselves quickly.
598
+ - It should not take on the role of persona definition, skill tutorial, giant operating manual, or
599
+ repo-wide policy dump.
600
+ - Those responsibilities belong, respectively, in `persona/knowledge/lessons`, skills, priming, or
601
+ the repo’s own policy files.
602
+ - This design doc only defines that boundary. For the current injection position, fallback behavior,
603
+ and authoring guidance, use `man({ "toolsetId": "team_mgmt", "topics": ["env"] })`.
604
+
516
605
  ## Bootstrap Policy: Shadow bootstrap members
517
606
 
518
607
  Preferred behavior for initial bootstrap:
@@ -42,6 +42,15 @@
42
42
  - 该手册与工具行为版本化,因此保持准确
43
43
  - 框架源代码树不应是团队配置格式被解释的"主要"地方。每个 rtws 可能具有不同的策略和默认值
44
44
 
45
+ ### 本文档的粒度边界(重要)
46
+
47
+ - 本文档只负责说明稳定的设计目标、概念边界、职责划分,以及为什么这样设计。
48
+ - 本文档**不应**承载过细的运行时规格,例如精确的注入顺序、当前回退规则、完整 topic 列表、动态枚举结果、逐字段渲染细节、或会随实现演进而频繁变化的 authoring 细则。
49
+ - 这类“当前实现到底怎么做”的信息,应统一以下列入口为准:
50
+ - `man({ "toolsetId": "team_mgmt" })`
51
+ - 对应实现代码与运行时校验
52
+ - 如果设计文档与运行时手册在“当前行为”上发生冲突,应默认以运行时手册为准,并回头修正文档,而不是让读者继续把设计文档当成运行时规格书。
53
+
45
54
  ## 当前问题陈述
46
55
 
47
56
  在典型部署中,我们通过通用 rtws 文件工具拒绝直接的 `.minds/` 访问:
@@ -137,7 +146,10 @@
137
146
  - `man({ "toolsetId": "team_mgmt", "topics": ["team"] })` → 如何管理 `.minds/team.yaml`(+ 模板)
138
147
  - `man({ "toolsetId": "team_mgmt", "topics": ["team", "member-properties"] })` → 列出支持的成员字段及其含义
139
148
  - `man({ "toolsetId": "team_mgmt", "topics": ["minds"] })` → 如何管理 `.minds/team/<id>/*.md`(persona/knowledge/lessons)
149
+ - `man({ "toolsetId": "team_mgmt", "topics": ["skills"] })` → 如何管理 `.minds/skills/*`(skills 的注入位置、口吻、标题层级、迁移边界)
140
150
  - `man({ "toolsetId": "team_mgmt", "topics": ["priming"] })` → 如何管理 `.minds/priming/*` 启动脚本(格式、维护、复用)
151
+ - `man({ "toolsetId": "team_mgmt", "topics": ["env"] })` → 如何管理 `.minds/env.*.md`(运行环境提示的注入位置、口吻、标题层级)
152
+ - `man({ "toolsetId": "team_mgmt", "topics": ["toolsets"] })` → 查看当前安装/rtws 下实际可见的 toolsets 及常见授权模式
141
153
  - `man({ "toolsetId": "team_mgmt", "topics": ["permissions"] })` → `read_dirs`/`write_dirs` 和拒绝列表如何工作
142
154
  - `man({ "toolsetId": "team_mgmt", "topics": ["troubleshooting"] })` → 常见故障模式及如何恢复
143
155
 
@@ -163,6 +175,16 @@
163
175
  - 解释 MCP 服务器如何映射到工具集(`<serverId>`)以及如何通过 `.minds/team.yaml` 授予这些工具集
164
176
  - 解释工具暴露控制(白名单/黑名单)和命名转换(前缀/后缀)
165
177
  - 解释密钥/env 接线模式和问题排查(问题 + 日志、重启、热重载语义)
178
+ - `!skills`:
179
+ - 解释 `.minds/skills/*` 属于可复用的团队技能资产
180
+ - 解释 skill 的职责边界:它是提示/指导资产,不是权限系统
181
+ - 解释何时应继续保留为 skill,何时应升级为 app / toolset / teammates contract
182
+ - `!env`:
183
+ - 解释 `.minds/env.*.md` 用于描述当前 rtws 的运行环境,而不是定义人格或复制仓库总规范
184
+ - 解释它与 `persona/knowledge/lessons`、skills、priming 的分工边界
185
+ - `!toolsets`:
186
+ - 解释当前可见 toolsets 包含内建 toolsets、已安装 apps 暴露的 toolsets、以及由 `.minds/mcp.yaml` 动态注册的 MCP toolsets
187
+ - 解释为什么该主题必须以运行时动态视图为准,而不是在设计文档里维护一份静态名单
166
188
 
167
189
  ## 从 Dominds 安装动态加载(运行时资源)
168
190
 
@@ -179,9 +201,23 @@
179
201
  - `man({ "toolsetId": "team_mgmt", "topics": ["llm", "builtin-defaults"] })`
180
202
  - 从运行时用于默认值的同一安装资源加载:`dominds/main/llm/defaults.yaml`(通过后端构建输出中的 `__dirname` 解析)
181
203
  - 优先重用 `LlmConfig.load()` 并格式化其合并视图,或添加一个返回"仅默认值"和"合并"提供商映射的助手
182
- - `man({ "toolsetId": "team_mgmt", "topics": ["toolsets"] })`(如果添加)
204
+ - `man({ "toolsetId": "team_mgmt", "topics": ["toolsets"] })`
183
205
  - 在运行时从内存中注册表加载(`dominds/main/tools/registry.ts` 中的 `listToolsets()` / `listTools()`),而不是维护单独的列表
184
206
 
207
+ ### 为什么 `toolsets` 必须是动态主题
208
+
209
+ - `toolsets` 不是一份稳定、可写死在设计文档里的清单。
210
+ - 当前可见的 toolsets 由三部分共同决定:
211
+ - 框架内建 toolsets
212
+ - 当前安装的 Dominds apps 所暴露的 toolsets
213
+ - `.minds/mcp.yaml` 中 `servers.<serverId>` 在运行时映射出来的 MCP toolsets
214
+ - 其中 MCP 部分天然是**动态的**:团队可以按 rtws 需要增删 server、调整命名、控制工具暴露;因此 toolset 集合会随当前安装状态与当前 rtws 配置而变化。
215
+ - 这样设计的原因,是把“能力发现”绑定到当前实际运行环境,而不是绑定到一份很快过时的静态文档:
216
+ - 避免设计文档维护一份注定漂移的 toolset 名单
217
+ - 避免读者误以为某个 MCP toolset 是框架内建常量
218
+ - 让团队管理者面对的始终是“此刻这个安装与这个 rtws 真实可用的能力”
219
+ - 因此,设计文档只需要解释机制与原因;“当前有哪些 toolsets”应由运行时 `man(...topics:[\"toolsets\"])` 动态呈现。
220
+
185
221
  将这些保持为**静态/手册文本**(而非动态加载):
186
222
 
187
223
  - 高级解释、最佳实践和"为什么"部分
@@ -197,13 +233,17 @@
197
233
  核心原则:
198
234
 
199
235
  - 启动脚本会映射为真实对话历史;它不是只读日志,而是可编辑的启动引导剧本。
236
+ - 其运行时语义不是 system prompt,而是“创建对话时先恢复 reminders,再把 records 尽可能回放成对话历史消息”。
237
+ - 因此内容口吻必须跟着 record 类型走:`human_text_record` 写成用户/诉请者对智能体说的话;`agent_words_record` 写成智能体已经说出口的话;`agent_thought_record` 仅适合非常克制地承载内部推理痕迹。
238
+ - `ui_only_markdown_record` 等部分技术 record 不会变成喂给模型的 chat message,因此不能拿它们充当主要行为引导。
239
+ - 外层结构应是“顶层 frontmatter + 多个 `### record <type>` 块”;不要再包一层 `# 启动脚本` / `## 历史` 这类装饰标题。
200
240
  - 团队管理者应鼓励按业务场景维护脚本,并可直接增删改 assistant/user 消息内容。
201
241
  - 允许完全重写脚本以匹配新的协作模式、质量标准和语言风格。
202
242
 
203
243
  推荐格式:
204
244
 
205
245
  - frontmatter(可选但推荐):`title`、`applicableMemberIds` 等元数据。
206
- - 消息块(必填):使用 `### user` / `### assistant`,支持 fenced markdown 块。
246
+ - record 块(必填):使用 `### record <type>`;具体内容放在对应的 markdown/json block 中。
207
247
 
208
248
  维护建议:
209
249
 
@@ -421,14 +461,20 @@ members:
421
461
 
422
462
  ## 管理 `.minds/team/<member>/*.md`(智能体心智)
423
463
 
424
- 运行时在每次对话开始时读取这些:
464
+ 运行时在每次对话开始时会读取该成员的 `persona.*.md` / `knowledge.*.md` / `lessons.*.md` 资产。
425
465
 
426
- - `.minds/team/<id>/persona.md`
427
- - `.minds/team/<id>/knowledge.md`
428
- - `.minds/team/<id>/lessons.md`
466
+ - 具体语言文件选择、回退规则、注入顺序与其它 authoring 细则,请以 `man({ "toolsetId": "team_mgmt", "topics": ["minds"] })` 为准。
429
467
 
430
468
  见 `dominds/main/minds/load.ts`(`readAgentMind()`)。
431
469
 
470
+ 写法约束(重要):
471
+
472
+ - `persona.*.md` 会被拼进该成员的 `role=system` 提示,因此默认应该直接写给“这个成员智能体本人”。
473
+ - 也就是说,`persona.*.md` 应优先使用第二人称“你”来规定职责边界、工作方式与交付标准。
474
+ - 不要把 `persona.*.md` 写成第三人称人物简介,不要把它当成给团队管理员/人类读者的说明书,也不要使用“祂”这类旁白口吻。
475
+ - `knowledge.*.md` / `lessons.*.md` 也同样会进入该成员的系统提示,分别落在 `## 知识` / `## 经验`。`knowledge` 更适合稳定事实、索引、约定、判断依据;`lessons` 更适合复用型经验规则(例如“遇到 X 信号先做 Y,不要做 Z”)。两者都应以“帮助该成员当下工作”为目标,而不是面向旁观者写注释。
476
+ - 标题层级也要按 system prompt 模板来写:系统外层已经自动提供 `## 角色设定` / `## 知识` / `## 经验`,所以正文通常应从 `###` 或普通 bullet 开始,不要再写 `#` / `##`,也不要把文件名或这些章节名重复当标题。
477
+
432
478
  建议的结构:
433
479
 
434
480
  ```
@@ -446,6 +492,24 @@ members:
446
492
  lessons.md
447
493
  ```
448
494
 
495
+ ## 管理 `.minds/skills/*`(技能资产)
496
+
497
+ 设计层面的定位:
498
+
499
+ - `.minds/skills/*` 用来承载可复用的团队技能/操作指导资产。
500
+ - 它的重点是“什么时候用、怎么做、边界是什么”,而不是授予权限。
501
+ - 如果某个 skill 需要脚本、专有工具、MCP、外部二进制、或稳定复用的执行能力,那么设计上通常更适合上升为 Dominds app / toolset / teammates contract,而不应只停留在 markdown 文案。
502
+ - 设计文档在这里仅说明边界与迁移方向;具体文件命名、注入位置、标题层级、语言文件选择等,以运行时 `man({ "toolsetId": "team_mgmt", "topics": ["skills"] })` 为准。
503
+
504
+ ## 管理 `.minds/env.*.md`(运行环境说明)
505
+
506
+ 设计层面的定位:
507
+
508
+ - `.minds/env.*.md` 用来描述“当前 rtws 运行环境”的稳定背景信息,帮助成员理解所处环境。
509
+ - 它不应承担 persona 定义、skill 教程、团队制度大全、或仓库全局规范汇总的职责。
510
+ - 这类内容按分工应分别落在 `persona/knowledge/lessons`、skills、priming、或仓库自己的规范文件中。
511
+ - 设计文档在这里仅强调用途边界;具体注入位置、回退规则、标题建议等,以运行时 `man({ "toolsetId": "team_mgmt", "topics": ["env"] })` 为准。
512
+
449
513
  ## 引导策略:影子成员引导
450
514
 
451
515
  初始引导的首选行为:
@@ -10,6 +10,7 @@ const log_1 = require("../../log");
10
10
  const load_1 = require("../../minds/load");
11
11
  const persistence_1 = require("../../persistence");
12
12
  const driver_messages_1 = require("../../runtime/driver-messages");
13
+ const interjection_pause_stop_1 = require("../../runtime/interjection-pause-stop");
13
14
  const reply_prompt_copy_1 = require("../../runtime/reply-prompt-copy");
14
15
  const work_language_1 = require("../../runtime/work-language");
15
16
  const client_1 = require("../client");
@@ -40,6 +41,30 @@ async function buildReplyToolReminderPrompt(args) {
40
41
  }),
41
42
  });
42
43
  }
44
+ async function loadFreshSuspensionStatusFromPersistence(dialog) {
45
+ const q4h = await persistence_1.DialogPersistence.loadQuestions4HumanState(dialog.id, dialog.status);
46
+ const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(dialog.id, dialog.status);
47
+ const hasQ4H = q4h.length > 0;
48
+ const hasSubdialogs = pendingSubdialogs.length > 0;
49
+ return {
50
+ q4h: hasQ4H,
51
+ subdialogs: hasSubdialogs,
52
+ blockingSubdialogs: hasSubdialogs,
53
+ canDrive: !hasQ4H && !hasSubdialogs,
54
+ };
55
+ }
56
+ function buildDisplayStateFromSuspensionStatus(args) {
57
+ if (args.q4h && args.subdialogs) {
58
+ return { kind: 'blocked', reason: { kind: 'needs_human_input_and_subdialogs' } };
59
+ }
60
+ if (args.q4h) {
61
+ return { kind: 'blocked', reason: { kind: 'needs_human_input' } };
62
+ }
63
+ if (args.subdialogs) {
64
+ return { kind: 'blocked', reason: { kind: 'waiting_for_subdialogs' } };
65
+ }
66
+ return { kind: 'idle_waiting_user' };
67
+ }
43
68
  async function loadPendingDiagnosticsSnapshot(args) {
44
69
  const ownerDialogIdObj = new dialog_1.DialogID(args.ownerDialogId, args.rootId);
45
70
  try {
@@ -304,6 +329,8 @@ async function executeDriveRound(args) {
304
329
  let subdialogReplyTarget;
305
330
  let activeTellaskReplyDirective;
306
331
  let activePromptWasReplyToolReminder = false;
332
+ let shouldPauseAfterLocalUserInterjection = false;
333
+ let resumeFromInterjectionPause = false;
307
334
  const allowResumeFromInterrupted = driveOptions?.allowResumeFromInterrupted === true || humanPrompt?.origin === 'user';
308
335
  const driveSource = resolveDriveRequestSource(humanPrompt, driveOptions);
309
336
  try {
@@ -340,6 +367,11 @@ async function executeDriveRound(args) {
340
367
  });
341
368
  return;
342
369
  }
370
+ resumeFromInterjectionPause =
371
+ humanPrompt === undefined &&
372
+ allowResumeFromInterrupted &&
373
+ latest?.executionMarker?.kind === 'interrupted' &&
374
+ (0, interjection_pause_stop_1.isUserInterjectionPauseStopReason)(latest.executionMarker.reason);
343
375
  }
344
376
  catch (err) {
345
377
  log_1.log.warn('kernel-driver failed to check execution facts before drive; proceeding best-effort', err, {
@@ -382,10 +414,39 @@ async function executeDriveRound(args) {
382
414
  return;
383
415
  }
384
416
  }
385
- const suspension = await dialog.getSuspensionStatus();
417
+ // WARNING:
418
+ // `allowResumeFromInterrupted` covers multiple stop reasons, but the interjection-pause case
419
+ // is semantically special. Clicking Continue here does NOT mean "blindly clear stopped and
420
+ // drive". We must re-read the fresh persistence facts first because there are three distinct
421
+ // true-source cases behind the same visible stopped panel:
422
+ // - no active reply obligation / not suspended anymore -> continue real driving now
423
+ // - active reply obligation + suspended -> restore true blocked state
424
+ // - active reply obligation + still proceeding entitlement (for example queued upNext) ->
425
+ // continue real driving now
426
+ //
427
+ // Do not refactor this branch using only `displayState` or only the previous interrupted
428
+ // marker. The correct behavior emerges from combining fresh blocker facts, queued prompt
429
+ // state, and the deferred reply reassertion logic elsewhere.
430
+ const suspension = resumeFromInterjectionPause
431
+ ? await loadFreshSuspensionStatusFromPersistence(dialog)
432
+ : await dialog.getSuspensionStatus();
386
433
  const queuedPrompt = dialog.peekUpNext();
387
434
  const queuedSubdialogPromptCanResume = dialog instanceof dialog_1.SubDialog && queuedPrompt !== undefined;
388
435
  if (!suspension.canDrive && !queuedSubdialogPromptCanResume) {
436
+ if (resumeFromInterjectionPause) {
437
+ const restoredState = buildDisplayStateFromSuspensionStatus({
438
+ q4h: suspension.q4h,
439
+ subdialogs: suspension.subdialogs,
440
+ });
441
+ await (0, dialog_display_state_1.setDialogDisplayState)(dialog.id, restoredState);
442
+ log_1.log.debug('kernel-driver continue after interjection pause restored true suspended state from fresh persistence facts', undefined, {
443
+ dialogId: dialog.id.valueOf(),
444
+ restoredState,
445
+ waitingQ4H: suspension.q4h,
446
+ waitingSubdialogs: suspension.subdialogs,
447
+ });
448
+ return;
449
+ }
389
450
  const lastTrigger = dialog_global_registry_1.globalDialogRegistry.getLastDriveTrigger(dialog.id.rootId);
390
451
  const lastTriggerAgeMs = lastTrigger !== undefined ? Math.max(0, Date.now() - lastTrigger.emittedAtMs) : undefined;
391
452
  log_1.log.debug('kernel-driver skip queued auto-drive while dialog is suspended', undefined, {
@@ -413,6 +474,15 @@ async function executeDriveRound(args) {
413
474
  });
414
475
  return;
415
476
  }
477
+ if (resumeFromInterjectionPause) {
478
+ log_1.log.debug('kernel-driver continue after interjection pause passed fresh fact scan and will keep driving', undefined, {
479
+ dialogId: dialog.id.valueOf(),
480
+ waitingQ4H: suspension.q4h,
481
+ waitingSubdialogs: suspension.subdialogs,
482
+ hasQueuedUpNext: dialog.hasUpNext(),
483
+ queuedSubdialogPromptCanResume,
484
+ });
485
+ }
416
486
  }
417
487
  const minds = await (0, load_1.loadAgentMinds)(dialog.agentId, dialog);
418
488
  const policy = (0, guardrails_1.buildKernelDriverPolicy)({
@@ -508,6 +578,14 @@ async function executeDriveRound(args) {
508
578
  dlg: dialog,
509
579
  prompt: effectivePrompt,
510
580
  });
581
+ // Only park into the special interjection stopped-panel state when this user turn has
582
+ // suppressed a still-pending inter-dialog reply obligation that must be reasserted later.
583
+ // User interjections without a parked original task should simply finish and fall back to the
584
+ // dialog's true underlying state, without showing the special stopped panel.
585
+ shouldPauseAfterLocalUserInterjection =
586
+ effectivePrompt?.origin === 'user' &&
587
+ replyGuidance.suppressInterDialogReplyGuidance &&
588
+ replyGuidance.deferredReplyReassertionDirective !== undefined;
511
589
  activeTellaskReplyDirective = replyGuidance.activeReplyDirective;
512
590
  activePromptWasReplyToolReminder = isReplyToolReminderPrompt(effectivePrompt);
513
591
  if (effectivePrompt && effectivePrompt.userLanguageCode) {
@@ -573,28 +651,64 @@ async function executeDriveRound(args) {
573
651
  });
574
652
  }
575
653
  else {
576
- if (typeof driveResult.lastAssistantSayingGenseq !== 'number' ||
577
- !Number.isFinite(driveResult.lastAssistantSayingGenseq) ||
578
- driveResult.lastAssistantSayingGenseq <= 0) {
579
- throw new Error(`Subdialog response supply invariant violation: missing lastAssistantSayingGenseq for dialog=${dialog.id.valueOf()}`);
580
- }
581
- const responseGenseq = Math.floor(driveResult.lastAssistantSayingGenseq);
582
- const directFallbackCallId = `direct-fallback-${(0, id_1.generateShortId)()}`;
583
- let supplied = false;
584
- if (subdialogReplyTarget) {
585
- supplied = await (0, subdialog_1.supplySubdialogResponseToSpecificCallerIfPendingV2)({
586
- subdialog: dialog,
587
- responseText: driveResult.lastAssistantSayingContent,
588
- responseGenseq,
589
- target: subdialogReplyTarget,
590
- deliveryMode: 'direct_fallback',
591
- replyResolution: {
592
- callId: directFallbackCallId,
593
- replyCallName: activeTellaskReplyDirective.expectedReplyCallName,
594
- },
595
- scheduleDrive: args.scheduleDrive,
654
+ if (!activePromptWasReplyToolReminder) {
655
+ const language = (0, work_language_1.getWorkLanguage)();
656
+ followUp = {
657
+ prompt: await buildReplyToolReminderPrompt({
658
+ dlg: dialog,
659
+ directive: activeTellaskReplyDirective,
660
+ language,
661
+ }),
662
+ msgId: (0, id_1.generateShortId)(),
663
+ grammar: 'markdown',
664
+ origin: 'runtime',
665
+ userLanguageCode: language,
666
+ tellaskReplyDirective: activeTellaskReplyDirective,
667
+ subdialogReplyTarget,
668
+ };
669
+ log_1.log.debug('kernel-driver queued subdialog replyTellask reminder after plain reply', undefined, {
670
+ dialogId: dialog.id.valueOf(),
671
+ targetCallId: activeTellaskReplyDirective.targetCallId,
672
+ targetOwnerDialogId: subdialogReplyTarget?.ownerDialogId,
596
673
  });
597
- if (!supplied) {
674
+ }
675
+ else {
676
+ if (typeof driveResult.lastAssistantSayingGenseq !== 'number' ||
677
+ !Number.isFinite(driveResult.lastAssistantSayingGenseq) ||
678
+ driveResult.lastAssistantSayingGenseq <= 0) {
679
+ throw new Error(`Subdialog response supply invariant violation: missing lastAssistantSayingGenseq for dialog=${dialog.id.valueOf()}`);
680
+ }
681
+ const responseGenseq = Math.floor(driveResult.lastAssistantSayingGenseq);
682
+ const directFallbackCallId = `direct-fallback-${(0, id_1.generateShortId)()}`;
683
+ let supplied = false;
684
+ if (subdialogReplyTarget) {
685
+ supplied = await (0, subdialog_1.supplySubdialogResponseToSpecificCallerIfPendingV2)({
686
+ subdialog: dialog,
687
+ responseText: driveResult.lastAssistantSayingContent,
688
+ responseGenseq,
689
+ target: subdialogReplyTarget,
690
+ deliveryMode: 'direct_fallback',
691
+ replyResolution: {
692
+ callId: directFallbackCallId,
693
+ replyCallName: activeTellaskReplyDirective.expectedReplyCallName,
694
+ },
695
+ scheduleDrive: args.scheduleDrive,
696
+ });
697
+ if (!supplied) {
698
+ supplied = await (0, subdialog_1.supplySubdialogResponseToAssignedCallerIfPendingV2)({
699
+ subdialog: dialog,
700
+ responseText: driveResult.lastAssistantSayingContent,
701
+ responseGenseq,
702
+ deliveryMode: 'direct_fallback',
703
+ replyResolution: {
704
+ callId: directFallbackCallId,
705
+ replyCallName: activeTellaskReplyDirective.expectedReplyCallName,
706
+ },
707
+ scheduleDrive: args.scheduleDrive,
708
+ });
709
+ }
710
+ }
711
+ else {
598
712
  supplied = await (0, subdialog_1.supplySubdialogResponseToAssignedCallerIfPendingV2)({
599
713
  subdialog: dialog,
600
714
  responseText: driveResult.lastAssistantSayingContent,
@@ -607,35 +721,21 @@ async function executeDriveRound(args) {
607
721
  scheduleDrive: args.scheduleDrive,
608
722
  });
609
723
  }
610
- }
611
- else {
612
- supplied = await (0, subdialog_1.supplySubdialogResponseToAssignedCallerIfPendingV2)({
613
- subdialog: dialog,
614
- responseText: driveResult.lastAssistantSayingContent,
615
- responseGenseq,
616
- deliveryMode: 'direct_fallback',
617
- replyResolution: {
618
- callId: directFallbackCallId,
619
- replyCallName: activeTellaskReplyDirective.expectedReplyCallName,
620
- },
621
- scheduleDrive: args.scheduleDrive,
622
- });
623
- }
624
- if (!supplied && subdialogReplyTarget) {
625
- const diagnostics = await loadPendingDiagnosticsSnapshot({
626
- rootId: dialog.id.rootId,
627
- ownerDialogId: subdialogReplyTarget.ownerDialogId,
628
- expectedSubdialogId: dialog.id.selfId,
629
- status: dialog.status,
630
- });
631
- log_1.log.debug('kernel-driver failed to supply subdialog response to specific caller', undefined, {
632
- calleeId: dialog.id.valueOf(),
633
- targetOwner: subdialogReplyTarget.ownerDialogId,
634
- targetOwnerDialogId: subdialogReplyTarget.ownerDialogId,
635
- targetCallType: subdialogReplyTarget.callType,
636
- targetCallId: subdialogReplyTarget.callId,
637
- diagnostics,
638
- });
724
+ if (!supplied && subdialogReplyTarget) {
725
+ const diagnostics = await loadPendingDiagnosticsSnapshot({
726
+ rootId: dialog.id.rootId,
727
+ ownerDialogId: subdialogReplyTarget.ownerDialogId,
728
+ expectedSubdialogId: dialog.id.selfId,
729
+ status: dialog.status,
730
+ });
731
+ log_1.log.debug('kernel-driver failed to supply subdialog response to specific caller', undefined, {
732
+ calleeId: dialog.id.valueOf(),
733
+ targetOwnerDialogId: subdialogReplyTarget.ownerDialogId,
734
+ targetCallType: subdialogReplyTarget.callType,
735
+ targetCallId: subdialogReplyTarget.callId,
736
+ diagnostics,
737
+ });
738
+ }
639
739
  }
640
740
  }
641
741
  }
@@ -716,6 +816,26 @@ async function executeDriveRound(args) {
716
816
  },
717
817
  });
718
818
  }
819
+ if (shouldPauseAfterLocalUserInterjection &&
820
+ !interruptedBySignal &&
821
+ followUp === undefined &&
822
+ driveResult?.lastAssistantSayingContent !== null) {
823
+ const pauseReason = (0, interjection_pause_stop_1.buildUserInterjectionPauseStopReason)();
824
+ await (0, dialog_display_state_1.setDialogDisplayState)(dialog.id, {
825
+ kind: 'stopped',
826
+ reason: pauseReason,
827
+ continueEnabled: true,
828
+ });
829
+ (0, dialog_display_state_1.broadcastDisplayStateMarker)(dialog.id, {
830
+ kind: 'interrupted',
831
+ reason: pauseReason,
832
+ });
833
+ log_1.log.debug('kernel-driver paused original task after local user interjection reply', undefined, {
834
+ dialogId: dialog.id.valueOf(),
835
+ rootId: dialog.id.rootId,
836
+ selfId: dialog.id.selfId,
837
+ });
838
+ }
719
839
  }
720
840
  catch (error) {
721
841
  tailError = error;
@@ -6,6 +6,8 @@ exports.buildReplyObligationSuppressionGuide = buildReplyObligationSuppressionGu
6
6
  exports.buildReplyObligationReassertionPrompt = buildReplyObligationReassertionPrompt;
7
7
  const dialog_1 = require("../../dialog");
8
8
  const dialog_instance_registry_1 = require("../../dialog-instance-registry");
9
+ const persistence_1 = require("../../persistence");
10
+ const interjection_pause_stop_1 = require("../../runtime/interjection-pause-stop");
9
11
  const reply_prompt_copy_1 = require("../../runtime/reply-prompt-copy");
10
12
  const work_language_1 = require("../../runtime/work-language");
11
13
  const tellask_special_1 = require("./tellask-special");
@@ -81,6 +83,17 @@ function buildPromptContentWithExactReplyToolName(args) {
81
83
  return `${note}\n\n${args.prompt.content}`;
82
84
  }
83
85
  async function shouldSuppressInterDialogReplyGuidanceForUserInterjection(args) {
86
+ // WARNING:
87
+ // This suppression decision is not a cosmetic prompt tweak. It is one leg of the full
88
+ // interjection-pause state machine:
89
+ // 1. user interjection suppresses the live reply obligation here;
90
+ // 2. `flow.ts` answers locally and parks the original task in a resumable stopped state;
91
+ // 3. manual Continue later decides from fresh persistence facts whether the dialog should stay
92
+ // blocked or resume real driving.
93
+ //
94
+ // Do not "simplify" this into a pure display-state check or a pure pending-subdialog check.
95
+ // The business anchor is the deferred reply reassertion, while the paused execution marker keeps
96
+ // repeated interjection turns behaving as local side conversation until explicit Continue.
84
97
  const prompt = args.prompt;
85
98
  if (!prompt) {
86
99
  return false;
@@ -91,7 +104,18 @@ async function shouldSuppressInterDialogReplyGuidanceForUserInterjection(args) {
91
104
  if (prompt.tellaskReplyDirective !== undefined) {
92
105
  return false;
93
106
  }
94
- return await args.dlg.hasPendingSubdialogs();
107
+ const latest = await persistence_1.DialogPersistence.loadDialogLatest(args.dlg.id, args.dlg.status);
108
+ if (latest?.deferredReplyReassertion?.reason === 'user_interjection_while_pending_subdialog') {
109
+ return true;
110
+ }
111
+ if (latest?.executionMarker?.kind === 'interrupted' &&
112
+ (0, interjection_pause_stop_1.isUserInterjectionPauseStopReason)(latest.executionMarker.reason)) {
113
+ return true;
114
+ }
115
+ // Use strict persistence reads here. This branch changes business behavior, so a read failure
116
+ // must loud-fail the round instead of being silently treated as "pending subdialogs exist".
117
+ const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(args.dlg.id, args.dlg.status);
118
+ return pendingSubdialogs.length > 0;
95
119
  }
96
120
  async function resolvePromptReplyGuidance(args) {
97
121
  const prompt = args.prompt;
@@ -140,8 +140,8 @@ async function deliverTellaskBackReplyFromDirective(args) {
140
140
  // the dialog running `replyTellaskBack` is the ask-back responder, while
141
141
  // directive.targetDialogId points to the ask-back requester that must receive the canonical
142
142
  // tellaskBack result. Keep those roles explicit, otherwise it is very easy to accidentally
143
- // write the same business result once from the reply-tool path and then again from a fallback
144
- // path that treats the responder's plain assistant words as if they were the canonical reply.
143
+ // write the same business result twice by confusing the responder's local plaintext with the
144
+ // canonical upstream delivery that must come only from an explicit reply tool call.
145
145
  const rootDialog = args.replyingDialog instanceof dialog_1.RootDialog
146
146
  ? args.replyingDialog
147
147
  : args.replyingDialog instanceof dialog_1.SubDialog
@@ -910,9 +910,6 @@ function findDeliveredTellaskBackReplyOnAskBackRequester(args) {
910
910
  return undefined;
911
911
  }
912
912
  async function extractAskBackResponderPlaintextFallback(args) {
913
- // This fallback is intentionally second-class: it exists only for legacy/plain-reply flows
914
- // where no explicit `replyTellaskBack` canonical result has been delivered. It must never
915
- // compete with or overwrite an already delivered canonical tellaskBack result.
916
913
  try {
917
914
  return extractLastAssistantResponse(args.responderDialog.msgs, 'Supdialog completed without producing output.');
918
915
  }
@@ -1253,6 +1250,7 @@ async function executeTellaskCall(dlg, mentionList, body, callId, callbacks, opt
1253
1250
  tellaskContent: body,
1254
1251
  responseBody: responseText,
1255
1252
  status: 'completed',
1253
+ deliveryMode: 'direct_fallback',
1256
1254
  language: (0, work_language_1.getWorkLanguage)(),
1257
1255
  });
1258
1256
  askBackRequesterDialog.setSuspensionState('resumed');
@@ -1553,7 +1553,7 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
1553
1553
  // Duplicate final results are not harmless transcript noise. They mean two different program
1554
1554
  // paths both believed they owned the same business-level completion fact for one callId.
1555
1555
  // In ask-back flows this usually points to identity confusion between requester/responder or
1556
- // canonical reply-tool delivery versus fallback plaintext synthesis. We fail fast here so the
1556
+ // canonical reply-tool delivery versus another mistaken write path. We fail fast here so the
1557
1557
  // second writer keeps its own stack trace instead of silently corrupting the dialog transcript.
1558
1558
  const err = new Error(`${args.kind} duplicate callId invariant violation: rootId=${args.dialog.id.rootId} selfId=${args.dialog.id.selfId} ` +
1559
1559
  `callId=${args.callId} callName=${args.callName} existingCourse=${args.existingCourse} ` +
@@ -0,0 +1,5 @@
1
+ import type { DialogInterruptionReason } from '@longrun-ai/kernel/types/display-state';
2
+ export declare function buildUserInterjectionPauseStopReason(): Extract<DialogInterruptionReason, {
3
+ kind: 'system_stop';
4
+ }>;
5
+ export declare function isUserInterjectionPauseStopReason(reason: DialogInterruptionReason | undefined): boolean;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildUserInterjectionPauseStopReason = buildUserInterjectionPauseStopReason;
4
+ exports.isUserInterjectionPauseStopReason = isUserInterjectionPauseStopReason;
5
+ const USER_INTERJECTION_PAUSE_STOP_DETAIL = 'user_interjection_pause_resume_original_task';
6
+ // WARNING:
7
+ // This special stop reason is only a UI/run-control projection for "user interjected, and there
8
+ // is still an original task parked for explicit Continue". It is intentionally encoded as
9
+ // `system_stop`, but it does NOT mean the same thing as ordinary system-stop failure semantics.
10
+ //
11
+ // Not every user interjection should use this reason. If there is no parked original task to
12
+ // resume afterwards, the interjection should simply complete and the dialog should fall back to
13
+ // its true underlying state without showing this stopped panel.
14
+ //
15
+ // Do not change this file in isolation. The complete behavior depends on coordinated logic across:
16
+ // - `reply-guidance.ts` suppressing upstream reply obligation during interjection chat
17
+ // - `flow.ts` parking after the local reply, then re-running fresh-fact resume
18
+ // - `dialog-display-state.ts` preserving this paused projection until explicit Continue
19
+ // - `websocket-handler.ts` treating Continue as "resume attempt" rather than immediate success
20
+ //
21
+ // Reading only this stop reason or only `displayState.kind === 'stopped'` gives an incomplete and
22
+ // often wrong mental model.
23
+ function buildUserInterjectionPauseStopReason() {
24
+ return {
25
+ kind: 'system_stop',
26
+ detail: USER_INTERJECTION_PAUSE_STOP_DETAIL,
27
+ i18nStopReason: {
28
+ zh: '插话已处理;原任务已暂停。点击“继续”恢复原任务,继续发送新消息则继续这段临时对话。',
29
+ en: 'Interjection handled; the original task is paused. Click Continue to resume it, or send another message to keep this temporary side conversation going.',
30
+ },
31
+ };
32
+ }
33
+ function isUserInterjectionPauseStopReason(reason) {
34
+ return reason?.kind === 'system_stop' && reason.detail === USER_INTERJECTION_PAUSE_STOP_DETAIL;
35
+ }
@@ -102,6 +102,15 @@ function emitRunControlRefresh(reason) {
102
102
  });
103
103
  }
104
104
  function buildResumeIneligibleMessage(latest) {
105
+ // WARNING:
106
+ // `resume_dialog` eligibility is intentionally based on the freshly healed projection, not on a
107
+ // naive local check of raw blocker facts. In particular, the paused-interjection stopped state
108
+ // must remain resumable here so the user can explicitly press Continue even while the underlying
109
+ // dialog may still be blocked.
110
+ //
111
+ // The actual outcome of that Continue attempt is decided later in `flow.ts` from fresh facts:
112
+ // it may restore `blocked`, or it may immediately continue driving. Do not reinterpret a
113
+ // resumable stopped state here as "guaranteed to run now".
105
114
  const state = latest?.displayState;
106
115
  if (!state) {
107
116
  return {
@@ -1288,6 +1297,11 @@ async function handleResumeDialog(ws, packet) {
1288
1297
  }
1289
1298
  const dialogIdObj = new dialog_1.DialogID(dialog.selfId, dialog.rootId);
1290
1299
  const latest = await (0, dialog_display_state_1.refreshRunControlProjectionFromPersistenceFacts)(dialogIdObj, 'resume_dialog');
1300
+ // WARNING:
1301
+ // Passing this gate only means "a manual Continue attempt is allowed". It does not mean the
1302
+ // dialog is guaranteed to re-enter proceeding immediately. For the paused-interjection flow, the
1303
+ // resumed drive itself performs a second fresh-fact decision and may land in true `blocked`
1304
+ // instead of proceeding.
1291
1305
  if (!(0, dialog_display_state_1.isDialogLatestResumable)(latest)) {
1292
1306
  const ineligible = buildResumeIneligibleMessage(latest);
1293
1307
  log.warn('resume_dialog rejected after fresh fact scan', undefined, {
@@ -3429,14 +3429,15 @@ function renderMindsManual(language) {
3429
3429
  return (fmtHeader('.minds/team/<id>/*') +
3430
3430
  fmtList([
3431
3431
  '推荐实践(建议默认采用):每个 `members.<id>` 同时维护 `persona.*.md` / `knowledge.*.md` / `lessons.*.md`,把角色设定、领域知识、经验教训分层管理。',
3432
- '最小要求:每个 `members.<id>` 建议至少提供 `persona.*.md`(否则该成员将缺少可发现的角色设定;具体忽略/回退/报错行为以当前实现为准)。',
3433
- 'persona.*.md:角色设定(稳定的工作方式与职责)。',
3432
+ '共同去向(按当前实现):这三类文件会在每次对话开始时按工作语言读取,并分别拼进 system prompt 的 `## 角色设定` / `## 知识` / `## 经验` 章节;它们是写给“当前成员智能体自己”看的长期提示,不是给团队管理者/人类旁观者读的人物简介。',
3433
+ '最小要求:每个 `members.<id>` 建议至少提供 `persona.*.md`。当前实现中,缺失 persona 时会回退到内置默认 persona 文案;缺失或空白的 knowledge/lessons 会在系统提示中以本地化的“无”占位显示。',
3434
+ 'persona.*.md:角色设定(稳定的工作方式与职责)。它会进入该成员的 `role=system` 提示,因此默认应直接写给该智能体本人,使用第二人称“你”来规定职责、边界与工作方式;不要把它写成第三人称人物简介,更不要使用“祂”这类旁白口吻。',
3435
+ 'knowledge.*.md:领域知识。它会进入 `## 知识`,适合写“当前成员在该职责下反复要用到的稳定事实 / 索引 / 约定 / 判断依据”;口吻可用第二人称提示,或零人称资料式 bullet,但目标始终是帮助该成员立刻行动,不是对外介绍这个成员。',
3436
+ 'lessons.*.md:经验教训。它会进入 `## 经验`,适合写成可复用的行为准则,例如“遇到什么信号 -> 先做什么 / 不要做什么 -> 为什么”;不要写成任务流水账、会议纪要或第三人称成长故事。',
3434
3437
  '若该成员承担团队管理职责(尤其获得 `team_mgmt`),其 `persona.*.md` 必须明确写出:执行任何团队管理操作前先查看 `man({ "toolsetId": "team_mgmt" })` 的相关章节,并按手册标准做法维护 `.minds/**` 团队心智资产。',
3435
- 'knowledge.*.md:领域知识(可维护)。',
3436
- 'lessons.*.md:经验教训(可维护)。',
3437
- '写法约束:`persona/knowledge/lessons` 文件里不要再写重复的总标题。系统提示模板会自动添加:`## 角色设定` / `## 知识` / `## 经验`(英文模板对应 `## Persona` / `## Knowledge` / `## Lessons`)。',
3438
+ '语言选择(按当前实现):优先读取 `persona.zh.md` / `knowledge.zh.md` / `lessons.zh.md` 这类工作语言文件,其次才回退到无语言后缀的 `persona.md` / `knowledge.md` / `lessons.md`;不会跨语言回退到另一种语言文件。',
3439
+ '标题层级约束:`persona/knowledge/lessons` 文件里不要再写重复的总标题。系统提示模板会自动添加:`## 角色设定` / `## 知识` / `## 经验`(英文模板对应 `## Persona` / `## Knowledge` / `## Lessons`)。因此正文通常应从 `###` 小节或普通 bullet 开始,而不是再写 `#` / `##`,也不要再把文件名或“角色设定/知识/经验”重复当标题写一遍。',
3438
3440
  'Codex provider 专属能力:若该成员使用 `provider: codex`,可在 `persona.*.md` 中单独写一行 `@codex-system-prompt`,把 stock Codex 内置系统提示拼接进当前 persona;需要固定某个模板版本时写 `@codex-system-prompt:<model>`。建议把这行放在文件最前面,再继续写本团队自己的角色边界与交付要求。',
3439
- '语言文件命名:优先按工作语言提供 `persona.zh.md` / `persona.en.md` 等;是否回退到 `persona.md`/其他语言版本,以当前实现为准。',
3440
3441
  ]) +
3441
3442
  fmtCodeBlock('text', [
3442
3443
  '.minds/',
@@ -3450,12 +3451,12 @@ function renderMindsManual(language) {
3450
3451
  '@codex-system-prompt',
3451
3452
  '',
3452
3453
  '### 核心身份',
3453
- '- 专业程序员,负责按规格完成代码开发。',
3454
+ '- 你是专业程序员,负责按规格完成代码开发。',
3454
3455
  '### 工作边界',
3455
- '- 不负责需求分析或产品策略决策。',
3456
- '- 只根据已确认的开发规格进行实现与重构。',
3456
+ '- 你不负责需求分析或产品策略决策。',
3457
+ '- 你只根据已确认的开发规格进行实现与重构。',
3457
3458
  '### 交付标准',
3458
- '- 输出可运行代码,并附关键验证步骤。',
3459
+ '- 你要输出可运行代码,并附关键验证步骤。',
3459
3460
  ]) +
3460
3461
  fmtCodeBlock('markdown', [
3461
3462
  '- 修改前先定位调用链与数据流,避免“只改表面”。',
@@ -3466,14 +3467,15 @@ function renderMindsManual(language) {
3466
3467
  return (fmtHeader('.minds/team/<id>/*') +
3467
3468
  fmtList([
3468
3469
  'Recommended default practice: for each `members.<id>`, maintain `persona.*.md` / `knowledge.*.md` / `lessons.*.md` together so persona, domain knowledge, and lessons stay layered and maintainable.',
3469
- 'Minimum: for each `members.<id>`, provide at least `persona.*.md` (otherwise the member may lack a discoverable persona; ignore/fallback/error behavior follows current implementation).',
3470
- 'persona.*.md: persona and operating style.',
3470
+ 'Shared destination (current implementation): these three files are read at every dialog start and are spliced into the system prompt as `## Persona` / `## Knowledge` / `## Lessons`. They are long-lived prompt assets written for the current member agent itself, not operator-facing biographies for a team manager or human observer.',
3471
+ 'Minimum: for each `members.<id>`, provide at least `persona.*.md`. In the current implementation, a missing persona falls back to built-in default persona text, while missing/blank knowledge and lessons render as the localized “none” placeholder.',
3472
+ 'persona.*.md: persona and operating style. It is injected into that member\'s `role=system` prompt, so write it directly to the agent in second person ("you") when specifying responsibilities, boundaries, and working style; do not turn it into a third-person biography.',
3473
+ 'knowledge.*.md: domain knowledge. It lands in `## Knowledge`, so use it for stable facts, indexes, conventions, and decision cues that the member repeatedly needs in this responsibility. Second-person guidance or neutral reference bullets are both fine, as long as the text helps the member act now rather than describing the member from the outside.',
3474
+ 'lessons.*.md: lessons learned. It lands in `## Lessons`, so prefer reusable heuristics such as “if signal X appears -> do / avoid Y -> because Z”. Do not treat it as a task log, meeting minutes, or a third-person growth narrative.',
3471
3475
  'If the member carries team-management responsibility (especially with `team_mgmt`), `persona.*.md` must explicitly require reading the relevant `man({ "toolsetId": "team_mgmt" })` chapters before any team-management action, and maintaining `.minds/**` team mind assets by handbook-standard workflow.',
3472
- 'knowledge.*.md: domain knowledge (maintainable).',
3473
- 'lessons.*.md: lessons learned (maintainable).',
3474
- 'Authoring rule: do not add top-level titles that duplicate the system prompt wrapper. The system prompt already adds: `## Persona` / `## Knowledge` / `## Lessons` (zh template: `## 角色设定` / `## 知识` / `## 经验`).',
3476
+ 'Language selection (current implementation): prefer work-language variants such as `persona.en.md` / `knowledge.en.md` / `lessons.en.md`, then fall back to `persona.md` / `knowledge.md` / `lessons.md`. There is no cross-language fallback to another language-specific file.',
3477
+ 'Heading rule: do not add top-level titles that duplicate the system prompt wrapper. The system prompt already adds: `## Persona` / `## Knowledge` / `## Lessons` (zh template: `## 角色设定` / `## 知识` / `## 经验`). In practice, bodies should usually start at `###` subsections or plain bullets rather than another `#` / `##`, and should not restate the filename or wrapper title as a heading.',
3475
3478
  'Codex-provider-specific capability: if this member uses `provider: codex`, you may place a standalone `@codex-system-prompt` line inside `persona.*.md` to splice the stock Codex built-in system prompt into the current persona. Use `@codex-system-prompt:<model>` to pin a specific bundled prompt variant. Put that line near the top, then continue with your team-specific role boundaries and delivery rules.',
3476
- 'Language variants: prefer working-language files like `persona.en.md` / `persona.zh.md`; whether it falls back to `persona.md` or other languages follows current implementation.',
3477
3479
  ]) +
3478
3480
  fmtCodeBlock('text', [
3479
3481
  '.minds/',
@@ -3487,12 +3489,12 @@ function renderMindsManual(language) {
3487
3489
  '@codex-system-prompt',
3488
3490
  '',
3489
3491
  '### Core Identity',
3490
- '- Professional programmer responsible for implementing approved development specs.',
3492
+ '- You are a professional programmer responsible for implementing approved development specs.',
3491
3493
  '### Work Boundaries',
3492
- '- Not responsible for requirement discovery or product strategy.',
3493
- '- Implements/refactors only against confirmed specs.',
3494
+ '- You are not responsible for requirement discovery or product strategy.',
3495
+ '- You implement/refactor only against confirmed specs.',
3494
3496
  '### Delivery Standard',
3495
- '- Deliver runnable code plus key verification steps.',
3497
+ '- You deliver runnable code plus key verification steps.',
3496
3498
  ]) +
3497
3499
  fmtCodeBlock('markdown', [
3498
3500
  '- Trace call chain and data flow before editing; avoid patching only symptoms.',
@@ -3508,6 +3510,8 @@ function renderSkillsManual(language) {
3508
3510
  '语言选择:Dominds 当前工作语言是 `zh|en`,但 skill 文件后缀采用更通行的 `cn|en`。当工作语言为 `zh` 时优先读取 `SKILL.cn.md`,当工作语言为 `en` 时优先读取 `SKILL.en.md`,两者都可回退到无语言标识的 `SKILL.md`;不会跨语言兜底到另一种语言文件。',
3509
3511
  '可移植优先格式:遵循当前主流 Agent Skills 生态公共子集,使用 `SKILL.md + YAML frontmatter`。最小必备字段是 `name` 与 `description`,正文 markdown 即真正的技能提示词/操作指引。',
3510
3512
  'Dominds 当前实现会把匹配到的 skill 内容直接注入 agent system prompt;因此这里的技能更接近“指导知识包”。这与部分平台的“先只加载 name/description、命中后再延迟加载正文”不同,请控制体量,把长参考资料拆到同目录其它文件并在正文里按需引用。',
3513
+ '去向与口吻(按当前实现):`name` 会成为 skills 小节里的标题,`description` 会作为说明文字显示,正文会原样进入 `Prompt` 区块。因此三者都属于写给“当前成员智能体”的系统提示内容。推荐把 `name` 写成稳定技能名,把 `description` 写成“何时用/何时不用”,把正文写成简洁的操作指引(多用祈使句/第二人称),不要写成 marketplace 营销文案、第三人称人物介绍,或对团队管理者的旁白说明。',
3514
+ '标题层级约束:skills 模板已经自动包好 `### Skills(工作技能)` 和每个 skill 的 `#### <name>` 标题。正文通常应从普通 bullet、步骤列表,或至多 `#####` 小节开始;不要在正文里再写 `# <skill-name>` / `## ...` 来重复外层标题结构。',
3511
3515
  '为兼容公开来源,可保留 `allowed-tools` / `user-invocable` / `disable-model-invocation` 字段;但在 Dominds 中:这些字段目前只用于迁移/文档语义,不会自动授予工具权限,也不会改变运行时调度逻辑。',
3512
3516
  '最重要的边界:skill 不是权限系统。真正的工具能力仍由 `.minds/team.yaml` 的 `toolsets` / `tools` 与已安装 Dominds apps 决定。',
3513
3517
  '团队管理职责的智能体可以联网搜索公开 skill 定义(优先官方文档/官方仓库/官方 marketplace 条目),也可以直接基于团队真实操作经验自行总结编写。迁移前必须核对 license、适用场景、是否依赖脚本/外部工具、是否夹带与本团队冲突的人设/权限假设。',
@@ -3526,13 +3530,11 @@ function renderSkillsManual(language) {
3526
3530
  'disable-model-invocation: false',
3527
3531
  '---',
3528
3532
  '',
3529
- '# Repo Debugger',
3530
- '',
3531
- '## 入口',
3533
+ '##### 入口',
3532
3534
  '- 先确认失败信号与复现入口。',
3533
3535
  '- 若需要 shell,必须使用当前团队已授权的 Dominds 工具/专员,不得把 `allowed-tools` 视为自动授权。',
3534
3536
  '',
3535
- '## 操作步骤',
3537
+ '##### 操作步骤',
3536
3538
  '1. 收集报错与最近变更。',
3537
3539
  '2. 最小化复现。',
3538
3540
  '3. 定位根因并给出验证方案。',
@@ -3572,6 +3574,8 @@ function renderSkillsManual(language) {
3572
3574
  'Language selection: Dominds work language is currently `zh|en`, but skill filenames use the more portable `cn|en` suffixes. When work language is `zh`, Dominds prefers `SKILL.cn.md`; when it is `en`, Dominds prefers `SKILL.en.md`; both may fall back to `SKILL.md`. There is no cross-language fallback.',
3573
3575
  'Portable-first format: follow the common Agent Skills subset used by GitHub/Codex/Claude/skills.sh style ecosystems: `SKILL.md + YAML frontmatter`. The minimum required fields are `name` and `description`; the Markdown body is the actual skill prompt/operating guidance.',
3574
3576
  'Current Dominds behavior eagerly injects matched skills into the agent system prompt. That makes a Dominds skill closer to a guidance knowledge pack than to a lazily loaded marketplace artifact. Keep bodies tight, and move long references into sibling files that the body points to.',
3577
+ 'Destination and tone (current implementation): `name` is rendered as the skill heading, `description` appears as visible description text, and the body is inserted verbatim into the `Prompt` block inside system prompt. Write all three for the current member agent. Use a stable skill name, trigger-oriented description, and concise operating guidance in the body; avoid marketplace sales copy, third-person biographies, or operator-facing narration.',
3578
+ 'Heading rule: the wrapper already provides `### Skills` and `#### <name>` for each skill. Bodies should usually start with plain bullets, numbered steps, or at most `#####` subsections; do not repeat the outer structure with another `# <skill-name>` / `## ...` inside the body.',
3575
3579
  'For compatibility with public skill sources, Dominds accepts `allowed-tools`, `user-invocable`, and `disable-model-invocation`; however, in Dominds these fields are currently informational only. They do not grant tools and do not change runtime dispatch yet.',
3576
3580
  'The hard boundary: a skill is not a permission system. Real tool access still comes from `.minds/team.yaml` (`toolsets` / `tools`) and installed Dominds apps.',
3577
3581
  'A team-management agent may browse the web for public skill definitions (prefer official docs/repos/marketplace listings), or write skills directly by summarizing the team’s own repeatable operating guidance. Before importing, verify license, applicability, script/tool dependencies, and any hidden persona/permission assumptions.',
@@ -3591,13 +3595,11 @@ function renderSkillsManual(language) {
3591
3595
  'disable-model-invocation: false',
3592
3596
  '---',
3593
3597
  '',
3594
- '# Repo Debugger',
3595
- '',
3596
- '## Entry',
3598
+ '##### Entry',
3597
3599
  '- Confirm the failure signal and reproduction path first.',
3598
3600
  '- If shell is required, use only the Dominds tools/specialists actually granted by the team; never treat `allowed-tools` as auto-authorization.',
3599
3601
  '',
3600
- '## Procedure',
3602
+ '##### Procedure',
3601
3603
  '1. Gather the error signal and recent changes.',
3602
3604
  '2. Minimize reproduction.',
3603
3605
  '3. Isolate root cause and propose verification.',
@@ -3636,14 +3638,19 @@ function renderPrimingManual(language) {
3636
3638
  return (fmtHeader('.minds/priming/*(启动脚本)') +
3637
3639
  fmtList([
3638
3640
  '目录约定:个人脚本放在 `.minds/priming/individual/<member-id>/<slug>.md`;团队共享脚本放在 `.minds/priming/team_shared/<slug>.md`。',
3639
- '脚本语义:启动脚本会映射成“对话历史”并在创建对话时注入;它不是只读日志,而是可编辑的行为引导层。',
3641
+ '脚本语义(按当前实现):创建对话时,frontmatter 里的 `reminders` 会先恢复为该对话的提醒状态;随后脚本 records 会被追加进持久化事件流,并尽可能回放成真正的对话历史消息。它不是 system prompt,也不是只读日志,而是可编辑的“起手历史/行为引导层”。',
3640
3642
  '推荐格式:`frontmatter + record 块`。每个 `### record <type>` 对应一个持久化事件(去掉 `ts`),可忠实复原 tool 记录与 call-id 等技术细节。',
3643
+ '口吻规则取决于 record 类型,而不是统一写成一种旁白:`human_text_record` 会变成 `role=user` 的 prompting message,应写成“用户/诉请者正在对这个智能体说的话”;`agent_words_record` 会变成 `role=assistant` 的可见回复,应写成“这个智能体已经说出口的话”;`agent_thought_record` 会变成 thinking message,只适合非常克制地承载内部推理痕迹,不适合拿来写通用制度说明。',
3644
+ '`func_call_record` / `func_result_record` / tellask 相关 records 属于技术回放层:如果要保留,就应写真实调用名、参数、结果与关联 id,而不是随手写一段 prose 摘要来冒充工具历史。',
3645
+ '`ui_only_markdown_record` 以及若干运行时技术 record 会被持久化,但不会转成喂给模型的 chat message;不要指望这类 record 去直接塑造模型行为。',
3641
3646
  '严格约束:不支持 `### user` / `### assistant` 旧写法。',
3642
3647
  '`func_call_record` 使用三反引号 `json`;其余 record 建议使用 6 重反引号 markdown block(避免与正文三反引号冲突)。',
3643
3648
  '建议在 frontmatter 里维护 `title`、`applicableMemberIds` 等元数据;`team_shared` 脚本可用 `applicableMemberIds` 控制适用成员。',
3649
+ 'frontmatter.reminders 的写法也要遵循 reminder 语义:内容应短小、像工作集提示,不要塞成长文手册;若省略 `scope`,当前实现默认是 `dialog`;只有明确希望后续对话也继续可见时,才使用 `personal` 或 `agent_shared`。',
3644
3650
  '维护原则:允许任意编辑/重写脚本内容,包括新增或改写 assistant 消息,以引导期望行为(而不是拘泥于历史实录)。',
3645
3651
  '每次修改 `.minds/priming/**` 后,建议运行 `team_mgmt_validate_priming_scripts({})` 做格式/路径校验。',
3646
3652
  'WebUI 支持把“当前 course 历史”直接导出为个人启动脚本;导出后应由团队管理者审阅并按团队规范再编辑。',
3653
+ '结构层级约束:priming 文件的外层结构以“顶层 frontmatter + 多个 `### record <type>` 块”为准;不要再包一层 `# 启动脚本` / `## 历史` 之类的装饰性章节。真正喂给模型的文本结构,应写在各个 record 自己的 markdown/json block 里。',
3647
3654
  'slug 规范:使用 `[A-Za-z0-9._-]` 路径段,可多级;严禁 `..`、绝对路径或非法字符。',
3648
3655
  ]) +
3649
3656
  fmtCodeBlock('markdown', [
@@ -3680,14 +3687,19 @@ function renderPrimingManual(language) {
3680
3687
  return (fmtHeader('.minds/priming/* (startup scripts)') +
3681
3688
  fmtList([
3682
3689
  'Directory convention: individual scripts live at `.minds/priming/individual/<member-id>/<slug>.md`; team-shared scripts live at `.minds/priming/team_shared/<slug>.md`.',
3683
- 'Script semantics: startup scripts are mapped into dialog history at creation time. They are not read-only logs; they are editable behavior-guidance assets.',
3690
+ 'Script semantics (current implementation): at dialog creation, frontmatter `reminders` are restored into dialog reminder state first; then records are appended to the persisted event stream and replayed into dialog history when possible. A priming script is not system prompt text and not a read-only log; it is an editable “startup history / behavior-guidance asset.',
3684
3691
  'Recommended format: `frontmatter + record blocks`. Each `### record <type>` maps to one persisted event (without `ts`) for faithful replay, including tool records and call-id links.',
3692
+ 'Tone depends on record type, not on one narrator voice: `human_text_record` becomes a `role=user` prompting message, so write it as what the user/requester is saying to the agent; `agent_words_record` becomes a visible `role=assistant` reply, so write it as words the agent has already said; `agent_thought_record` becomes a thinking message and should be used sparingly, not as a place for general policy prose.',
3693
+ '`func_call_record` / `func_result_record` / tellask-related records are technical replay artifacts. Keep real call names, arguments, results, and linked ids if you include them; do not replace them with loose prose summaries.',
3694
+ '`ui_only_markdown_record` and several runtime-only technical records persist on disk but do not become chat messages for model context. Do not rely on those record types to steer the model directly.',
3685
3695
  'Strict rule: legacy `### user` / `### assistant` sections are not supported.',
3686
3696
  '`func_call_record` uses triple-backtick `json`; other records should use six-backtick markdown blocks to avoid nested-fence collisions.',
3687
3697
  'Use frontmatter for metadata like `title` and `applicableMemberIds`; for `team_shared`, `applicableMemberIds` narrows applicability.',
3698
+ 'frontmatter.reminders should follow reminder semantics too: keep them short like working-set prompts rather than mini-manuals. If `scope` is omitted, the current implementation defaults it to `dialog`; use `personal` / `agent_shared` only when you intentionally want later dialogs to keep seeing the reminder.',
3688
3699
  'Maintenance principle: freely edit or fully rewrite scripts, including assistant messages, to shape expected behavior.',
3689
3700
  'After each edit under `.minds/priming/**`, run `team_mgmt_validate_priming_scripts({})` for format/path validation.',
3690
3701
  'WebUI can export current-course history into an individual startup script; team managers should review and refine exported scripts.',
3702
+ 'Structure rule: the outer file structure is “top-level frontmatter + repeated `### record <type>` blocks”. Do not wrap the script in decorative `# Startup Script` / `## History` headings. The model-facing structure belongs inside each record’s own markdown/json block.',
3691
3703
  'Slug rule: use `[A-Za-z0-9._-]` path segments (nested allowed); reject `..`, absolute paths, and illegal characters.',
3692
3704
  ]) +
3693
3705
  fmtCodeBlock('markdown', [
@@ -3725,17 +3737,19 @@ function renderEnvManual(language) {
3725
3737
  if (language === 'zh') {
3726
3738
  return (fmtHeader('.minds/env.*.md(运行环境提示)') +
3727
3739
  fmtList([
3728
- '用途:为“当前 rtws 的运行环境”提供一段稳定的介绍文案。Dominds 会将其注入到 agent 的 system prompt 中,注入位置在“团队目录(Team Directory)”之前。',
3740
+ '用途:为“当前 rtws 的运行环境”提供一段稳定的介绍文案。Dominds 会将其注入到 agent 的 system prompt 的 `## 运行环境` 章节,注入位置在“团队目录(Team Directory)”之前。',
3729
3741
  '文件位置:写在当前 rtws 的 `.minds/` 下;切换 rtws(例如 `-C ux-rtws`)时,应在对应 rtws 的 `.minds/` 下分别维护。',
3730
3742
  '推荐文件名:`env.zh.md`(中文语义基准)与 `env.en.md`(英文对齐)。',
3731
3743
  '回退规则:优先按工作语言读取 `env.<lang>.md`;如不存在,可回退到 `env.md`(以当前实现为准)。空文件/仅空白会被当作“无提示”。',
3744
+ '口吻建议:它虽然进入 system prompt,但职责不是定义人设,而是给当前成员做环境定向。最合适的是简洁、可核实的事实说明,可用中性 bullet,也可用“你当前处于……”这类第二人称 briefing;核心是让成员快速知道自己身处哪个 rtws、有哪些关键路径/入口/端口/约束。',
3745
+ '标题层级约束:系统模板已经提供 `## 运行环境` 标题,所以 `env.*.md` 正文通常应从 `###` 小节或普通 bullet 开始;不要再写一层 `# 环境说明` / `## 本 rtws ...` 来重复模板标题。',
3746
+ '边界提醒:不要把 `env.*.md` 写成 persona、skill、工具手册或仓库总规范大杂烩;不要在这里重复团队职责边界、长篇流程制度,或凭空编造环境事实。',
3732
3747
  'i18n 约定:`zh` 为语义基准。不要把 `zh` 通过翻译 `en` 来更新;应让 `en` 追随 `zh` 的语义。',
3733
3748
  '管理者提醒:若发现缺失/质量不佳/与实际环境不符,应与人类用户讨论并确认措辞,然后再写入/更新对应的 `env.*.md`(避免“凭空编造”的环境描述)。',
3734
3749
  ]) +
3735
3750
  fmtCodeBlock('text', [
3736
- '# 示例(片段;请按你的 rtws 真实环境改写)',
3737
- '## rtws 的 Dominds 运行环境说明',
3738
- '',
3751
+ '示例(片段;请按你的 rtws 真实环境改写)',
3752
+ '### 当前 rtws 概况',
3739
3753
  '- 本 rtws 用于 Dominds 自我开发与联调。',
3740
3754
  '- Dominds 程序来源:本机全局 link 的 `dominds`,由 `./dominds/` 构建产物提供。',
3741
3755
  '- WebUI dev/UX:`./dev-server.sh` 使用 `ux-rtws/` 作为 rtws(避免污染根 rtws)。',
@@ -3743,17 +3757,19 @@ function renderEnvManual(language) {
3743
3757
  }
3744
3758
  return (fmtHeader('.minds/env.*.md (runtime environment intro)') +
3745
3759
  fmtList([
3746
- 'Purpose: provide a stable intro note describing the “current rtws runtime environment”. Dominds injects it into the agent system prompt, positioned before “Team Directory”.',
3760
+ 'Purpose: provide a stable intro note describing the “current rtws runtime environment”. Dominds injects it into the `## Runtime Environment` section of the agent system prompt, positioned before “Team Directory”.',
3747
3761
  'Location: place it under the current rtws `.minds/`. If you switch rtws (e.g. `-C ux-rtws`), maintain a separate `env.*.md` under that rtws’s `.minds/`.',
3748
3762
  'Recommended filenames: `env.zh.md` (canonical semantics) and `env.en.md` (English aligned to zh).',
3749
3763
  'Fallback behavior: prefer `env.<lang>.md` by working language; if missing, it may fall back to `env.md` (per current implementation). Empty/whitespace-only content is treated as “no intro”.',
3764
+ 'Tone guidance: although it enters system prompt, its job is environmental orientation rather than persona definition. Prefer concise, verifiable facts in neutral bullets or a brief second-person orientation like “you are currently in...”; the goal is to help the member quickly understand the active rtws, key paths, entrypoints, ports, and constraints.',
3765
+ 'Heading rule: the system template already provides the `## Runtime Environment` heading, so `env.*.md` bodies should usually start at `###` subsections or plain bullets rather than another `# Environment Notes` / `## This rtws ...` wrapper.',
3766
+ 'Boundary reminder: do not turn `env.*.md` into a persona file, skill, tool manual, or giant repo-policy dump. Avoid duplicating role rules, long procedures, or speculative environment claims.',
3750
3767
  'i18n rule: `zh` is canonical. Do not update `zh` by translating from `en`; update `en` to match `zh` semantics.',
3751
3768
  'Manager reminder: if the file is missing / inaccurate / low quality, discuss wording with the human user and then write/update `env.*.md` (avoid fabricating environment details).',
3752
3769
  ]) +
3753
3770
  fmtCodeBlock('text', [
3754
- '# Example (snippet; tailor to your real rtws)',
3755
- '## Dominds runtime environment notes',
3756
- '',
3771
+ 'Example (snippet; tailor to your real rtws)',
3772
+ '### Current rtws overview',
3757
3773
  '- This rtws is used for Dominds self-development and integration.',
3758
3774
  '- Program source: a globally linked `dominds` built from `./dominds/`.',
3759
3775
  '- WebUI dev/UX: `./dev-server.sh` uses `ux-rtws/` as rtws (keeps root rtws clean).',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dominds",
3
- "version": "1.16.6",
3
+ "version": "1.16.8",
4
4
  "description": "Dominds CLI and aggregation shell for the LongRun AI kernel/runtime packages.",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {