dominds 0.8.18 → 0.8.20

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.
@@ -10,7 +10,7 @@ exports.getAccessDeniedMessage = getAccessDeniedMessage;
10
10
  /**
11
11
  * Module: access-control
12
12
  *
13
- * Directory-based access control helpers:
13
+ * Directory/file-extension based access control helpers:
14
14
  * - `matchesPattern` for glob-like directory scope matching (supports `*` and `**`)
15
15
  * - `hasReadAccess`/`hasWriteAccess` to evaluate member permissions
16
16
  * - `getAccessDeniedMessage` to format denial responses
@@ -31,6 +31,41 @@ function isRootDialogsPath(targetPath) {
31
31
  const normalized = targetPath.replace(/\\/g, '/').replace(/^\/+/, '');
32
32
  return normalized === '.dialogs' || normalized.startsWith('.dialogs/');
33
33
  }
34
+ function normalizeFileExtName(raw) {
35
+ return raw.trim().toLowerCase().replace(/^\.+/, '');
36
+ }
37
+ function extractFileExtName(targetPath) {
38
+ const normalized = targetPath.replace(/\\/g, '/').replace(/\/+$/g, '');
39
+ if (normalized === '' || normalized === '.')
40
+ return undefined;
41
+ const baseName = normalized.split('/').pop();
42
+ if (!baseName || baseName === '.' || baseName === '..')
43
+ return undefined;
44
+ const dotIndex = baseName.lastIndexOf('.');
45
+ if (dotIndex <= 0 || dotIndex === baseName.length - 1)
46
+ return undefined;
47
+ return normalizeFileExtName(baseName.slice(dotIndex + 1));
48
+ }
49
+ function hasFileExtAccess(fileExtName, whitelist, blacklist) {
50
+ // Extension rules only apply to file-like paths with a detectable extension.
51
+ if (!fileExtName)
52
+ return true;
53
+ for (const ext of blacklist ?? []) {
54
+ if (normalizeFileExtName(ext) === fileExtName) {
55
+ return false;
56
+ }
57
+ }
58
+ const allow = whitelist ?? [];
59
+ if (allow.length === 0) {
60
+ return true;
61
+ }
62
+ for (const ext of allow) {
63
+ if (normalizeFileExtName(ext) === fileExtName) {
64
+ return true;
65
+ }
66
+ }
67
+ return false;
68
+ }
34
69
  /**
35
70
  * Directory-specific pattern matching for access control.
36
71
  * This function determines if a target path (file or directory) should be controlled
@@ -142,8 +177,10 @@ function matchesPattern(targetPath, dirPattern) {
142
177
  * Access control logic:
143
178
  * 1. Check blacklist first (no_read_dirs) - if path matches any blacklist pattern, deny access
144
179
  * 2. Check whitelist (read_dirs) - if path matches any whitelist pattern, allow access
145
- * 3. If no whitelist patterns are defined, allow access (default allow)
146
- * 4. If whitelist patterns exist but none match, deny access
180
+ * 3. Check extension blacklist (no_read_file_ext_names) - if extension matches, deny access
181
+ * 4. Check extension whitelist (read_file_ext_names)
182
+ * 5. If no whitelist patterns are defined for a dimension, allow access (default allow)
183
+ * 6. If whitelist patterns exist but none match, deny access
147
184
  */
148
185
  function hasReadAccess(member, targetPath) {
149
186
  // Get resolved relative path from rtws root
@@ -183,17 +220,19 @@ function hasReadAccess(member, targetPath) {
183
220
  const whitelist = member.read_dirs || [];
184
221
  // Note: `.minds/**` is handled above as a hard deny (unless internal bypass is enabled).
185
222
  // If no whitelist is defined, allow access (after blacklist check)
186
- if (whitelist.length === 0) {
187
- return true;
188
- }
189
- // Check if path matches any whitelist pattern
190
- for (const pattern of whitelist) {
191
- if (matchesPattern(relativePath, pattern)) {
192
- return true; // Explicitly allowed
223
+ let directoryAllowed = whitelist.length === 0;
224
+ if (!directoryAllowed) {
225
+ // Check if path matches any whitelist pattern
226
+ for (const pattern of whitelist) {
227
+ if (matchesPattern(relativePath, pattern)) {
228
+ directoryAllowed = true;
229
+ break;
230
+ }
193
231
  }
194
232
  }
195
- // Path doesn't match any whitelist pattern
196
- return false;
233
+ if (!directoryAllowed)
234
+ return false;
235
+ return hasFileExtAccess(extractFileExtName(relativePath), member.read_file_ext_names, member.no_read_file_ext_names);
197
236
  }
198
237
  /**
199
238
  * Check if a member has write access to a specific path.
@@ -201,8 +240,10 @@ function hasReadAccess(member, targetPath) {
201
240
  * Access control logic:
202
241
  * 1. Check blacklist first (no_write_dirs) - if path matches any blacklist pattern, deny access
203
242
  * 2. Check whitelist (write_dirs) - if path matches any whitelist pattern, allow access
204
- * 3. If no whitelist patterns are defined, allow access (default allow)
205
- * 4. If whitelist patterns exist but none match, deny access
243
+ * 3. Check extension blacklist (no_write_file_ext_names) - if extension matches, deny access
244
+ * 4. Check extension whitelist (write_file_ext_names)
245
+ * 5. If no whitelist patterns are defined for a dimension, allow access (default allow)
246
+ * 6. If whitelist patterns exist but none match, deny access
206
247
  */
207
248
  function hasWriteAccess(member, targetPath) {
208
249
  // Get resolved relative path from rtws root
@@ -242,17 +283,19 @@ function hasWriteAccess(member, targetPath) {
242
283
  const whitelist = member.write_dirs || [];
243
284
  // Note: `.minds/**` is handled above as a hard deny (unless internal bypass is enabled).
244
285
  // If no whitelist is defined, allow access (after blacklist check)
245
- if (whitelist.length === 0) {
246
- return true;
247
- }
248
- // Check if path matches any whitelist pattern
249
- for (const pattern of whitelist) {
250
- if (matchesPattern(relativePath, pattern)) {
251
- return true; // Explicitly allowed
286
+ let directoryAllowed = whitelist.length === 0;
287
+ if (!directoryAllowed) {
288
+ // Check if path matches any whitelist pattern
289
+ for (const pattern of whitelist) {
290
+ if (matchesPattern(relativePath, pattern)) {
291
+ directoryAllowed = true;
292
+ break;
293
+ }
252
294
  }
253
295
  }
254
- // Path doesn't match any whitelist pattern
255
- return false;
296
+ if (!directoryAllowed)
297
+ return false;
298
+ return hasFileExtAccess(extractFileExtName(relativePath), member.write_file_ext_names, member.no_write_file_ext_names);
256
299
  }
257
300
  /**
258
301
  * Get an access denied error message for a specific operation and path.
@@ -308,5 +351,11 @@ function getAccessDeniedMessage(operation, targetPath, language = 'en') {
308
351
  lines.push(`- Hint: For Dominds debugging, reproduce in a nested rtws (e.g. \`ux-rtws/.dialogs/\`), which is not covered by this hard deny.`);
309
352
  }
310
353
  }
354
+ if (language === 'zh') {
355
+ lines.push(`- 说明:该路径可能命中目录权限(\`read_dirs/write_dirs/no_*_dirs\`)或扩展名权限(\`*_file_ext_names/no_*_file_ext_names\`)。`);
356
+ }
357
+ else {
358
+ lines.push(`- Note: This path may be blocked by directory rules (\`read_dirs/write_dirs/no_*_dirs\`) or file-extension rules (\`*_file_ext_names/no_*_file_ext_names\`).`);
359
+ }
311
360
  return lines.join('\n');
312
361
  }
@@ -63,6 +63,8 @@ providers:
63
63
  gpt-5.3-codex:
64
64
  name: GPT-5.3 Codex
65
65
  optimal_max_tokens: 200000
66
+ # Caution remediation reinjection cadence in generation turns (default: 10).
67
+ caution_remediation_cadence_generations: 10
66
68
  context_length: 272000
67
69
  input_length: 272000
68
70
  output_length: 32768
@@ -70,6 +72,8 @@ providers:
70
72
  gpt-5.3-codex-spark:
71
73
  name: GPT-5.3 Codex Spark
72
74
  optimal_max_tokens: 80000
75
+ # Caution remediation reinjection cadence in generation turns (default: 10).
76
+ caution_remediation_cadence_generations: 3
73
77
  context_length: 128000
74
78
  input_length: 128000
75
79
  output_length: 32768
@@ -77,6 +81,8 @@ providers:
77
81
  gpt-5.2-codex:
78
82
  name: GPT-5.2 Codex
79
83
  optimal_max_tokens: 200000
84
+ # Caution remediation reinjection cadence in generation turns (default: 10).
85
+ caution_remediation_cadence_generations: 10
80
86
  context_length: 272000
81
87
  input_length: 272000
82
88
  output_length: 32768
@@ -84,6 +90,8 @@ providers:
84
90
  gpt-5.2:
85
91
  name: GPT-5.2
86
92
  optimal_max_tokens: 200000
93
+ # Caution remediation reinjection cadence in generation turns (default: 10).
94
+ caution_remediation_cadence_generations: 10
87
95
  context_length: 272000
88
96
  input_length: 272000
89
97
  output_length: 32768
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DRIVER_V2_DEFAULT_CRITICAL_COUNTDOWN_GENERATIONS = void 0;
3
+ exports.DRIVER_V2_DEFAULT_CAUTION_REMEDIATION_CADENCE_GENERATIONS = exports.DRIVER_V2_DEFAULT_CRITICAL_COUNTDOWN_GENERATIONS = void 0;
4
4
  exports.resetContextHealthRoundState = resetContextHealthRoundState;
5
5
  exports.resolveCriticalCountdownRemaining = resolveCriticalCountdownRemaining;
6
6
  exports.consumeCriticalCountdown = consumeCriticalCountdown;
7
+ exports.resolveCautionRemediationCadenceGenerations = resolveCautionRemediationCadenceGenerations;
7
8
  exports.decideDriverV2ContextHealth = decideDriverV2ContextHealth;
8
9
  exports.DRIVER_V2_DEFAULT_CRITICAL_COUNTDOWN_GENERATIONS = 5;
10
+ exports.DRIVER_V2_DEFAULT_CAUTION_REMEDIATION_CADENCE_GENERATIONS = 10;
9
11
  const contextHealthRoundStateByDialogKey = new Map();
10
12
  function getContextHealthRoundState(dialogKey) {
11
13
  const existing = contextHealthRoundStateByDialogKey.get(dialogKey);
@@ -30,7 +32,6 @@ function resolveCriticalCountdownRemaining(dialogKey, snapshot) {
30
32
  return exports.DRIVER_V2_DEFAULT_CRITICAL_COUNTDOWN_GENERATIONS;
31
33
  }
32
34
  const state = getContextHealthRoundState(dialogKey);
33
- state.lastSeenLevel = snapshot.level;
34
35
  state.criticalCountdownRemaining = undefined;
35
36
  return exports.DRIVER_V2_DEFAULT_CRITICAL_COUNTDOWN_GENERATIONS;
36
37
  }
@@ -56,8 +57,18 @@ function consumeCriticalCountdown(dialogKey) {
56
57
  state.criticalCountdownRemaining = next;
57
58
  return next;
58
59
  }
60
+ function resolveCautionRemediationCadenceGenerations(configured) {
61
+ if (typeof configured !== 'number' || !Number.isFinite(configured)) {
62
+ return exports.DRIVER_V2_DEFAULT_CAUTION_REMEDIATION_CADENCE_GENERATIONS;
63
+ }
64
+ const normalized = Math.floor(configured);
65
+ if (normalized <= 0) {
66
+ return exports.DRIVER_V2_DEFAULT_CAUTION_REMEDIATION_CADENCE_GENERATIONS;
67
+ }
68
+ return normalized;
69
+ }
59
70
  function decideDriverV2ContextHealth(args) {
60
- const { snapshot } = args;
71
+ const { snapshot, dialogKey } = args;
61
72
  if (!snapshot || snapshot.kind !== 'available') {
62
73
  return { kind: 'proceed' };
63
74
  }
@@ -65,11 +76,39 @@ function decideDriverV2ContextHealth(args) {
65
76
  return { kind: 'proceed' };
66
77
  }
67
78
  if (snapshot.level === 'caution') {
68
- return args.hadUserPromptThisGen
69
- ? { kind: 'proceed' }
70
- : { kind: 'continue', reason: 'caution_soft_remediation' };
79
+ const state = getContextHealthRoundState(dialogKey);
80
+ const cadence = resolveCautionRemediationCadenceGenerations(args.cautionRemediationCadenceGenerations);
81
+ const enteringCaution = state.lastSeenLevel !== 'caution';
82
+ state.lastSeenLevel = 'caution';
83
+ state.criticalCountdownRemaining = undefined;
84
+ if (enteringCaution) {
85
+ state.cautionPromptDue = true;
86
+ state.cautionGenerationsSincePrompt = 0;
87
+ }
88
+ else if (state.cautionPromptDue !== true) {
89
+ const previous = typeof state.cautionGenerationsSincePrompt === 'number' &&
90
+ Number.isFinite(state.cautionGenerationsSincePrompt)
91
+ ? Math.max(0, Math.floor(state.cautionGenerationsSincePrompt))
92
+ : 0;
93
+ const next = previous + 1;
94
+ state.cautionGenerationsSincePrompt = next;
95
+ if (next >= cadence) {
96
+ state.cautionPromptDue = true;
97
+ }
98
+ }
99
+ const shouldInjectPrompt = state.cautionPromptDue === true && !args.hadUserPromptThisGen && args.canInjectPromptThisGen;
100
+ if (!shouldInjectPrompt) {
101
+ return { kind: 'proceed' };
102
+ }
103
+ state.cautionPromptDue = false;
104
+ state.cautionGenerationsSincePrompt = 0;
105
+ return { kind: 'continue', reason: 'caution_soft_remediation' };
71
106
  }
72
107
  if (snapshot.level === 'critical') {
108
+ const state = getContextHealthRoundState(dialogKey);
109
+ state.lastSeenLevel = 'critical';
110
+ state.cautionPromptDue = undefined;
111
+ state.cautionGenerationsSincePrompt = undefined;
73
112
  if (args.criticalCountdownRemaining <= 0) {
74
113
  return { kind: 'continue', reason: 'critical_force_new_course' };
75
114
  }
@@ -819,10 +819,15 @@ async function driveDialogStreamCoreV2(dlg, humanPrompt, driveOptions, callbacks
819
819
  if (genIterNo > 1) {
820
820
  const snapshot = dlg.getLastContextHealth();
821
821
  const hasQueuedUpNext = dlg.hasUpNext() || pendingPrompt !== undefined;
822
+ const modelInfoForRemediation = resolveModelInfo(providerCfg, model);
823
+ const cautionRemediationCadenceGenerations = (0, context_health_1.resolveCautionRemediationCadenceGenerations)(modelInfoForRemediation?.caution_remediation_cadence_generations);
822
824
  const criticalCountdownRemaining = (0, context_health_1.resolveCriticalCountdownRemaining)(dlg.id.key(), snapshot);
823
825
  const healthDecision = (0, context_health_1.decideDriverV2ContextHealth)({
826
+ dialogKey: dlg.id.key(),
824
827
  snapshot,
825
828
  hadUserPromptThisGen: isUserOriginPrompt(pendingPrompt),
829
+ canInjectPromptThisGen: !hasQueuedUpNext,
830
+ cautionRemediationCadenceGenerations,
826
831
  criticalCountdownRemaining,
827
832
  });
828
833
  if (healthDecision.kind === 'suspend') {
@@ -10,6 +10,7 @@ const persistence_1 = require("../../persistence");
10
10
  const driver_messages_1 = require("../../shared/i18n/driver-messages");
11
11
  const runtime_language_1 = require("../../shared/runtime-language");
12
12
  const id_1 = require("../../shared/utils/id");
13
+ const client_1 = require("../client");
13
14
  const context_health_1 = require("./context-health");
14
15
  const core_1 = require("./core");
15
16
  const policy_1 = require("./policy");
@@ -164,10 +165,21 @@ async function executeDriveRound(args) {
164
165
  }
165
166
  const snapshot = dialog.getLastContextHealth();
166
167
  const hasQueuedUpNext = dialog.hasUpNext();
168
+ const provider = policy.effectiveAgent.provider ?? minds.team.memberDefaults.provider;
169
+ const model = policy.effectiveAgent.model ?? minds.team.memberDefaults.model;
170
+ let cautionRemediationCadenceGenerations = (0, context_health_1.resolveCautionRemediationCadenceGenerations)(undefined);
171
+ if (provider && model) {
172
+ const llmCfg = await client_1.LlmConfig.load();
173
+ const providerCfg = llmCfg.getProvider(provider);
174
+ cautionRemediationCadenceGenerations = (0, context_health_1.resolveCautionRemediationCadenceGenerations)(providerCfg?.models[model]?.caution_remediation_cadence_generations);
175
+ }
167
176
  const criticalCountdownRemaining = (0, context_health_1.resolveCriticalCountdownRemaining)(dialog.id.key(), snapshot);
168
177
  const healthDecision = (0, context_health_1.decideDriverV2ContextHealth)({
178
+ dialogKey: dialog.id.key(),
169
179
  snapshot,
170
180
  hadUserPromptThisGen: humanPrompt !== undefined,
181
+ canInjectPromptThisGen: !hasQueuedUpNext,
182
+ cautionRemediationCadenceGenerations,
171
183
  criticalCountdownRemaining,
172
184
  });
173
185
  if (healthDecision.kind === 'suspend') {
package/dist/team.js CHANGED
@@ -94,6 +94,14 @@ exports.Team = Team;
94
94
  this.no_read_dirs = params.no_read_dirs;
95
95
  if (params.no_write_dirs !== undefined)
96
96
  this.no_write_dirs = params.no_write_dirs;
97
+ if (params.read_file_ext_names !== undefined)
98
+ this.read_file_ext_names = params.read_file_ext_names;
99
+ if (params.write_file_ext_names !== undefined)
100
+ this.write_file_ext_names = params.write_file_ext_names;
101
+ if (params.no_read_file_ext_names !== undefined)
102
+ this.no_read_file_ext_names = params.no_read_file_ext_names;
103
+ if (params.no_write_file_ext_names !== undefined)
104
+ this.no_write_file_ext_names = params.no_write_file_ext_names;
97
105
  if (params.icon !== undefined)
98
106
  this.icon = params.icon;
99
107
  if (params.streaming !== undefined)
@@ -119,6 +127,10 @@ exports.Team = Team;
119
127
  'write_dirs',
120
128
  'no_read_dirs',
121
129
  'no_write_dirs',
130
+ 'read_file_ext_names',
131
+ 'write_file_ext_names',
132
+ 'no_read_file_ext_names',
133
+ 'no_write_file_ext_names',
122
134
  'icon',
123
135
  'streaming',
124
136
  'hidden',
@@ -224,6 +236,34 @@ exports.Team = Team;
224
236
  }
225
237
  this.no_write_dirs = noWriteDirs;
226
238
  }
239
+ setReadFileExtNames(readFileExtNames) {
240
+ if (readFileExtNames === undefined) {
241
+ delete this.read_file_ext_names;
242
+ return;
243
+ }
244
+ this.read_file_ext_names = readFileExtNames;
245
+ }
246
+ setWriteFileExtNames(writeFileExtNames) {
247
+ if (writeFileExtNames === undefined) {
248
+ delete this.write_file_ext_names;
249
+ return;
250
+ }
251
+ this.write_file_ext_names = writeFileExtNames;
252
+ }
253
+ setNoReadFileExtNames(noReadFileExtNames) {
254
+ if (noReadFileExtNames === undefined) {
255
+ delete this.no_read_file_ext_names;
256
+ return;
257
+ }
258
+ this.no_read_file_ext_names = noReadFileExtNames;
259
+ }
260
+ setNoWriteFileExtNames(noWriteFileExtNames) {
261
+ if (noWriteFileExtNames === undefined) {
262
+ delete this.no_write_file_ext_names;
263
+ return;
264
+ }
265
+ this.no_write_file_ext_names = noWriteFileExtNames;
266
+ }
227
267
  setIcon(icon) {
228
268
  if (icon === undefined) {
229
269
  delete this.icon;
@@ -798,6 +838,10 @@ exports.Team = Team;
798
838
  'write_dirs',
799
839
  'no_read_dirs',
800
840
  'no_write_dirs',
841
+ 'read_file_ext_names',
842
+ 'write_file_ext_names',
843
+ 'no_read_file_ext_names',
844
+ 'no_write_file_ext_names',
801
845
  'icon',
802
846
  'streaming',
803
847
  'hidden',
@@ -1117,6 +1161,38 @@ exports.Team = Team;
1117
1161
  errors.push(asErrorText(err));
1118
1162
  }
1119
1163
  }
1164
+ if (hasOwnKey(rv, 'read_file_ext_names')) {
1165
+ try {
1166
+ overrides.read_file_ext_names = requireDefined(asOptionalStringArray(rv['read_file_ext_names'], `${at}.read_file_ext_names`), `${at}.read_file_ext_names`);
1167
+ }
1168
+ catch (err) {
1169
+ errors.push(asErrorText(err));
1170
+ }
1171
+ }
1172
+ if (hasOwnKey(rv, 'write_file_ext_names')) {
1173
+ try {
1174
+ overrides.write_file_ext_names = requireDefined(asOptionalStringArray(rv['write_file_ext_names'], `${at}.write_file_ext_names`), `${at}.write_file_ext_names`);
1175
+ }
1176
+ catch (err) {
1177
+ errors.push(asErrorText(err));
1178
+ }
1179
+ }
1180
+ if (hasOwnKey(rv, 'no_read_file_ext_names')) {
1181
+ try {
1182
+ overrides.no_read_file_ext_names = requireDefined(asOptionalStringArray(rv['no_read_file_ext_names'], `${at}.no_read_file_ext_names`), `${at}.no_read_file_ext_names`);
1183
+ }
1184
+ catch (err) {
1185
+ errors.push(asErrorText(err));
1186
+ }
1187
+ }
1188
+ if (hasOwnKey(rv, 'no_write_file_ext_names')) {
1189
+ try {
1190
+ overrides.no_write_file_ext_names = requireDefined(asOptionalStringArray(rv['no_write_file_ext_names'], `${at}.no_write_file_ext_names`), `${at}.no_write_file_ext_names`);
1191
+ }
1192
+ catch (err) {
1193
+ errors.push(asErrorText(err));
1194
+ }
1195
+ }
1120
1196
  if (hasOwnKey(rv, 'icon')) {
1121
1197
  try {
1122
1198
  overrides.icon = requireDefined(asOptionalString(rv['icon'], `${at}.icon`), `${at}.icon`);
@@ -1174,6 +1250,14 @@ exports.Team = Team;
1174
1250
  member.setNoReadDirs(overrides.no_read_dirs);
1175
1251
  if (overrides.no_write_dirs !== undefined)
1176
1252
  member.setNoWriteDirs(overrides.no_write_dirs);
1253
+ if (overrides.read_file_ext_names !== undefined)
1254
+ member.setReadFileExtNames(overrides.read_file_ext_names);
1255
+ if (overrides.write_file_ext_names !== undefined)
1256
+ member.setWriteFileExtNames(overrides.write_file_ext_names);
1257
+ if (overrides.no_read_file_ext_names !== undefined)
1258
+ member.setNoReadFileExtNames(overrides.no_read_file_ext_names);
1259
+ if (overrides.no_write_file_ext_names !== undefined)
1260
+ member.setNoWriteFileExtNames(overrides.no_write_file_ext_names);
1177
1261
  if (overrides.icon !== undefined)
1178
1262
  member.setIcon(overrides.icon);
1179
1263
  if (overrides.streaming !== undefined)
@@ -30,6 +30,7 @@ team_mgmt is Dominds' toolset for managing `.minds/` (team configuration and rtw
30
30
  - **Only operates in `.minds/`**: This toolset only operates within the `.minds/` subtree and should not touch other rtws files
31
31
  - **Shell guardrail**: toolset `os` includes `shell_cmd` / `stop_daemon` / `get_daemon_output`; any member with these shell tools must be listed in top-level `shell_specialists`
32
32
  - **Member assets recommended**: strongly recommend `persona/knowledge/lessons` files for every `members.<id>` to define ownership, boundaries, and reusable lessons
33
+ - **Default responder recommendation**: `default_responder` is not technically required, but should be set explicitly to avoid implicit fallback drift
33
34
 
34
35
  ## Quick Navigation
35
36
 
@@ -61,6 +61,7 @@
61
61
  - Must run after modifying `team.yaml`
62
62
  - Clear all team.yaml errors in Problems panel before proceeding
63
63
  - Also reads declarations from `.minds/mcp.yaml` for toolset binding checks; even when MCP toolsets are not loaded in the current scene (e.g. read-mind flows), it still detects unknown/invalid MCP serverId references in `members.<id>.toolsets`
64
+ - Also recommended to confirm `default_responder` is explicitly set (not hard-required, but best practice)
64
65
  - `team_mgmt_validate_mcp_cfg({})`: Validate `.minds/mcp.yaml` and MCP-related problems
65
66
  - Must run after modifying `mcp.yaml`
66
67
  - Clear all MCP-related errors in Problems panel before proceeding
@@ -30,6 +30,7 @@ team_mgmt 是 Dominds 用于管理 `.minds/`(团队配置与 rtws 记忆)的
30
30
  - **只操作 `.minds/`**:该 toolset 只允许操作 `.minds/` 子树,不会也不应触碰 rtws 其他文件
31
31
  - **shell 权限约束**:`os` toolset 包含 `shell_cmd` / `stop_daemon` / `get_daemon_output`;任何拿到这些工具的成员都必须出现在顶层 `shell_specialists`
32
32
  - **成员资产推荐**:强烈建议为每个 `members.<id>` 配置 `persona/knowledge/lessons` 资产文件,显式定义角色职责、边界和经验复用
33
+ - **默认响应者建议**:`default_responder` 虽非技术必填,但强烈建议显式配置,避免隐式兜底带来的行为漂移
33
34
 
34
35
  ## 快速导航
35
36
 
@@ -61,6 +61,7 @@
61
61
  - 修改完 `team.yaml` 后必须运行
62
62
  - 清空 Problems 面板里的 team.yaml 错误后再继续
63
63
  - 会读取 `.minds/mcp.yaml` 声明做 toolset 绑定校验;即使当前场景未加载 MCP toolsets(例如 read mind),也会检查 `members.<id>.toolsets` 是否引用了不存在/无效的 MCP serverId
64
+ - 同时建议检查是否显式设置了 `default_responder`(不是硬性必填,但推荐)
64
65
  - `team_mgmt_validate_mcp_cfg({})`:验证 `.minds/mcp.yaml` 配置与 MCP 相关问题
65
66
  - 修改完 `mcp.yaml` 后必须运行
66
67
  - 清空 Problems 面板里的 MCP 相关错误后再继续
@@ -2698,7 +2698,7 @@ function renderMemberProperties(language) {
2698
2698
  '`diligence-push-max`:鞭策 上限(number)。也接受兼容别名 `diligence_push_max`,但请优先用 `diligence-push-max`。',
2699
2699
  '`streaming`:是否启用流式输出。注意:若该成员解析后的 provider 的 `apiType` 是 `codex`,则 `streaming: false` 属于配置错误(Codex 仅支持流式);会在 team 校验与运行期被视为严重问题并中止请求。',
2700
2700
  '`hidden`(影子/隐藏成员:不出现在系统提示的团队目录里,但仍可被诉请)',
2701
- '`read_dirs` / `write_dirs` / `no_read_dirs` / `no_write_dirs`(冲突规则见 `team_mgmt_manual({ topics: ["permissions"] })`;read 与 write 是独立控制,别默认 write implies read)',
2701
+ '`read_dirs` / `write_dirs` / `no_read_dirs` / `no_write_dirs` / `read_file_ext_names` / `write_file_ext_names` / `no_read_file_ext_names` / `no_write_file_ext_names`(冲突规则见 `team_mgmt_manual({ topics: ["permissions"] })`;read 与 write 是独立控制,别默认 write implies read)',
2702
2702
  ]));
2703
2703
  }
2704
2704
  return (fmtHeader('Member Properties (members.<id>)') +
@@ -2711,12 +2711,13 @@ function renderMemberProperties(language) {
2711
2711
  '`diligence-push-max`: Diligence Push cap (number). Compatibility alias `diligence_push_max` is accepted, but prefer `diligence-push-max`.',
2712
2712
  '`streaming`: whether to enable streaming output. Note: if the member resolves to a provider whose `apiType` is `codex`, then `streaming: false` is a configuration error (Codex is streaming-only); it is treated as a severe issue during validation/runtime and the request will be aborted.',
2713
2713
  '`hidden` (shadow/hidden member: excluded from system-prompt team directory, but callable)',
2714
- '`read_dirs` / `write_dirs` / `no_read_dirs` / `no_write_dirs`(冲突规则见 `team_mgmt_manual({ topics: ["permissions"] })`;read 与 write 是独立控制,别默认 write implies read)',
2714
+ '`read_dirs` / `write_dirs` / `no_read_dirs` / `no_write_dirs` / `read_file_ext_names` / `write_file_ext_names` / `no_read_file_ext_names` / `no_write_file_ext_names`(冲突规则见 `team_mgmt_manual({ topics: ["permissions"] })`;read 与 write 是独立控制,别默认 write implies read)',
2715
2715
  ]));
2716
2716
  }
2717
2717
  function renderTeamManual(language) {
2718
2718
  const common = [
2719
2719
  'member_defaults: strongly recommended to set provider/model explicitly (omitting may fall back to built-in defaults)',
2720
+ 'default_responder: not a hard requirement, but strongly recommended to set explicitly to avoid implicit fallback responder selection and cross-run drift',
2720
2721
  'members: per-agent overrides inherit from member_defaults via prototype fallback',
2721
2722
  'after every modification to `.minds/team.yaml`: you must run `team_mgmt_validate_team_cfg({})` and resolve any Problems panel errors before proceeding to avoid runtime issues (e.g., wrong field types, missing fields, or broken path bindings)',
2722
2723
  'when changing provider/model: validate provider exists + env var is configured (use `team_mgmt_check_provider({ provider_key: "<providerKey>", model: "", all_models: false, live: false, max_models: 0 })`)',
@@ -2731,6 +2732,7 @@ function renderTeamManual(language) {
2731
2732
  fmtList([
2732
2733
  '团队定义入口文件是 `.minds/team.yaml`(当前没有 `.minds/team.yml` / `.minds/team.json` 等别名;也不使用 `.minds/team.yaml` 以外的“等效入口”)。',
2733
2734
  '强烈建议显式设置 `member_defaults.provider` 与 `member_defaults.model`:如果省略,可能会使用实现内置的默认值(以当前实现为准),但可移植性/可复现性会变差,也更容易在环境变量未配置时把系统刷成板砖。',
2735
+ '`default_responder` 虽然不是技术必填项,但实践上强烈建议显式设置:否则会退回到实现内置的响应者选择逻辑(例如按可见成员/内置成员兜底),容易造成跨环境或跨轮次行为漂移。',
2734
2736
  '每次修改 `.minds/team.yaml` 必须运行 `team_mgmt_validate_team_cfg({})`,并在继续之前先清空 Problems 面板里的 team.yaml 相关错误,避免潜在错误进入运行期(例如字段类型错误/字段缺失/路径绑定错误)。',
2735
2737
  '强烈建议为每个成员配置 `.minds/team/<id>/{persona,knowledge,lessons}.*.md` 三类资产,用来明确角色职责、工作边界与可复用经验;同一个 `<id>` 必须在 `team.yaml` 的 `members` 里出现,且在 `.minds/team/<id>/` 下存在对应的 mind 文件。',
2736
2738
  '典型内容示例(可直接作为起点,按团队语境改写):\n```markdown\n# .minds/team/coder/persona.zh.md\n# @coder 角色设定\n## 核心身份\n- 专业程序员,负责按规格完成代码开发。\n## 工作边界\n- 不负责需求分析或产品策略决策。\n- 只根据已确认的开发规格进行实现与重构。\n## 交付标准\n- 输出可运行代码,并附关键验证步骤。\n```\n```markdown\n# .minds/team/coder/lessons.zh.md\n# @coder 经验教训\n- 修改前先定位调用链与数据流,避免“只改表面”。\n- 涉及权限/配置时,改完立即运行对应校验工具并清空 Problems。\n- 涉及高风险改动时,先给最小可审查方案,再逐步扩展。\n```',
@@ -2798,6 +2800,7 @@ function renderTeamManual(language) {
2798
2800
  return (fmtHeader('.minds/team.yaml') +
2799
2801
  fmtList(common.concat([
2800
2802
  'The team definition entrypoint is `.minds/team.yaml` (no `.minds/team.yml` alias today).',
2803
+ '`default_responder` is not technically required, but strongly recommended in practice: without it, runtime falls back to implementation-defined responder selection (for example visible-member/built-in fallback), which can drift across environments/runs.',
2801
2804
  'Strongly recommended: for each member, configure `.minds/team/<id>/{persona,knowledge,lessons}.*.md` assets to define role ownership, work boundaries, and reusable lessons. The same `<id>` must exist in `members.<id>` in `team.yaml`.',
2802
2805
  'Typical content examples (use as a starting point, then adapt to your team context):\n```markdown\n# .minds/team/coder/persona.en.md\n# @coder Persona\n## Core Identity\n- Professional programmer responsible for implementing approved development specs.\n## Work Boundaries\n- Not responsible for requirement discovery or product strategy.\n- Implements/refactors only against confirmed specs.\n## Delivery Standard\n- Deliver runnable code plus key verification steps.\n```\n```markdown\n# .minds/team/coder/lessons.en.md\n# @coder Lessons\n- Trace call chain and data flow before editing; avoid patching only symptoms.\n- After changing permissions/config, run corresponding validators and clear Problems.\n- For high-risk changes, start with a minimal reviewable plan before expansion.\n```',
2803
2806
  'The team mechanism default is long-lived agents (long-lived teammates): `members` is a stable roster of callable teammates, not “on-demand sub-roles”. This is a product mechanism, not a deployment preference.\nTo pick who acts, use `-m/--member <id>` in CLI/TUI.\n`members.<id>.gofor` is a responsibility flashcard / scope / deliverables summary (≤ 5 lines). Use it for fast routing/reminders; put detailed specs in Markdown assets like `.minds/team/<id>/*` or `.minds/team/domains/*.md`.\nExample (`gofor`):\n```yaml\nmembers:\n qa_guard:\n name: QA Guard\n gofor:\n - Own release regression checklist and pass/fail gate\n - Maintain runnable smoke tests and docs\n - Flag high-risk changes and required manual checks\n```\nExample (`gofor`, object; rendered in YAML key order):\n```yaml\nmembers:\n qa_guard:\n name: QA Guard\n gofor:\n Scope: release regression gate\n Deliverables: checklist + runnable scripts\n Non-goals: feature dev\n Interfaces: coordinates with server/webui owners\n```',
@@ -2853,7 +2856,7 @@ async function renderMcpManual(language) {
2853
2856
  '默认按“每个对话租用一个 MCP client”运行(更安全):首次使用该 toolset 会产生 sticky reminder,完成后用 `mcp_release` 释放;如确实是无状态服务器,可配置 `truely-stateless: true` 允许跨对话共享。',
2854
2857
  'stdio 配置格式:`command` 必须是字符串(可执行命令),参数放在 `args`(string[],可省略,默认空数组)。`cwd` 可选(字符串):用于固定相对路径解析目录。',
2855
2858
  '用 `tools.whitelist/blacklist` 控制暴露的工具,用 `transform` 做命名变换。',
2856
- '常见坑:stdio transport 需要可执行命令路径正确,且受成员目录权限(`read_dirs/write_dirs/no_*`)约束;HTTP transport 需要服务可达(url/端口/网络)。',
2859
+ '常见坑:stdio transport 需要可执行命令路径正确,且受成员权限(目录 + 扩展名:`*_dirs/no_*_dirs/*_file_ext_names/no_*_file_ext_names`)约束;HTTP transport 需要服务可达(url/端口/网络)。',
2857
2860
  '高频坑(stdio 路径):若未设置 `cwd`,相对路径按 Dominds 进程工作目录(通常 rtws 根目录)解析;建议显式配置 `cwd` 或直接使用绝对路径。`cwd` 必须存在且是目录。',
2858
2861
  '最小诊断流程(建议顺序):1) 先用 `team_mgmt_check_provider({ provider_key: \"<providerKey>\", model: \"\", all_models: false, live: false, max_models: 0 })` 确认 LLM provider 可用;2) 再检查该成员的目录权限(`team_mgmt_manual({ topics: [\"permissions\"] })`);3) 运行 `team_mgmt_validate_mcp_cfg({})` 汇总 `.minds/mcp.yaml` 与 MCP 问题;4) 必要时 `mcp_restart`,用完记得 `mcp_release`。',
2859
2862
  ]) +
@@ -2903,7 +2906,7 @@ async function renderMcpManual(language) {
2903
2906
  "Default is per-dialog MCP client leasing (safer): first use adds a sticky reminder; call `mcp_release` when you're sure you won't need the toolset soon. If the server is truly stateless, set `truely-stateless: true` to allow cross-dialog sharing.",
2904
2907
  'Stdio shape: `command` must be a string executable; parameters go in `args` (string[], optional, defaults to empty). Optional `cwd` (string) fixes the working directory used for relative paths.',
2905
2908
  'Use `tools.whitelist/blacklist` for exposure control and `transform` for naming transforms.',
2906
- 'Common pitfalls: stdio transport needs a correct executable/command path, and is subject to member directory permissions (`read_dirs/write_dirs/no_*`); HTTP transport requires the server URL to be reachable.',
2909
+ 'Common pitfalls: stdio transport needs a correct executable/command path, and is subject to member permissions (directory + extension: `*_dirs/no_*_dirs/*_file_ext_names/no_*_file_ext_names`); HTTP transport requires the server URL to be reachable.',
2907
2910
  'High-frequency pitfall (stdio paths): if `cwd` is omitted, relative paths are resolved from Dominds process cwd (usually rtws root). Prefer setting `cwd` explicitly or use absolute paths. `cwd` must exist and be a directory.',
2908
2911
  'Minimal diagnostic flow: 1) run `team_mgmt_check_provider({ provider_key: \"<providerKey>\", model: \"\", all_models: false, live: false, max_models: 0 })` to confirm the LLM provider works; 2) review member directory permissions (`team_mgmt_manual({ topics: [\"permissions\"] })`); 3) run `team_mgmt_validate_mcp_cfg({})` to summarize `.minds/mcp.yaml` + MCP issues; 4) use `mcp_restart` if needed, and `mcp_release` when done.',
2909
2912
  ]) +
@@ -2948,12 +2951,15 @@ async function renderMcpManual(language) {
2948
2951
  }
2949
2952
  function renderPermissionsManual(language) {
2950
2953
  if (language === 'zh') {
2951
- return (fmtHeader('目录权限(read_dirs / write_dirs)') +
2954
+ return (fmtHeader('权限(目录 + 扩展名)') +
2952
2955
  fmtList([
2953
- '权限字段:`read_dirs` / `write_dirs` / `no_read_dirs` / `no_write_dirs`。',
2954
- 'deny-list(no_*)优先于 allow-list(*_dirs)。',
2955
- '若未配置 allow-list,则默认允许(在 deny-list 不命中的前提下)。这很方便,但也更容易“权限过大”;如需最小权限,建议显式收敛 allow-list 并对敏感目录加 deny-list。',
2956
+ '目录字段:`read_dirs` / `write_dirs` / `no_read_dirs` / `no_write_dirs`。',
2957
+ '扩展名字段:`read_file_ext_names` / `write_file_ext_names` / `no_read_file_ext_names` / `no_write_file_ext_names`。',
2958
+ 'deny-list(`no_*`)优先于 allow-list(`*`)。目录与扩展名两个维度都需要通过。',
2959
+ '若某维度未配置 allow-list,则该维度默认允许(在 deny-list 不命中的前提下)。这很方便,但也更容易“权限过大”;如需最小权限,建议显式收敛 allow-list 并对敏感目录/扩展名加 deny-list。',
2956
2960
  '`read_dirs` 与 `write_dirs` 是独立控制:不要默认 write implies read(以当前实现的权限检查为准)。',
2961
+ '`read_file_ext_names` 与 `write_file_ext_names` 同样是独立控制。',
2962
+ '扩展名按文件名后缀精确匹配(大小写不敏感,配置项可写 `ts` 或 `.ts`)。',
2957
2963
  '模式支持 `*` 和 `**`,按“目录范围”语义匹配(按目录/路径前缀范围来理解)。',
2958
2964
  '示例:`dominds/**` 会匹配 `dominds/README.md`、`dominds/main/server.ts`、`dominds/webapp/src/...` 等路径。',
2959
2965
  '示例:`.minds/**` 会匹配 `.minds/team.yaml`、`.minds/team/<id>/persona.zh.md` 等;常用于限制普通成员访问 minds 资产。',
@@ -2971,12 +2977,15 @@ function renderPermissionsManual(language) {
2971
2977
  ' no_write_dirs: [".minds/**"]',
2972
2978
  ]));
2973
2979
  }
2974
- return (fmtHeader('Directory Permissions (read_dirs / write_dirs)') +
2980
+ return (fmtHeader('Permissions (Directory + Extension)') +
2975
2981
  fmtList([
2976
- 'Fields: `read_dirs` / `write_dirs` / `no_read_dirs` / `no_write_dirs`.',
2977
- 'Deny-lists (no_*) override allow-lists (*_dirs).',
2978
- 'If no allow-list is configured, access defaults to allow (after deny-list check). This is convenient but can be overly permissive; for least privilege, explicitly narrow allow-lists and deny sensitive directories.',
2982
+ 'Directory fields: `read_dirs` / `write_dirs` / `no_read_dirs` / `no_write_dirs`.',
2983
+ 'Extension fields: `read_file_ext_names` / `write_file_ext_names` / `no_read_file_ext_names` / `no_write_file_ext_names`.',
2984
+ 'Deny-lists (`no_*`) override allow-lists (`*`). Both directory and extension dimensions must pass.',
2985
+ 'If a dimension has no allow-list, that dimension defaults to allow (after deny-list check). This is convenient but can be overly permissive; for least privilege, explicitly narrow allow-lists and deny sensitive directories/extensions.',
2979
2986
  '`read_dirs` and `write_dirs` are controlled independently (do not assume write implies read; follow current implementation).',
2987
+ '`read_file_ext_names` and `write_file_ext_names` are also controlled independently.',
2988
+ 'Extension names are exact suffix matches (case-insensitive; config accepts `ts` or `.ts`).',
2980
2989
  'Patterns support `*` and `**` with directory-scope semantics (think directory/path-range matching).',
2981
2990
  'Example: `dominds/**` matches `dominds/README.md`, `dominds/main/server.ts`, `dominds/webapp/src/...`, etc.',
2982
2991
  'Example: `.minds/**` matches `.minds/team.yaml` and `.minds/team/<id>/persona.*.md`; commonly used to restrict normal members from minds assets.',
@@ -3114,7 +3123,7 @@ function renderTroubleshooting(language) {
3114
3123
  '症状:提示“缺少 provider/model” → 原因:`member_defaults` 或成员覆盖缺失 → 步骤:检查 `.minds/team.yaml` 的 `member_defaults.provider/model`(以及 `members.<id>.provider/model` 是否写错)。',
3115
3124
  '症状:提示“Provider not found” → 原因:provider key 未定义/拼写错误/未按预期合并 defaults → 步骤:检查 `.minds/llm.yaml` 的 provider keys,并确认 `.minds/team.yaml` 引用的 key 存在。',
3116
3125
  '症状:提示“Model not found” → 原因:model key 未定义/拼写错误/不在该 provider 下 → 步骤:用 `team_mgmt_list_models({ provider_pattern: \"<providerKey>\", model_pattern: \"*\" })` 查已有模型 key,再修正 `.minds/team.yaml` 引用或补全 `.minds/llm.yaml`。',
3117
- '症状:提示“permission denied / forbidden / not allowed” → 原因:目录权限(read/write/no_*)命中 deny-list 或未被 allow-list 覆盖 → 步骤:用 `team_mgmt_manual({ topics: [\"permissions\"] })` 复核规则,并检查该成员的 `read_dirs/write_dirs/no_*` 配置。',
3126
+ '症状:提示“permission denied / forbidden / not allowed” → 原因:权限规则(目录或扩展名)命中 deny-list 或未被 allow-list 覆盖 → 步骤:用 `team_mgmt_manual({ topics: [\"permissions\"] })` 复核规则,并检查该成员的 `*_dirs/no_*_dirs/*_file_ext_names/no_*_file_ext_names` 配置。',
3118
3127
  '症状:MCP 不生效 → 原因:mcp 配置错误/服务不可用/租用未释放 → 步骤:先运行 `team_mgmt_validate_mcp_cfg({})` 汇总错误;必要时用 `mcp_restart`;完成后用 `mcp_release` 释放租用。',
3119
3128
  ]));
3120
3129
  }
@@ -3124,7 +3133,7 @@ function renderTroubleshooting(language) {
3124
3133
  'Symptom: "Missing provider/model" → Cause: missing `member_defaults` or member overrides → Steps: check `.minds/team.yaml` `member_defaults.provider/model` (and `members.<id>.provider/model`).',
3125
3134
  'Symptom: "Provider not found" → Cause: provider key not defined / typo / unexpected merge with defaults → Steps: check `.minds/llm.yaml` provider keys and ensure `.minds/team.yaml` references an existing key.',
3126
3135
  'Symptom: "Model not found" → Cause: model key not defined / typo / not under that provider → Steps: run `team_mgmt_list_models({ provider_pattern: \"<providerKey>\", model_pattern: \"*\" })` and fix `.minds/team.yaml` references or update `.minds/llm.yaml`.',
3127
- 'Symptom: "permission denied / forbidden / not allowed" → Cause: directory permissions (read/write/no_*) hit deny-list or not covered by allow-list → Steps: review `team_mgmt_manual({ topics: [\"permissions\"] })` and the member `read_dirs/write_dirs/no_*` config.',
3136
+ 'Symptom: "permission denied / forbidden / not allowed" → Cause: permission rules (directory or extension) hit deny-list or are not covered by allow-list → Steps: review `team_mgmt_manual({ topics: [\"permissions\"] })` and the member `*_dirs/no_*_dirs/*_file_ext_names/no_*_file_ext_names` config.',
3128
3137
  'Symptom: MCP not working → Cause: bad config / server down / leasing issues → Steps: run `team_mgmt_validate_mcp_cfg({})` first, then use `mcp_restart` if needed; call `mcp_release` when done.',
3129
3138
  ]));
3130
3139
  }
@@ -3802,7 +3811,7 @@ exports.teamMgmtManualTool = {
3802
3811
  `\`team_mgmt_manual({ topics: ["team"] })\`:${topicTitle('team')} — .minds/team.yaml(团队花名册、工具集、目录权限入口)`,
3803
3812
  `\`team_mgmt_manual({ topics: ["minds"] })\`:${topicTitle('minds')} — .minds/team/<id>/*(persona/knowledge/lessons 资产怎么写)`,
3804
3813
  `\`team_mgmt_manual({ topics: ["env"] })\`:${topicTitle('env')} — .minds/env.*.md(运行环境提示:在团队介绍之前注入)`,
3805
- `\`team_mgmt_manual({ topics: ["permissions"] })\`:${topicTitle('permissions')} — 目录权限(read_dirs/write_dirs/no_* 语义与冲突规则)`,
3814
+ `\`team_mgmt_manual({ topics: ["permissions"] })\`:${topicTitle('permissions')} — 目录+扩展名权限(*_dirs/no_*_dirs/*_file_ext_names/no_*_file_ext_names 语义与冲突规则)`,
3806
3815
  `\`team_mgmt_manual({ topics: ["toolsets"] })\`:${topicTitle('toolsets')} — toolsets 列表(当前已注册 toolsets;常见三种授权模式)`,
3807
3816
  `\`team_mgmt_manual({ topics: ["llm"] })\`:${topicTitle('llm')} — .minds/llm.yaml(provider key 如何定义/引用;env var 安全边界)`,
3808
3817
  `\`team_mgmt_manual({ topics: ["mcp"] })\`:${topicTitle('mcp')} — .minds/mcp.yaml(MCP serverId→toolset;热重载与租用;可复制最小模板)`,
@@ -3822,7 +3831,7 @@ exports.teamMgmtManualTool = {
3822
3831
  `\`team_mgmt_manual({ topics: ["team"] })\`: ${topicTitle('team')} — .minds/team.yaml (roster/toolsets/permissions entrypoint)`,
3823
3832
  `\`team_mgmt_manual({ topics: ["minds"] })\`: ${topicTitle('minds')} — .minds/team/<id>/* (persona/knowledge/lessons assets)`,
3824
3833
  `\`team_mgmt_manual({ topics: ["env"] })\`: ${topicTitle('env')} — .minds/env.*.md (runtime intro injected before Team Directory)`,
3825
- `\`team_mgmt_manual({ topics: ["permissions"] })\`: ${topicTitle('permissions')} — directory permissions (semantics + conflict rules)`,
3834
+ `\`team_mgmt_manual({ topics: ["permissions"] })\`: ${topicTitle('permissions')} — directory + extension permissions (semantics + conflict rules)`,
3826
3835
  `\`team_mgmt_manual({ topics: ["toolsets"] })\`: ${topicTitle('toolsets')} — toolsets list (registered toolsets + common patterns)`,
3827
3836
  `\`team_mgmt_manual({ topics: ["llm"] })\`: ${topicTitle('llm')} — .minds/llm.yaml (provider keys, env var boundaries)`,
3828
3837
  `\`team_mgmt_manual({ topics: ["mcp"] })\`: ${topicTitle('mcp')} — .minds/mcp.yaml (serverId→toolset, hot reload, leasing, minimal templates)`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dominds",
3
- "version": "0.8.18",
3
+ "version": "0.8.20",
4
4
  "description": "DevOps Mindsets — Sustainable Agentic Product Lifecycle",
5
5
  "type": "commonjs",
6
6
  "private": false,