@aiwg/cli 2026.10.1 → 2026.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +270 -0
  2. package/agentic/code/providers/model-capabilities.v1.json +3091 -24
  3. package/bin/aiwg.mjs +86 -21
  4. package/dist/src/a2a/codecs.js +9 -0
  5. package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
  6. package/dist/src/artifacts/repair.js +9 -4
  7. package/dist/src/channel/manager.mjs +59 -10
  8. package/dist/src/cli/child-process-result.js +40 -0
  9. package/dist/src/cli/env.js +38 -16
  10. package/dist/src/cli/flag-parsing.js +236 -0
  11. package/dist/src/cli/handlers/decision.js +210 -4
  12. package/dist/src/cli/handlers/diagnose.js +9 -2
  13. package/dist/src/cli/handlers/feedback.js +14 -5
  14. package/dist/src/cli/handlers/init.js +245 -30
  15. package/dist/src/cli/handlers/mc.js +23 -0
  16. package/dist/src/cli/handlers/models.js +69 -6
  17. package/dist/src/cli/handlers/ralph.js +3 -0
  18. package/dist/src/cli/handlers/refresh.js +16 -0
  19. package/dist/src/cli/handlers/regenerate.js +14 -0
  20. package/dist/src/cli/handlers/runtime-info.js +24 -9
  21. package/dist/src/cli/handlers/script-runner.js +16 -6
  22. package/dist/src/cli/handlers/session.js +396 -62
  23. package/dist/src/cli/handlers/sessions.js +146 -40
  24. package/dist/src/cli/handlers/setup-manifest.js +439 -39
  25. package/dist/src/cli/handlers/setup.js +139 -39
  26. package/dist/src/cli/handlers/subcommands.js +3 -1
  27. package/dist/src/cli/handlers/team.js +18 -8
  28. package/dist/src/cli/handlers/use.js +13 -0
  29. package/dist/src/cli/handlers/utilities.js +106 -23
  30. package/dist/src/cli/prompt-utils.js +72 -0
  31. package/dist/src/cli/provider-resolution.js +11 -0
  32. package/dist/src/config/aiwg-config.js +268 -4
  33. package/dist/src/config/cli.js +146 -19
  34. package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
  35. package/dist/src/config/project-artifacts-health.mjs +14 -3
  36. package/dist/src/config/user-config.js +36 -10
  37. package/dist/src/decision/adapters/jev.js +75 -27
  38. package/dist/src/decision/driver.js +3 -1
  39. package/dist/src/decision/entry.js +38 -6
  40. package/dist/src/decision/heldout/collector.js +10 -1
  41. package/dist/src/decision/index.js +1 -0
  42. package/dist/src/decision/interop.js +60 -8
  43. package/dist/src/decision/ledger.js +839 -0
  44. package/dist/src/decision/validate.js +6 -3
  45. package/dist/src/effects/dispatch-verifier.js +252 -0
  46. package/dist/src/effects/index.js +1 -0
  47. package/dist/src/features/installer.js +2 -2
  48. package/dist/src/flow/graph-metadata.js +5 -0
  49. package/dist/src/gates/providers/loader.js +3 -1
  50. package/dist/src/installation/manager-command.mjs +1 -1
  51. package/dist/src/installation/manager.mjs +17 -2
  52. package/dist/src/lint/runner.js +2 -2
  53. package/dist/src/mcp/adapters/codex-runtime.js +360 -56
  54. package/dist/src/mcp/cli.mjs +587 -77
  55. package/dist/src/mcp/config-file.mjs +279 -0
  56. package/dist/src/mcp/config-layers.mjs +348 -0
  57. package/dist/src/mcp/credentials.mjs +484 -0
  58. package/dist/src/mcp/grok-build-config.mjs +16 -26
  59. package/dist/src/mcp/helpers.mjs +106 -2
  60. package/dist/src/mcp/omp-config.mjs +50 -29
  61. package/dist/src/mcp/profiles.js +97 -23
  62. package/dist/src/mcp/profiles.mjs +99 -27
  63. package/dist/src/mcp/registry.js +264 -57
  64. package/dist/src/mcp/registry.mjs +198 -45
  65. package/dist/src/mcp/server.mjs +403 -20
  66. package/dist/src/mcp/toml-string.mjs +9 -0
  67. package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
  68. package/dist/src/mcp/tool-filters.mjs +357 -0
  69. package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
  70. package/dist/src/mcp/tools/decision.mjs +21 -3
  71. package/dist/src/mcp/tools/orchestration.mjs +38 -0
  72. package/dist/src/mcp/tools/subsystems.mjs +39 -8
  73. package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
  74. package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
  75. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
  76. package/dist/src/memory/project-registry.js +2 -2
  77. package/dist/src/mission-protocol/codecs.js +639 -1
  78. package/dist/src/mission-protocol/types.js +110 -0
  79. package/dist/src/mission-protocol/worker-status.js +2 -0
  80. package/dist/src/models/config-loader.js +70 -13
  81. package/dist/src/models/grok-build-models.js +93 -41
  82. package/dist/src/models/model-capabilities.v1.json +3091 -24
  83. package/dist/src/models/model-discovery.js +862 -63
  84. package/dist/src/models/provider-policy.js +625 -11
  85. package/dist/src/models/qualification-digest.mjs +17 -0
  86. package/dist/src/models/resolver.js +259 -0
  87. package/dist/src/models/worker-direct-readiness.js +529 -0
  88. package/dist/src/models/worker-dispatch-offer.js +169 -0
  89. package/dist/src/models/worker-health-readiness.js +131 -0
  90. package/dist/src/models/worker-installation.mjs +879 -0
  91. package/dist/src/models/wrapper-route.js +18 -0
  92. package/dist/src/ops/registry.js +9 -14
  93. package/dist/src/plugins/standalone-packager.js +4 -3
  94. package/dist/src/process/bounded-child-process.mjs +387 -0
  95. package/dist/src/process/cli-command.mjs +75 -0
  96. package/dist/src/providers/capability-matrix.js +8 -0
  97. package/dist/src/providers/capability-matrix.yaml +270 -0
  98. package/dist/src/providers/grok-build-headless.js +43 -21
  99. package/dist/src/providers/provider-definitions.js +9 -1
  100. package/dist/src/providers/provider-definitions.mjs +9 -2
  101. package/dist/src/serve/executor-registry.js +16 -24
  102. package/dist/src/serve/fleet-mission-conductor.js +9 -0
  103. package/dist/src/serve/mission-conductor.js +9 -0
  104. package/dist/src/serve/shared-host-scheduler.js +2 -0
  105. package/dist/src/serve/worker-invocation.js +538 -0
  106. package/dist/src/serve/worker-stdin-frame.mjs +107 -0
  107. package/dist/src/sessions/adapter-registry.js +45 -0
  108. package/dist/src/sessions/adapters/generic.js +37 -3
  109. package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
  110. package/dist/src/sessions/adapters/grokbot.js +423 -24
  111. package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
  112. package/dist/src/sessions/adapters/muse.js +68 -29
  113. package/dist/src/sessions/adapters/pi.js +1 -0
  114. package/dist/src/sessions/batch-contracts.js +13 -1
  115. package/dist/src/sessions/batch-import.js +23 -42
  116. package/dist/src/sessions/candidates.js +47 -11
  117. package/dist/src/sessions/contracts.js +35 -0
  118. package/dist/src/sessions/coverage-preflight.js +134 -0
  119. package/dist/src/sessions/discovery.js +3 -0
  120. package/dist/src/sessions/grok-build-cli.js +67 -5
  121. package/dist/src/sessions/importer.js +24 -8
  122. package/dist/src/sessions/index.js +2 -0
  123. package/dist/src/sessions/repository.js +54 -13
  124. package/dist/src/sessions/workspace-discovery.js +99 -37
  125. package/dist/src/skills/adapters/clawhub.js +10 -8
  126. package/dist/src/skills/adapters/openclaw.js +10 -7
  127. package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
  128. package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
  129. package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
  130. package/dist/src/smiths/mcpsmith/example.js +1 -1
  131. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
  132. package/dist/src/storage/backends/logseq.js +68 -7
  133. package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
  134. package/dist/src/testing/generators/test-code-generator.js +57 -30
  135. package/dist/src/uhp/client.js +11 -1
  136. package/dist/src/uhp/mission.js +5 -0
  137. package/dist/src/update/checker.mjs +5 -5
  138. package/dist/src/update/notifier.mjs +54 -2
  139. package/dist/src/writing/fidelity.js +18 -1
  140. package/package.json +18 -1
  141. package/schemas/mission-v1.schema.json +2429 -0
  142. package/schemas/models/model-catalog.v1.schema.json +301 -0
  143. package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
  144. package/schemas/models/model-policy.v1.schema.json +14 -0
  145. package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
  146. package/tools/_resolve-impl.mjs +39 -0
  147. package/tools/agents/deploy-agents.mjs +15 -2
  148. package/tools/agents/providers/base.mjs +49 -32
  149. package/tools/agents/providers/grokbot.mjs +11 -12
  150. package/tools/agents/providers/hermes.mjs +6 -3
  151. package/tools/agents/providers/omp.mjs +5 -2
  152. package/tools/agents/providers/openclaw-translator.mjs +19 -5
  153. package/tools/providers/deepseek-harness-transport.mjs +358 -0
  154. package/tools/providers/omp-transport.mjs +192 -0
  155. package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
  156. package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
  157. package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
  158. package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
  159. package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
  160. package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
  161. package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
  162. package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
  163. package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
  164. package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
  165. package/tools/ralph-external/lib/worker-health.mjs +439 -0
  166. package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
@@ -1,7 +1,12 @@
1
+ import { tomlString } from './toml-string.mjs';
2
+ import { isLowerLayerEntry, loadLayered, lowerEntryQualified, resolveConfigLayers, saveLayerData } from './config-layers.mjs';
3
+ import { assertConfigDestination, assertProjectCredentials, classifyMcpDestination, projectCredentialRemedy, writeConfigAtomic, writeConfigTransaction } from './config-file.mjs';
1
4
  import { manageOmpMcp } from './omp-config.mjs';
2
5
  import { manageGrokBuildMcp } from './grok-build-config.mjs';
3
6
  import { replaceServer } from './toml-editor.mjs';
4
7
  import { resolveOmpPaths } from '../providers/omp-paths.mjs';
8
+ import { applyJsonToolFilterPlan, claudeSettingsPath, prepareClaudePermissions, planToolFilters } from './tool-filters.mjs';
9
+ import { assertCredentialPolicy, resolveCredentialPolicy, renderCredentialMaps, validateEnvReferenceName } from './credentials.mjs';
5
10
  /**
6
11
  * MCP Server Registry (Runtime ESM)
7
12
  *
@@ -15,7 +20,8 @@ import { resolveOmpPaths } from '../providers/omp-paths.mjs';
15
20
  * @implements #554
16
21
  */
17
22
 
18
- import { readFile, writeFile, mkdir } from 'fs/promises';
23
+ import { readFile } from 'fs/promises';
24
+ import { qualificationDigest } from '../models/qualification-digest.mjs';
19
25
  import { dirname, resolve } from 'path';
20
26
  import { homedir } from 'os';
21
27
  import { existsSync } from 'fs';
@@ -73,14 +79,31 @@ function validateCredentialReferences(def) {
73
79
  throw new Error(`Invalid MCP header environment variable reference "${envName}"`);
74
80
  }
75
81
  }
82
+ for (const [key, envName] of Object.entries(def.envFrom || {})) {
83
+ if (!ENV_REFERENCE_NAME.test(key)) throw new Error(`Invalid MCP env-from variable name "${key}"`);
84
+ if (!ENV_REFERENCE_NAME.test(envName)) {
85
+ throw new Error(`Invalid MCP env-from environment variable reference "${envName}"`);
86
+ }
87
+ }
88
+ }
89
+
90
+ // The typed worker dispatch boundary owns qualification. Legacy runtime edits
91
+ // invalidate its binding without importing the TypeScript policy owner.
92
+ export function workerServerDigest(server) {
93
+ const { workerQualification, description, injectedProviders, addedAt, updatedAt, ...execution } = server;
94
+ return qualificationDigest(execution);
76
95
  }
77
96
 
78
97
  export class McpServerRegistry {
79
98
  #configDir;
80
99
  #cache = null;
81
100
 
101
+ #layers;
102
+ #layering = null;
103
+
82
104
  constructor(configDirOverride) {
83
- this.#configDir = resolveConfigDir(configDirOverride);
105
+ this.#layers = resolveConfigLayers(configDirOverride);
106
+ this.#configDir = this.#layers ? this.#layers[this.#layers.length - 1] : resolveConfigDir(configDirOverride);
84
107
  }
85
108
 
86
109
  getPath() {
@@ -90,6 +113,13 @@ export class McpServerRegistry {
90
113
  async load() {
91
114
  if (this.#cache) return this.#cache;
92
115
 
116
+ if (this.#layers) {
117
+ const layered = await loadLayered(this.#layers, REGISTRY_FILENAME, 'servers', DEFAULT_REGISTRY);
118
+ this.#cache = layered.data;
119
+ this.#layering = layered.layering;
120
+ return this.#cache;
121
+ }
122
+
93
123
  const filePath = this.getPath();
94
124
  try {
95
125
  const content = await readFile(filePath, 'utf-8');
@@ -103,19 +133,36 @@ export class McpServerRegistry {
103
133
  this.#cache = { ...DEFAULT_REGISTRY, servers: {} };
104
134
  }
105
135
 
136
+ try {
137
+ resolveCredentialPolicy({ registryPolicy: this.#cache.credentialPolicy, env: {} });
138
+ } catch (error) {
139
+ this.#cache = null;
140
+ throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
141
+ }
142
+
106
143
  return this.#cache;
107
144
  }
108
145
 
109
- async save() {
110
- if (!this.#cache) return;
111
- await mkdir(this.#configDir, { recursive: true });
112
- const filePath = this.getPath();
113
- await writeFile(filePath, JSON.stringify(this.#cache, null, 2) + '\n', 'utf-8');
146
+ async save(data = this.#cache, explicitPolicy = false) {
147
+ if (!data) return;
148
+ // A fresh instance has no ownership record yet; load it first so inherited
149
+ // entries and inherited policy are never copied into the write layer.
150
+ let layering = this.#layering;
151
+ if (this.#layers && !layering) {
152
+ await this.load();
153
+ layering = this.#layering;
154
+ }
155
+ await saveLayerData(this.getPath(), data, 'servers', this.#layers, layering, explicitPolicy);
156
+ this.#cache = data;
157
+ if (this.#layers) {
158
+ this.clearCache();
159
+ await this.load();
160
+ }
114
161
  }
115
162
 
116
163
  async add(def) {
117
164
  validateCredentialReferences(def);
118
- const data = await this.load();
165
+ const data = structuredClone(await this.load());
119
166
 
120
167
  if (data.servers[def.name]) {
121
168
  throw new Error(`Server "${def.name}" already exists. Use "update" to modify it.`);
@@ -123,27 +170,34 @@ export class McpServerRegistry {
123
170
 
124
171
  data.servers[def.name] = {
125
172
  ...def,
173
+ workerQualification: undefined,
126
174
  injectedProviders: [],
127
175
  addedAt: new Date().toISOString(),
128
176
  updatedAt: new Date().toISOString(),
129
177
  };
130
178
 
131
- await this.save();
179
+ await this.save(data);
132
180
  }
133
181
 
134
182
  async remove(name) {
135
- const data = await this.load();
183
+ const data = structuredClone(await this.load());
136
184
 
137
185
  if (!data.servers[name]) {
138
186
  throw new Error(`Server "${name}" not found.`);
139
187
  }
140
188
 
189
+ if (this.#layering && isLowerLayerEntry(this.#layering, name)) {
190
+ throw new Error(`Server "${name}" is defined in a lower configuration layer; remove it there.`);
191
+ }
192
+ if (this.#layering && lowerEntryQualified(this.#layering, name)) {
193
+ throw new Error(`Server "${name}" overrides a qualified lower-layer entry; removing the override would silently re-expose its qualification. Remove the lower-layer entry in its own layer instead.`);
194
+ }
141
195
  delete data.servers[name];
142
- await this.save();
196
+ await this.save(data);
143
197
  }
144
198
 
145
199
  async update(name, updates) {
146
- const data = await this.load();
200
+ const data = structuredClone(await this.load());
147
201
 
148
202
  if (!data.servers[name]) {
149
203
  throw new Error(`Server "${name}" not found.`);
@@ -156,9 +210,11 @@ export class McpServerRegistry {
156
210
  updatedAt: new Date().toISOString(),
157
211
  };
158
212
  validateCredentialReferences(next);
213
+ next.workerQualification = workerServerDigest(next) === workerServerDigest(data.servers[name])
214
+ ? data.servers[name].workerQualification : undefined;
159
215
  data.servers[name] = next;
160
216
 
161
- await this.save();
217
+ await this.save(data);
162
218
  }
163
219
 
164
220
  async get(name) {
@@ -172,7 +228,7 @@ export class McpServerRegistry {
172
228
  }
173
229
 
174
230
  async recordInjection(name, provider) {
175
- const data = await this.load();
231
+ const data = structuredClone(await this.load());
176
232
  const server = data.servers[name];
177
233
  if (!server) return;
178
234
 
@@ -183,7 +239,7 @@ export class McpServerRegistry {
183
239
  server.injectedProviders.push(provider);
184
240
  }
185
241
 
186
- await this.save();
242
+ await this.save(data);
187
243
  }
188
244
 
189
245
  async getInjectedProviders() {
@@ -197,6 +253,36 @@ export class McpServerRegistry {
197
253
  return [...providers];
198
254
  }
199
255
 
256
+ async getCredentialPolicy() {
257
+ const data = await this.load();
258
+ return data.credentialPolicy;
259
+ }
260
+
261
+ /** The enforced organisation floor for this registry, if any layer below the write layer sets one. */
262
+ async getCredentialPolicyFloor() {
263
+ await this.load();
264
+ return this.#layering?.lowerPolicy;
265
+ }
266
+
267
+ /** The config file of the layer that set the enforced floor, for diagnostics. */
268
+ async getCredentialPolicyFloorSource() {
269
+ await this.load();
270
+ return this.#layering?.lowerPolicySource;
271
+ }
272
+
273
+ async setCredentialPolicy(policy) {
274
+ const data = structuredClone(await this.load());
275
+ const floorPolicy = this.#layering?.lowerPolicy;
276
+ const floorSource = this.#layering?.lowerPolicySource;
277
+ const effective = resolveCredentialPolicy({ flag: policy, floorPolicy, floorSource, env: {} });
278
+ if (effective !== policy) {
279
+ const location = floorSource ? ` (set in ${floorSource})` : '';
280
+ throw new Error(`Cannot relax MCP credential policy floor "${effective}"${location} to "${policy}".`);
281
+ }
282
+ data.credentialPolicy = policy;
283
+ await this.save(data, true);
284
+ }
285
+
200
286
  clearCache() {
201
287
  this.#cache = null;
202
288
  }
@@ -206,27 +292,48 @@ export class McpServerRegistry {
206
292
  // Provider injection logic
207
293
  // ============================================
208
294
 
209
- function buildServerConfig(server, provider) {
295
+ export function buildServerConfig(server, provider) {
210
296
  const mcpDefinition = getMcpInjectionDefinition(provider);
297
+ // TOML providers render references through their own named keys in
298
+ // buildServerToml; unconditional interpolation would refuse them here even
299
+ // though the TOML writer handles them.
300
+ const { env, headers } = mcpDefinition?.serverConfigFormat === 'toml'
301
+ ? {}
302
+ : renderCredentialMaps(server, normalizeRuntimeProviderId(provider) || provider);
211
303
 
212
304
  switch (mcpDefinition?.serverConfigFormat) {
305
+ case 'claude-code': {
306
+ if (server.type === 'stdio') {
307
+ return {
308
+ command: server.command,
309
+ args: server.args || [],
310
+ ...(env ? { env } : {}),
311
+ };
312
+ }
313
+ // Claude Code skips a url entry that has no type.
314
+ return {
315
+ type: server.type,
316
+ url: server.url,
317
+ ...(headers ? { headers } : {}),
318
+ };
319
+ }
213
320
  case 'antigravity': {
214
321
  if (server.type === 'stdio') {
215
- return { command: server.command, args: server.args || [], ...(server.env ? { env: server.env } : {}) };
322
+ return { command: server.command, args: server.args || [], ...(env ? { env } : {}) };
216
323
  }
217
- return { serverUrl: server.url, ...(server.headers ? { headers: server.headers } : {}) };
324
+ return { serverUrl: server.url, ...(headers ? { headers } : {}) };
218
325
  }
219
326
  case 'standard': {
220
327
  if (server.type === 'stdio') {
221
328
  return {
222
329
  command: server.command,
223
330
  args: server.args || [],
224
- ...(server.env ? { env: server.env } : {}),
331
+ ...(env ? { env } : {}),
225
332
  };
226
333
  }
227
334
  return {
228
335
  url: server.url,
229
- ...(server.headers ? { headers: server.headers } : {}),
336
+ ...(headers ? { headers } : {}),
230
337
  };
231
338
  }
232
339
 
@@ -237,29 +344,30 @@ function buildServerConfig(server, provider) {
237
344
  command: server.command,
238
345
  args: server.args || [],
239
346
  disabled: false,
240
- ...(server.env ? { env: server.env } : {}),
347
+ ...(env ? { env } : {}),
241
348
  };
242
349
  }
243
350
  return {
244
351
  type: server.type,
245
352
  url: server.url,
246
353
  disabled: false,
247
- ...(server.headers ? { headers: server.headers } : {}),
354
+ ...(headers ? { headers } : {}),
248
355
  };
249
356
  }
250
357
 
251
358
  case 'opencode': {
252
359
  if (server.type === 'stdio') {
360
+ // opencode names a local server's variables `environment`.
253
361
  return {
254
362
  type: 'local',
255
363
  command: [server.command, ...(server.args || [])],
256
- ...(server.env ? { env: server.env } : {}),
364
+ ...(env ? { environment: env } : {}),
257
365
  };
258
366
  }
259
367
  return {
260
368
  type: 'remote',
261
369
  url: server.url,
262
- ...(server.headers ? { headers: server.headers } : {}),
370
+ ...(headers ? { headers } : {}),
263
371
  };
264
372
  }
265
373
 
@@ -271,20 +379,15 @@ function buildServerConfig(server, provider) {
271
379
  }
272
380
  }
273
381
 
274
- function tomlString(value) {
275
- if (typeof value !== 'string' || [...value].some(char => {
276
- const point = char.codePointAt(0);
277
- return point >= 0xd800 && point <= 0xdfff;
278
- })) throw new Error('TOML values must be strings containing valid Unicode scalar values');
279
- // JSON escapes align with TOML basic strings except DEL must also be escaped.
280
- return JSON.stringify(value).replace(/\u007f/g, '\\u007f');
281
- }
282
-
283
382
  function tomlKey(value) {
284
383
  return typeof value === 'string' && /^[A-Za-z0-9_-]+$/.test(value) ? value : tomlString(value);
285
384
  }
286
385
 
287
- function buildServerToml(server) {
386
+ function tomlInlineTable(values) {
387
+ return `{ ${Object.entries(values).map(([name, value]) => `${tomlKey(name)} = ${tomlString(value)}`).join(', ')} }`;
388
+ }
389
+
390
+ export function buildServerToml(server) {
288
391
  const lines = [];
289
392
  lines.push(`[mcp_servers.${tomlKey(server.name)}]`);
290
393
 
@@ -294,8 +397,21 @@ function buildServerToml(server) {
294
397
  const argsStr = server.args.map(a => tomlString(a)).join(', ');
295
398
  lines.push(`args = [${argsStr}]`);
296
399
  }
400
+ if (server.env && Object.keys(server.env).length > 0) lines.push(`env = ${tomlInlineTable(server.env)}`);
401
+ // Codex forwards a variable only under its own name (env_vars).
402
+ const forwarded = Object.entries(server.envFrom || {});
403
+ for (const [key, name] of forwarded) {
404
+ validateEnvReferenceName(name);
405
+ if (key !== name) {
406
+ throw new Error(`MCP server "${server.name}": Codex forwards environment variables under their own name only; env-from ${key}=${name} cannot be rendered. Use ${name}=${name}.`);
407
+ }
408
+ }
409
+ if (forwarded.length > 0) lines.push(`env_vars = [${forwarded.map(([name]) => tomlString(name)).join(', ')}]`);
297
410
  } else {
298
411
  lines.push(`url = ${tomlString(server.url)}`);
412
+ if (server.headers && Object.keys(server.headers).length > 0) lines.push(`http_headers = ${tomlInlineTable(server.headers)}`);
413
+ for (const name of Object.values(server.headerEnv || {})) validateEnvReferenceName(name);
414
+ if (server.headerEnv && Object.keys(server.headerEnv).length > 0) lines.push(`env_http_headers = ${tomlInlineTable(server.headerEnv)}`);
299
415
  }
300
416
 
301
417
  lines.push(`startup_timeout_sec = 10.0`);
@@ -327,18 +443,39 @@ export async function injectServers(registry, provider, options = {}) {
327
443
  }
328
444
 
329
445
  let allServers = await registry.list();
330
- if (serverFilter && serverFilter.length > 0) {
446
+ if (serverFilter !== undefined) {
331
447
  allServers = allServers.filter(s => serverFilter.includes(s.name));
332
448
  }
333
449
 
450
+ const toolPlan = options.toolFilters
451
+ ? planToolFilters(provider, allServers.map(server => server.name), options.toolFilters)
452
+ : null;
453
+ if (toolPlan) result.warnings = toolPlan.warnings;
334
454
  if (allServers.length === 0) {
335
455
  result.error = 'No servers to inject. Use "aiwg mcp add" first.';
336
456
  return result;
337
457
  }
338
458
 
459
+ assertCredentialPolicy(allServers, resolveCredentialPolicy({
460
+ flag: options.credentialPolicy, registryPolicy: await registry.getCredentialPolicy(),
461
+ floorPolicy: await registry.getCredentialPolicyFloor(),
462
+ floorSource: await registry.getCredentialPolicyFloorSource(),
463
+ }));
464
+
465
+ // The shareable-project decision follows the actual resolved destination,
466
+ // never the requested scope: a `--scope user` request for a provider without
467
+ // a user-scope destination still resolves to the project file and is refused.
468
+ const { userScope, projectRoot } = classifyMcpDestination(provider, options.scope, configPath, projectDir);
469
+ await assertConfigDestination(configPath, projectRoot);
470
+ // Every destination inside the project directory is meant to be committed to
471
+ // version control; home destinations are private, so literals there are not
472
+ // copied into a shared file.
473
+ if (projectRoot) {
474
+ assertProjectCredentials(allServers, configPath, projectCredentialRemedy(provider, projectDir));
475
+ }
339
476
  if (normalizedProvider === 'omp') {
340
477
  try {
341
- const managed = await manageOmpMcp(configPath, allServers, { dryRun });
478
+ const managed = await manageOmpMcp(configPath, allServers, { dryRun, userScope, projectRoot });
342
479
  if (!dryRun) for (const server of allServers) await registry.recordInjection(server.name, 'omp');
343
480
  return { ...result, ...managed };
344
481
  } catch (error) {
@@ -350,6 +487,7 @@ export async function injectServers(registry, provider, options = {}) {
350
487
  try {
351
488
  const managed = await manageGrokBuildMcp(configPath, allServers, {
352
489
  dryRun,
490
+ userScope,
353
491
  root: options.scope === 'user'
354
492
  ? dirname(dirname(configPath))
355
493
  : resolve(projectDir),
@@ -365,13 +503,13 @@ export async function injectServers(registry, provider, options = {}) {
365
503
  }
366
504
 
367
505
  if (mcpDefinition?.configFormat === 'toml') {
368
- return injectToml(registry, allServers, configPath, provider, dryRun, result);
506
+ return injectToml(registry, allServers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan);
369
507
  }
370
508
 
371
- return injectJson(registry, allServers, configPath, provider, dryRun, result);
509
+ return injectJson(registry, allServers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan, { projectDir, scope: options.scope });
372
510
  }
373
511
 
374
- async function injectJson(registry, servers, configPath, provider, dryRun, result) {
512
+ async function injectJson(registry, servers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan = null, location = {}) {
375
513
  let existing = {};
376
514
  try {
377
515
  const content = await readFile(configPath, 'utf-8');
@@ -404,10 +542,24 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
404
542
  }
405
543
 
406
544
  const merged = { ...existing, [mcpKey]: newServers };
545
+ if (toolPlan) {
546
+ applyJsonToolFilterPlan(merged, mcpKey, toolPlan);
547
+ }
548
+
549
+ const isClaude = normalizeRuntimeProviderId(provider) === 'claude-code';
550
+ const settingsPath = isClaude ? claudeSettingsPath(location.projectDir, location.scope) : null;
551
+ const settings = settingsPath ? await prepareClaudePermissions(
552
+ settingsPath, toolPlan?.claudePermissions || { deny: [], allow: [] },
553
+ { userScope, projectRoot, managedDir: dirname(registry.getPath()), mcpPath: configPath },
554
+ ) : null;
555
+ if (settings?.active && settingsPath) result.settingsPath = settingsPath;
556
+ if (settings?.warnings.length) result.warnings = [...(result.warnings || []), ...settings.warnings];
407
557
 
408
558
  if (!dryRun) {
409
- await mkdir(resolve(configPath, '..'), { recursive: true });
410
- await writeFile(configPath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
559
+ await writeConfigTransaction([
560
+ { file: configPath, content: JSON.stringify(merged, null, 2) + '\n', options: { userScope, projectRoot } },
561
+ ...(settings?.writes || []),
562
+ ]);
411
563
 
412
564
  for (const server of servers) {
413
565
  if (normalizeRuntimeProviderId(provider) === 'antigravity' && !result.serversInjected.includes(server.name)) continue;
@@ -415,10 +567,11 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
415
567
  }
416
568
  }
417
569
 
570
+
418
571
  return result;
419
572
  }
420
573
 
421
- async function injectToml(registry, servers, configPath, provider, dryRun, result) {
574
+ async function injectToml(registry, servers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan = null) {
422
575
  let existing = '';
423
576
  try {
424
577
  existing = await readFile(configPath, 'utf-8');
@@ -427,15 +580,15 @@ async function injectToml(registry, servers, configPath, provider, dryRun, resul
427
580
  }
428
581
 
429
582
  for (const server of servers) {
430
- const edited = replaceServer(existing, server.name, buildServerToml(server));
583
+ const filterLines = toolPlan?.tomlLines[server.name] || [];
584
+ const edited = replaceServer(existing, server.name, [buildServerToml(server), ...filterLines].join('\n'));
431
585
  existing = edited.text;
432
586
  if (edited.alreadyPresent) result.alreadyPresent.push(server.name);
433
587
  result.serversInjected.push(server.name);
434
588
  }
435
589
 
436
590
  if (!dryRun) {
437
- await mkdir(resolve(configPath, '..'), { recursive: true });
438
- await writeFile(configPath, existing, 'utf-8');
591
+ await writeConfigAtomic(configPath, existing, { userScope, projectRoot });
439
592
 
440
593
  for (const server of servers) {
441
594
  await registry.recordInjection(server.name, provider);