dominds 1.16.5 → 1.16.7

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.
@@ -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
  初始引导的首选行为:
@@ -676,7 +676,7 @@ async function executeDriveRound(args) {
676
676
  }
677
677
  else {
678
678
  await (0, tellask_special_1.deliverTellaskBackReplyFromDirective)({
679
- dlg: dialog,
679
+ replyingDialog: dialog,
680
680
  directive: activeTellaskReplyDirective,
681
681
  replyContent: driveResult.lastAssistantSayingContent,
682
682
  callbacks: {
@@ -67,7 +67,7 @@ export type InvalidTellaskFunctionCall = Readonly<{
67
67
  export declare function isTellaskCallFunctionName(name: string): name is TellaskCallFunctionName;
68
68
  export declare function loadLatestActiveTellaskReplyDirective(dialog: Dialog): Promise<TellaskReplyDirective | undefined>;
69
69
  export declare function deliverTellaskBackReplyFromDirective(args: {
70
- dlg: Dialog;
70
+ replyingDialog: Dialog;
71
71
  directive: Extract<TellaskReplyDirective, {
72
72
  expectedReplyCallName: 'replyTellaskBack';
73
73
  }>;
@@ -136,39 +136,48 @@ function buildTellaskBackReplyDirective(args) {
136
136
  };
137
137
  }
138
138
  async function deliverTellaskBackReplyFromDirective(args) {
139
- const rootDialog = args.dlg instanceof dialog_1.RootDialog
140
- ? args.dlg
141
- : args.dlg instanceof dialog_1.SubDialog
142
- ? args.dlg.rootDialog
139
+ // Type-A ask-back is the one place where the local "caller/callee" intuition flips:
140
+ // the dialog running `replyTellaskBack` is the ask-back responder, while
141
+ // directive.targetDialogId points to the ask-back requester that must receive the canonical
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.
145
+ const rootDialog = args.replyingDialog instanceof dialog_1.RootDialog
146
+ ? args.replyingDialog
147
+ : args.replyingDialog instanceof dialog_1.SubDialog
148
+ ? args.replyingDialog.rootDialog
143
149
  : undefined;
144
150
  if (!rootDialog) {
145
151
  throw new Error('replyTellaskBack invariant violation: missing root dialog');
146
152
  }
147
- const targetDialogId = new dialog_1.DialogID(args.directive.targetDialogId, rootDialog.id.rootId);
148
- const targetDialog = rootDialog.lookupDialog(targetDialogId.selfId) ??
149
- (await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, targetDialogId, rootDialog.status));
150
- if (!targetDialog) {
151
- throw new Error(`replyTellaskBack invariant violation: target dialog ${targetDialogId.selfId} not found`);
153
+ const askBackRequesterDialogId = new dialog_1.DialogID(args.directive.targetDialogId, rootDialog.id.rootId);
154
+ const askBackRequesterDialog = rootDialog.lookupDialog(askBackRequesterDialogId.selfId) ??
155
+ (await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, askBackRequesterDialogId, rootDialog.status));
156
+ if (!askBackRequesterDialog) {
157
+ throw new Error(`replyTellaskBack invariant violation: target dialog ${askBackRequesterDialogId.selfId} not found`);
152
158
  }
153
- targetDialog.setSuspensionState('resumed');
154
159
  const response = (0, inter_dialog_format_1.formatTellaskResponseContent)({
155
160
  callName: 'tellaskBack',
156
- responderId: args.dlg.agentId,
157
- requesterId: targetDialog.agentId,
161
+ responderId: args.replyingDialog.agentId,
162
+ requesterId: askBackRequesterDialog.agentId,
158
163
  tellaskContent: args.directive.tellaskContent,
159
164
  responseBody: args.replyContent,
160
165
  status: 'completed',
161
166
  deliveryMode: args.deliveryMode,
162
167
  language: (0, work_language_1.getWorkLanguage)(),
163
168
  });
164
- const replyMirror = await targetDialog.receiveTellaskResponse(args.dlg.agentId, 'tellaskBack', undefined, args.directive.tellaskContent, 'completed', args.dlg.id, {
169
+ const replyMirror = await askBackRequesterDialog.receiveTellaskResponse(args.replyingDialog.agentId, 'tellaskBack', undefined, args.directive.tellaskContent, 'completed', args.replyingDialog.id, {
165
170
  response,
166
- agentId: args.dlg.agentId,
171
+ agentId: args.replyingDialog.agentId,
167
172
  callId: args.directive.targetCallId,
168
- originMemberId: targetDialog.agentId,
173
+ originMemberId: askBackRequesterDialog.agentId,
169
174
  });
170
- await targetDialog.addChatMessages(replyMirror);
171
- await reviveDialogIfUnblocked(targetDialog, args.callbacks, 'reply_tellask_back_delivered');
175
+ await askBackRequesterDialog.addChatMessages(replyMirror);
176
+ // Do not mark the requester resumed until the canonical tellaskBack result has actually been
177
+ // persisted and mirrored locally. Otherwise a write failure here would leave suspension state
178
+ // claiming "resumed" while the business fact never landed.
179
+ askBackRequesterDialog.setSuspensionState('resumed');
180
+ await reviveDialogIfUnblocked(askBackRequesterDialog, args.callbacks, 'reply_tellask_back_delivered');
172
181
  }
173
182
  function isReplyTellaskCallRecord(record) {
174
183
  return isReplyTellaskCallName(record.name);
@@ -883,9 +892,29 @@ function extractLastAssistantResponse(messages, defaultMessage) {
883
892
  }
884
893
  return responseText;
885
894
  }
886
- async function extractSupdialogResponseForTypeA(supdialog) {
895
+ function findDeliveredTellaskBackReplyOnAskBackRequester(args) {
896
+ // `replyTellaskBack` persists the canonical tellaskBack business result onto the ask-back
897
+ // requester dialog immediately. Type-A orchestration must check that canonical delivery first
898
+ // before it even considers any fallback extraction from responder plaintext, or we risk a
899
+ // second final result with the same target callId.
900
+ for (let i = args.requesterDialog.msgs.length - 1; i >= 0; i -= 1) {
901
+ const msg = args.requesterDialog.msgs[i];
902
+ if (msg.type !== 'tellask_result_msg' || msg.callName !== 'tellaskBack') {
903
+ continue;
904
+ }
905
+ if (msg.callId !== args.targetCallId) {
906
+ continue;
907
+ }
908
+ return msg;
909
+ }
910
+ return undefined;
911
+ }
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.
887
916
  try {
888
- return extractLastAssistantResponse(supdialog.msgs, 'Supdialog completed without producing output.');
917
+ return extractLastAssistantResponse(args.responderDialog.msgs, 'Supdialog completed without producing output.');
889
918
  }
890
919
  catch (err) {
891
920
  log_1.log.warn('Failed to extract supdialog response for Type A', err);
@@ -1158,14 +1187,20 @@ async function executeTellaskCall(dlg, mentionList, body, callId, callbacks, opt
1158
1187
  }
1159
1188
  if (parseResult.type === 'A') {
1160
1189
  if (dlg instanceof dialog_1.SubDialog) {
1161
- const supdialog = dlg.supdialog;
1162
- dlg.setSuspensionState('suspended');
1190
+ // Identity map for Type-A ask-back:
1191
+ // - `askBackRequesterDialog` is the sideline dialog that asked upstream for clarification.
1192
+ // - `askBackResponderDialog` is the upstream dialog that must answer that ask-back.
1193
+ // The original tellask relationship is the opposite of the current ask-back relationship,
1194
+ // so variable names like "supdialog" or "target" are too lossy here and invite bugs.
1195
+ const askBackRequesterDialog = dlg;
1196
+ const askBackResponderDialog = dlg.supdialog;
1197
+ askBackRequesterDialog.setSuspensionState('suspended');
1163
1198
  try {
1164
- const assignment = dlg.assignmentFromSup;
1199
+ const assignment = askBackRequesterDialog.assignmentFromSup;
1165
1200
  const supPrompt = {
1166
1201
  content: (0, inter_dialog_format_1.formatSupdialogCallPrompt)({
1167
- fromAgentId: dlg.agentId,
1168
- toAgentId: supdialog.agentId,
1202
+ fromAgentId: askBackRequesterDialog.agentId,
1203
+ toAgentId: askBackResponderDialog.agentId,
1169
1204
  subdialogRequest: {
1170
1205
  callName,
1171
1206
  mentionList,
@@ -1182,12 +1217,12 @@ async function executeTellaskCall(dlg, mentionList, body, callId, callbacks, opt
1182
1217
  grammar: 'markdown',
1183
1218
  origin: 'runtime',
1184
1219
  tellaskReplyDirective: buildTellaskBackReplyDirective({
1185
- targetDialogId: dlg.id.selfId,
1220
+ targetDialogId: askBackRequesterDialog.id.selfId,
1186
1221
  targetCallId: callId,
1187
1222
  tellaskContent: body,
1188
1223
  }),
1189
1224
  };
1190
- await callbacks.driveDialog(supdialog, {
1225
+ await callbacks.driveDialog(askBackResponderDialog, {
1191
1226
  humanPrompt: supPrompt,
1192
1227
  waitInQue: true,
1193
1228
  driveOptions: {
@@ -1195,20 +1230,34 @@ async function executeTellaskCall(dlg, mentionList, body, callId, callbacks, opt
1195
1230
  reason: 'type_a_supdialog_roundtrip',
1196
1231
  },
1197
1232
  });
1198
- const responseText = await extractSupdialogResponseForTypeA(supdialog);
1233
+ const explicitReplyDelivery = findDeliveredTellaskBackReplyOnAskBackRequester({
1234
+ requesterDialog: askBackRequesterDialog,
1235
+ targetCallId: callId,
1236
+ });
1237
+ if (explicitReplyDelivery) {
1238
+ // Important invariant: once the responder used `replyTellaskBack`, that write is the
1239
+ // single source of truth. Do not also synthesize another tellask result from the
1240
+ // responder's generic assistant words, even if those words look "compatible".
1241
+ askBackRequesterDialog.setSuspensionState('resumed');
1242
+ toolOutputs.push(explicitReplyDelivery);
1243
+ return toolOutputs;
1244
+ }
1245
+ const responseText = await extractAskBackResponderPlaintextFallback({
1246
+ responderDialog: askBackResponderDialog,
1247
+ });
1199
1248
  const responseContent = (0, inter_dialog_format_1.formatTellaskResponseContent)({
1200
1249
  callName,
1201
1250
  responderId: parseResult.agentId,
1202
- requesterId: dlg.agentId,
1251
+ requesterId: askBackRequesterDialog.agentId,
1203
1252
  mentionList,
1204
1253
  tellaskContent: body,
1205
1254
  responseBody: responseText,
1206
1255
  status: 'completed',
1207
1256
  language: (0, work_language_1.getWorkLanguage)(),
1208
1257
  });
1209
- dlg.setSuspensionState('resumed');
1258
+ askBackRequesterDialog.setSuspensionState('resumed');
1210
1259
  toolOutputs.push(buildTellaskResultToolOutput({
1211
- genseq: dlg.activeGenSeqOrUndefined ?? 1,
1260
+ genseq: askBackRequesterDialog.activeGenSeqOrUndefined ?? 1,
1212
1261
  callId,
1213
1262
  callName,
1214
1263
  content: responseContent,
@@ -1217,24 +1266,24 @@ async function executeTellaskCall(dlg, mentionList, body, callId, callbacks, opt
1217
1266
  tellaskContent: body,
1218
1267
  mentionList,
1219
1268
  agentId: parseResult.agentId,
1220
- originMemberId: dlg.agentId,
1221
- calleeDialogId: supdialog.id.selfId,
1269
+ originMemberId: askBackRequesterDialog.agentId,
1270
+ calleeDialogId: askBackResponderDialog.id.selfId,
1222
1271
  }));
1223
- await dlg.receiveTellaskResponse(parseResult.agentId, callName, mentionList, body, 'completed', supdialog.id, {
1272
+ await askBackRequesterDialog.receiveTellaskResponse(parseResult.agentId, callName, mentionList, body, 'completed', askBackResponderDialog.id, {
1224
1273
  response: responseContent,
1225
1274
  agentId: parseResult.agentId,
1226
1275
  callId,
1227
- originMemberId: dlg.agentId,
1276
+ originMemberId: askBackRequesterDialog.agentId,
1228
1277
  });
1229
1278
  }
1230
1279
  catch (err) {
1231
1280
  log_1.log.warn('Type A supdialog processing error:', err);
1232
- dlg.setSuspensionState('resumed');
1281
+ askBackRequesterDialog.setSuspensionState('resumed');
1233
1282
  const errorText = `❌ **Error processing request to @${parseResult.agentId}:**\n\n${showErrorToAi(err)}`;
1234
1283
  const errorContent = (0, inter_dialog_format_1.formatTellaskResponseContent)({
1235
1284
  callName,
1236
1285
  responderId: parseResult.agentId,
1237
- requesterId: dlg.agentId,
1286
+ requesterId: askBackRequesterDialog.agentId,
1238
1287
  mentionList,
1239
1288
  tellaskContent: body,
1240
1289
  responseBody: errorText,
@@ -1242,7 +1291,7 @@ async function executeTellaskCall(dlg, mentionList, body, callId, callbacks, opt
1242
1291
  language: (0, work_language_1.getWorkLanguage)(),
1243
1292
  });
1244
1293
  toolOutputs.push(buildTellaskResultToolOutput({
1245
- genseq: dlg.activeGenSeqOrUndefined ?? 1,
1294
+ genseq: askBackRequesterDialog.activeGenSeqOrUndefined ?? 1,
1246
1295
  callId,
1247
1296
  callName,
1248
1297
  content: errorContent,
@@ -1251,14 +1300,14 @@ async function executeTellaskCall(dlg, mentionList, body, callId, callbacks, opt
1251
1300
  tellaskContent: body,
1252
1301
  mentionList,
1253
1302
  agentId: parseResult.agentId,
1254
- originMemberId: dlg.agentId,
1255
- calleeDialogId: supdialog.id.selfId,
1303
+ originMemberId: askBackRequesterDialog.agentId,
1304
+ calleeDialogId: askBackResponderDialog.id.selfId,
1256
1305
  }));
1257
- await dlg.receiveTellaskResponse(parseResult.agentId, callName, mentionList, body, 'failed', supdialog.id, {
1306
+ await askBackRequesterDialog.receiveTellaskResponse(parseResult.agentId, callName, mentionList, body, 'failed', askBackResponderDialog.id, {
1258
1307
  response: errorContent,
1259
1308
  agentId: parseResult.agentId,
1260
1309
  callId,
1261
- originMemberId: dlg.agentId,
1310
+ originMemberId: askBackRequesterDialog.agentId,
1262
1311
  });
1263
1312
  }
1264
1313
  }
@@ -1779,7 +1828,7 @@ async function executeReplyTellaskCall(args) {
1779
1828
  throw new Error('replyTellaskBack invariant violation: unexpected active reply directive');
1780
1829
  }
1781
1830
  await deliverTellaskBackReplyFromDirective({
1782
- dlg: args.dlg,
1831
+ replyingDialog: args.dlg,
1783
1832
  directive: activeDirective,
1784
1833
  replyContent: args.call.replyContent,
1785
1834
  callbacks: args.callbacks,
@@ -72,6 +72,9 @@ export declare class DiskFileDialogStore extends DialogStore {
72
72
  * Ensure subdialog directory exists (delegate to DialogPersistence)
73
73
  */
74
74
  private ensureSubdialogDirectory;
75
+ private findExistingFuncResultRecord;
76
+ private findExistingTellaskResultRecord;
77
+ private raiseDuplicateCallResultInvariantViolation;
75
78
  /**
76
79
  * Append event to course JSONL file (delegate to DialogPersistence)
77
80
  */
@@ -1433,6 +1433,19 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
1433
1433
  if (!Number.isFinite(genseq) || genseq <= 0) {
1434
1434
  throw new Error(`receiveFuncResult invariant violation: missing valid genseq for func result ${funcResult.id}`);
1435
1435
  }
1436
+ const existingFuncResult = await this.findExistingFuncResultRecord(dialog, funcResult.id);
1437
+ if (existingFuncResult) {
1438
+ await this.raiseDuplicateCallResultInvariantViolation({
1439
+ dialog,
1440
+ kind: 'func_result',
1441
+ callId: funcResult.id,
1442
+ callName: funcResult.name,
1443
+ incomingCourse: course,
1444
+ incomingGenseq: genseq,
1445
+ existingCourse: existingFuncResult.course,
1446
+ existingGenseq: existingFuncResult.record.genseq,
1447
+ });
1448
+ }
1436
1449
  const funcResultRecord = buildFuncResultRecord(funcResult, genseq);
1437
1450
  await this.appendEvent(dialog, course, funcResultRecord);
1438
1451
  // Send event to frontend
@@ -1464,6 +1477,19 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
1464
1477
  ...result,
1465
1478
  genseq,
1466
1479
  };
1480
+ const existingTellaskResult = await this.findExistingTellaskResultRecord(dialog, normalizedResult.callId);
1481
+ if (existingTellaskResult) {
1482
+ await this.raiseDuplicateCallResultInvariantViolation({
1483
+ dialog,
1484
+ kind: 'tellask_result',
1485
+ callId: normalizedResult.callId,
1486
+ callName: normalizedResult.callName,
1487
+ incomingCourse: course,
1488
+ incomingGenseq: genseq,
1489
+ existingCourse: existingTellaskResult.course,
1490
+ existingGenseq: existingTellaskResult.record.genseq,
1491
+ });
1492
+ }
1467
1493
  const record = buildTellaskResultRecord(normalizedResult, genseq);
1468
1494
  await this.appendEvent(dialog, course, record);
1469
1495
  (0, evt_registry_1.postDialogEvent)(dialog, buildTellaskResultEvent(normalizedResult, course));
@@ -1489,6 +1515,74 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
1489
1515
  async ensureSubdialogDirectory(dialogId) {
1490
1516
  return await DialogPersistence.ensureSubdialogDirectory(dialogId);
1491
1517
  }
1518
+ async findExistingFuncResultRecord(dialog, callId) {
1519
+ const latest = await DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
1520
+ const maxCourse = latest?.currentCourse ?? dialog.currentCourse;
1521
+ for (let course = 1; course <= maxCourse; course += 1) {
1522
+ const events = await DialogPersistence.loadCourseEvents(dialog.id, course, dialog.status);
1523
+ for (const event of events) {
1524
+ if (event.type !== 'func_result_record') {
1525
+ continue;
1526
+ }
1527
+ if (event.id !== callId) {
1528
+ continue;
1529
+ }
1530
+ return { course, record: event };
1531
+ }
1532
+ }
1533
+ return undefined;
1534
+ }
1535
+ async findExistingTellaskResultRecord(dialog, callId) {
1536
+ const latest = await DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
1537
+ const maxCourse = latest?.currentCourse ?? dialog.currentCourse;
1538
+ for (let course = 1; course <= maxCourse; course += 1) {
1539
+ const events = await DialogPersistence.loadCourseEvents(dialog.id, course, dialog.status);
1540
+ for (const event of events) {
1541
+ if (event.type !== 'tellask_result_record') {
1542
+ continue;
1543
+ }
1544
+ if (event.callId !== callId) {
1545
+ continue;
1546
+ }
1547
+ return { course, record: event };
1548
+ }
1549
+ }
1550
+ return undefined;
1551
+ }
1552
+ async raiseDuplicateCallResultInvariantViolation(args) {
1553
+ // Duplicate final results are not harmless transcript noise. They mean two different program
1554
+ // paths both believed they owned the same business-level completion fact for one callId.
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
1557
+ // second writer keeps its own stack trace instead of silently corrupting the dialog transcript.
1558
+ const err = new Error(`${args.kind} duplicate callId invariant violation: rootId=${args.dialog.id.rootId} selfId=${args.dialog.id.selfId} ` +
1559
+ `callId=${args.callId} callName=${args.callName} existingCourse=${args.existingCourse} ` +
1560
+ `existingGenseq=${args.existingGenseq} incomingCourse=${args.incomingCourse} incomingGenseq=${args.incomingGenseq}`);
1561
+ log_1.log.error('Duplicate call result detected; rejecting second write', err, {
1562
+ rootId: args.dialog.id.rootId,
1563
+ selfId: args.dialog.id.selfId,
1564
+ callId: args.callId,
1565
+ callName: args.callName,
1566
+ kind: args.kind,
1567
+ existingCourse: args.existingCourse,
1568
+ existingGenseq: args.existingGenseq,
1569
+ incomingCourse: args.incomingCourse,
1570
+ incomingGenseq: args.incomingGenseq,
1571
+ });
1572
+ try {
1573
+ await this.streamError(args.dialog, err.message);
1574
+ }
1575
+ catch (streamErr) {
1576
+ log_1.log.warn('Failed to emit stream_error_evt for duplicate call result', streamErr, {
1577
+ rootId: args.dialog.id.rootId,
1578
+ selfId: args.dialog.id.selfId,
1579
+ callId: args.callId,
1580
+ callName: args.callName,
1581
+ kind: args.kind,
1582
+ });
1583
+ }
1584
+ throw err;
1585
+ }
1492
1586
  /**
1493
1587
  * Append event to course JSONL file (delegate to DialogPersistence)
1494
1588
  */
@@ -1953,7 +2047,7 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
1953
2047
  async streamError(dialog, error) {
1954
2048
  log_1.log.error(`Dialog stream error '${error}'`, new Error(), { dialog });
1955
2049
  const course = dialog.activeGenCourseOrUndefined ?? dialog.currentCourse;
1956
- const genseq = typeof dialog.activeGenSeq === 'number' ? dialog.activeGenSeq : undefined;
2050
+ const genseq = dialog.activeGenSeqOrUndefined;
1957
2051
  // Enhanced stream error event with better error classification
1958
2052
  const streamErrorEvent = {
1959
2053
  type: 'stream_error_evt',
@@ -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.5",
3
+ "version": "1.16.7",
4
4
  "description": "Dominds CLI and aggregation shell for the LongRun AI kernel/runtime packages.",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {