@aiwg/cli 2026.9.1 → 2026.9.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 (71) hide show
  1. package/agentic/code/providers/antigravity/provider-contract.v1.json +121 -0
  2. package/agentic/code/providers/capability-matrix.yaml +88 -2
  3. package/agentic/code/providers/model-capabilities.v1.json +42 -0
  4. package/agentic/code/providers/model-catalog.v1.json +37 -0
  5. package/agentic/code/providers/omp/README.md +58 -0
  6. package/agentic/code/providers/omp/aiwg-bridge.ts +52 -0
  7. package/agentic/code/providers/pi/aiwg-bridge.ts +26 -0
  8. package/dist/src/agents/agent-deployer.js +18 -0
  9. package/dist/src/agents/agent-packager.js +25 -0
  10. package/dist/src/artifacts/backends/sqlite-backend.js +18 -10
  11. package/dist/src/artifacts/query-engine.js +30 -0
  12. package/dist/src/auth/credential-store.js +6 -0
  13. package/dist/src/cli/agent-spawn.js +13 -2
  14. package/dist/src/cli/handlers/help.js +3 -1
  15. package/dist/src/cli/handlers/init.js +2 -0
  16. package/dist/src/cli/handlers/models.js +1 -1
  17. package/dist/src/cli/handlers/runtime-info.js +8 -1
  18. package/dist/src/cli/handlers/session.js +5 -4
  19. package/dist/src/cli/handlers/sessions.js +54 -19
  20. package/dist/src/cli/handlers/setup.js +9 -2
  21. package/dist/src/cli/handlers/steward.js +13 -2
  22. package/dist/src/cli/handlers/subcommands.js +11 -0
  23. package/dist/src/cli/handlers/team.js +68 -7
  24. package/dist/src/cli/handlers/use.js +121 -9
  25. package/dist/src/cli/scope-resolver.js +7 -0
  26. package/dist/src/config/aiwg-config.js +1 -0
  27. package/dist/src/dataset/fortemi-live-qualification.d.ts +53 -0
  28. package/dist/src/dataset/fortemi-live-qualification.js +297 -0
  29. package/dist/src/dataset/index.d.ts +1 -0
  30. package/dist/src/dataset/index.js +1 -0
  31. package/dist/src/mcp/cli.mjs +30 -1
  32. package/dist/src/mcp/omp-config.mjs +128 -0
  33. package/dist/src/mcp/registry.js +45 -5
  34. package/dist/src/mcp/registry.mjs +29 -6
  35. package/dist/src/models/model-capabilities.v1.json +42 -0
  36. package/dist/src/models/model-catalog.v1.json +37 -0
  37. package/dist/src/models/model-discovery.js +78 -5
  38. package/dist/src/models/provider-policy.js +6 -3
  39. package/dist/src/plugin/skill-command-translator.js +2 -0
  40. package/dist/src/providers/capability-matrix.yaml +88 -2
  41. package/dist/src/providers/omp-agent.mjs +40 -0
  42. package/dist/src/providers/omp-diagnostics.mjs +15 -0
  43. package/dist/src/providers/omp-paths.mjs +38 -0
  44. package/dist/src/providers/provider-definitions.js +83 -0
  45. package/dist/src/providers/provider-definitions.mjs +25 -1
  46. package/dist/src/providers/provider-inventory.js +2 -0
  47. package/dist/src/sessions/adapters/omp.js +203 -0
  48. package/dist/src/sessions/adapters/pi.js +141 -0
  49. package/dist/src/sessions/batch-import.js +7 -0
  50. package/dist/src/sessions/contracts.js +1 -1
  51. package/dist/src/sessions/importer.js +4 -3
  52. package/dist/src/sessions/index.js +2 -0
  53. package/dist/src/sessions/readers.js +4 -3
  54. package/dist/src/sessions/workspace-discovery.js +22 -2
  55. package/dist/src/skills/deployer.js +21 -1
  56. package/dist/src/smiths/agentsmith/generator.js +1 -0
  57. package/dist/src/smiths/context-pipeline/parallelism-section.js +2 -0
  58. package/dist/src/smiths/context-pipeline/provider-policy.js +2 -2
  59. package/dist/src/smiths/context-pipeline/workspace-context.js +2 -2
  60. package/dist/src/storage/backends/fortemi.js +142 -17
  61. package/dist/src/storage/fortemi-qualification-receipt.js +206 -0
  62. package/dist/src/storage/fortemi-qualification.js +67 -6
  63. package/dist/src/storage/index.js +1 -0
  64. package/package.json +2 -1
  65. package/schemas/dataset/fortemi-live-qualification-receipt.v1.schema.json +132 -0
  66. package/tools/agents/deploy-agents.mjs +6 -3
  67. package/tools/agents/providers/antigravity.mjs +147 -0
  68. package/tools/agents/providers/omp.d.mts +4 -0
  69. package/tools/agents/providers/omp.mjs +256 -0
  70. package/tools/agents/providers/pi.mjs +13 -1
  71. package/tools/providers/antigravity-transport.mjs +124 -0
@@ -18,6 +18,7 @@ import {
18
18
  } from './registry.mjs';
19
19
  import { McpProfileRegistry } from './profiles.mjs';
20
20
  import { getMcpInjectionDefinition } from '../providers/provider-definitions.mjs';
21
+ import { manageOmpMcp } from './omp-config.mjs';
21
22
 
22
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
24
 
@@ -38,6 +39,7 @@ Usage:
38
39
  aiwg mcp update <name> [opts] Update a server definition
39
40
  aiwg mcp list List registered MCP servers
40
41
  aiwg mcp inject [opts] Inject servers into provider configs
42
+ aiwg mcp uninject [opts] Remove unchanged AIWG-owned OMP server entries
41
43
  aiwg mcp profile <sub> Manage MCP profiles (named server subsets)
42
44
 
43
45
  Server Options (for add/update):
@@ -52,7 +54,8 @@ Server Options (for add/update):
52
54
  --description <text> Optional description
53
55
 
54
56
  Inject Options:
55
- --provider <name> Target provider (claude-code, cursor, factory, codex, opencode, windsurf, warp)
57
+ --provider <name> Target provider (including antigravity / agy and omp / oh-my-pi)
58
+ --scope <scope> project (default) or user; provider user scope must be documented
56
59
  --all Inject into all previously configured providers
57
60
  --servers <a,b,...> Only inject specific servers (comma-separated names)
58
61
  --dry-run Show what would change without writing
@@ -651,6 +654,8 @@ async function handleInject(args) {
651
654
  const serversStr = parseFlag(args, '--servers');
652
655
  const dryRun = args.includes('--dry-run');
653
656
  const projectDir = parseFlag(args, '--project') || '.';
657
+ const scope = parseFlag(args, '--scope') || 'project';
658
+ if (!['project', 'user'].includes(scope)) throw new Error('Scope must be project or user');
654
659
  const profileName = parseFlag(args, '--profile');
655
660
  const ephemeral = args.includes('--ephemeral');
656
661
  const outPath = parseFlag(args, '--out');
@@ -782,10 +787,12 @@ async function handleInject(args) {
782
787
  servers: serverFilter,
783
788
  projectDir,
784
789
  dryRun,
790
+ scope,
785
791
  });
786
792
 
787
793
  if (result.error) {
788
794
  console.error(` ${p}: ${result.error}`);
795
+ process.exitCode = 1;
789
796
  continue;
790
797
  }
791
798
 
@@ -1124,6 +1131,15 @@ export async function main(args = process.argv.slice(2)) {
1124
1131
  }
1125
1132
 
1126
1133
  case 'install': {
1134
+ if (['omp', 'oh-my-pi'].includes(args[1])) {
1135
+ const scope = parseFlag(args, '--scope') || 'project';
1136
+ if (!['project', 'user'].includes(scope)) throw new Error('Scope must be project or user');
1137
+ const projectDir = parseFlag(args, '--project') || (args[2] && !args[2].startsWith('--') ? args[2] : '.');
1138
+ const configPath = getProviderConfigPath('omp', projectDir, { scope });
1139
+ const result = await manageOmpMcp(configPath, [{ name: 'aiwg', type: 'stdio', command: 'aiwg', args: ['mcp', 'serve'] }], { dryRun: args.includes('--dry-run') });
1140
+ console.log(JSON.stringify(result, null, 2));
1141
+ break;
1142
+ }
1127
1143
  // Parse install arguments (skip flags)
1128
1144
  const installArgs = args.slice(1).filter(a => !a.startsWith('--'));
1129
1145
  const target = installArgs[0] || 'claude';
@@ -1184,6 +1200,19 @@ export async function main(args = process.argv.slice(2)) {
1184
1200
  await handleInject(subArgs);
1185
1201
  break;
1186
1202
 
1203
+ case 'uninject': {
1204
+ const provider = parseFlag(subArgs, '--provider');
1205
+ if (!['omp', 'oh-my-pi'].includes(provider)) throw new Error('uninject currently supports --provider omp');
1206
+ const scope = parseFlag(subArgs, '--scope') || 'project';
1207
+ if (!['project', 'user'].includes(scope)) throw new Error('Scope must be project or user');
1208
+ const remove = (parseFlag(subArgs, '--servers') || '').split(',').map(s => s.trim()).filter(Boolean);
1209
+ if (!remove.length) throw new Error('uninject requires --servers name[,name]');
1210
+ const configPath = getProviderConfigPath(provider, parseFlag(subArgs, '--project') || '.', { scope });
1211
+ const result = await manageOmpMcp(configPath, [], { remove, dryRun: subArgs.includes('--dry-run') });
1212
+ console.log(JSON.stringify(result, null, 2));
1213
+ break;
1214
+ }
1215
+
1187
1216
  case 'profile':
1188
1217
  await handleProfile(subArgs);
1189
1218
  break;
@@ -0,0 +1,128 @@
1
+ import { readFile, writeFile, mkdir, rename, lstat, unlink, open } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+
5
+ const hash = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');
6
+ const record = value => value !== null && typeof value === 'object' && !Array.isArray(value);
7
+ async function rejectSymlinkPath(file) {
8
+ let current = resolve(file);
9
+ for (;;) {
10
+ try {
11
+ if ((await lstat(current)).isSymbolicLink()) throw new Error('OMP MCP configuration path cannot traverse a symbolic link');
12
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
13
+ const parent = dirname(current);
14
+ if (parent === current) return;
15
+ current = parent;
16
+ }
17
+ }
18
+ async function readObject(file) {
19
+ await rejectSymlinkPath(file);
20
+ try {
21
+ if ((await lstat(file)).isSymbolicLink()) throw new Error('OMP MCP configuration cannot be a symbolic link');
22
+ const data = JSON.parse(await readFile(file, 'utf8'));
23
+ if (!record(data)) throw new Error('OMP MCP configuration must be an object');
24
+ return data;
25
+ } catch (error) {
26
+ if (error.code === 'ENOENT') return {};
27
+ if (error instanceof SyntaxError) throw new Error('OMP MCP configuration is invalid JSON; repair it before injection');
28
+ throw error;
29
+ }
30
+ }
31
+ async function atomic(file, data) {
32
+ await mkdir(dirname(file), { recursive: true });
33
+ const temporary = `${file}.${randomUUID()}.tmp`;
34
+ try {
35
+ await writeFile(temporary, JSON.stringify(data, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
36
+ await rename(temporary, file);
37
+ } finally { await unlink(temporary).catch(() => {}); }
38
+ }
39
+
40
+ export function ompServerConfig(server) {
41
+ const config = {};
42
+ for (const field of ['command', 'args', 'env', 'cwd', 'url', 'headers', 'enabled', 'timeout', 'requestIdFormat', 'auth', 'oauth', 'type']) {
43
+ if (server[field] !== undefined) config[field] = server[field];
44
+ }
45
+ if (!config.command && !config.url && config.enabled !== false) throw new Error(`OMP MCP server ${server.name} needs command or url`);
46
+ if (config.type !== undefined && !['stdio', 'http', 'sse'].includes(config.type)) throw new Error('Invalid OMP MCP transport');
47
+ if (config.command && config.url) throw new Error('OMP MCP server must choose command or url');
48
+ if (config.enabled !== false && (config.type === 'stdio' && !config.command || ['http', 'sse'].includes(config.type) && !config.url)) throw new Error('OMP MCP transport does not match endpoint');
49
+ for (const field of ['command', 'cwd', 'url']) {
50
+ if (config[field] !== undefined && typeof config[field] !== 'string') throw new Error(`Invalid OMP MCP ${field}`);
51
+ }
52
+ if (config.args !== undefined && (!Array.isArray(config.args) || config.args.some(x => typeof x !== 'string'))) throw new Error('Invalid OMP MCP args');
53
+ for (const field of ['env', 'headers']) {
54
+ if (config[field] !== undefined && (!record(config[field]) || Object.values(config[field]).some(x => typeof x !== 'string'))) throw new Error(`Invalid OMP MCP ${field}`);
55
+ }
56
+ if (config.requestIdFormat !== undefined && !['string', 'number'].includes(config.requestIdFormat)) throw new Error('Invalid OMP MCP requestIdFormat');
57
+ if (config.auth !== undefined && (!record(config.auth) || !['oauth', 'apikey'].includes(config.auth.type))) throw new Error('Invalid OMP MCP auth');
58
+ if (config.oauth !== undefined && !record(config.oauth)) throw new Error('Invalid OMP MCP oauth');
59
+ if (config.timeout !== undefined && (!Number.isFinite(config.timeout) || config.timeout < 0)) throw new Error('Invalid OMP MCP timeout');
60
+ if (config.enabled !== undefined && typeof config.enabled !== 'boolean') throw new Error('Invalid OMP MCP enabled flag');
61
+ for (const field of ['envPolicy', 'envLiteralKeys', 'headerPolicy']) {
62
+ if (server[field] !== undefined) throw new Error(`OMP native mcp.json does not enforce ${field}; this control requires the native SDK/plugin interface`);
63
+ }
64
+ for (const field of ['auth', 'oauth']) {
65
+ if (config[field]) for (const [key, value] of Object.entries(config[field])) {
66
+ if (key === 'callbackPort') { if (!Number.isInteger(value) || value < 0 || value > 65535) throw new Error('Invalid OMP MCP callbackPort'); }
67
+ else if (['type', 'credentialId', 'tokenUrl', 'clientId', 'clientSecret', 'resource', 'scope', 'redirectUri', 'callbackPath', 'prompt'].includes(key) && typeof value !== 'string') throw new Error(`Invalid OMP MCP ${field}.${key}`);
68
+ }
69
+ }
70
+ if (server.headerEnv) {
71
+ config.headers = { ...config.headers };
72
+ for (const [header, variable] of Object.entries(server.headerEnv)) {
73
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(variable)) throw new Error('Invalid MCP header environment reference');
74
+ config.headers[header] = '${' + variable + '}';
75
+ }
76
+ }
77
+ return config;
78
+ }
79
+
80
+ /** Hash-only receipt: never saves an extra copy of operator configuration. */
81
+ async function manageLockedOmpMcp(configPath, servers, { dryRun = false, remove = [] } = {}) {
82
+ const receiptPath = `${configPath}.aiwg-ownership.json`;
83
+ const existing = await readObject(configPath);
84
+ const receipt = await readObject(receiptPath);
85
+ if (receipt.servers !== undefined && (!record(receipt.servers) || receipt.schema !== 'aiwg.omp-mcp-ownership.v1')) throw new Error('Invalid OMP MCP ownership receipt');
86
+ if (existing.mcpServers !== undefined && !record(existing.mcpServers)) throw new Error('Invalid OMP mcpServers object');
87
+ const next = { ...(existing.mcpServers || {}) };
88
+ const owned = { ...(receipt.servers || {}) };
89
+ const result = { configPath, serversInjected: [], alreadyPresent: [], removed: [] };
90
+ // Validate all collisions before the first write.
91
+ for (const name of [...servers.map(s => s.name), ...remove]) {
92
+ if (typeof name !== 'string' || !name || ['__proto__', 'constructor', 'prototype'].includes(name)) throw new Error('Invalid OMP MCP server name');
93
+ if (Object.hasOwn(next, name) && owned[name] !== hash(next[name])) {
94
+ throw new Error(`OMP MCP server ${name} is operator-owned or modified; preserve it and choose a different name`);
95
+ }
96
+ }
97
+ for (const server of servers) {
98
+ if (!server.name || ['__proto__', 'constructor', 'prototype'].includes(server.name)) throw new Error('Invalid OMP MCP server name');
99
+ if (Object.hasOwn(next, server.name)) result.alreadyPresent.push(server.name);
100
+ next[server.name] = ompServerConfig(server);
101
+ owned[server.name] = hash(next[server.name]);
102
+ result.serversInjected.push(server.name);
103
+ }
104
+ for (const name of remove) {
105
+ if (owned[name]) { delete next[name]; delete owned[name]; result.removed.push(name); }
106
+ }
107
+ if (!dryRun) {
108
+ if (hash(await readObject(configPath)) !== hash(existing) || hash(await readObject(receiptPath)) !== hash(receipt)) throw new Error('OMP MCP configuration changed during injection; retry after reviewing the operator edit');
109
+ // If interrupted between writes, a retry fails closed on the hash mismatch.
110
+ await atomic(configPath, { ...existing, mcpServers: next });
111
+ await atomic(receiptPath, { schema: 'aiwg.omp-mcp-ownership.v1', servers: owned });
112
+ }
113
+ return result;
114
+ }
115
+
116
+ /** Serialize AIWG writers; operator edits detected before committing a replacement. */
117
+ export async function manageOmpMcp(configPath, servers, options = {}) {
118
+ if (options.dryRun) return manageLockedOmpMcp(configPath, servers, options);
119
+ await rejectSymlinkPath(configPath);
120
+ const lockPath = `${configPath}.aiwg-lock`;
121
+ await rejectSymlinkPath(lockPath);
122
+ await mkdir(dirname(configPath), { recursive: true });
123
+ let lock;
124
+ try { lock = await open(lockPath, 'wx', 0o600); }
125
+ catch (error) { if (error.code === 'EEXIST') throw new Error('OMP MCP injection already locked; wait for the active writer or review a stale lock before retrying'); throw error; }
126
+ try { return await manageLockedOmpMcp(configPath, servers, options); }
127
+ finally { await lock.close(); await unlink(lockPath); }
128
+ }
@@ -1,3 +1,5 @@
1
+ import { manageOmpMcp } from './omp-config.mjs';
2
+ import { resolveOmpPaths } from '../providers/omp-paths.mjs';
1
3
  /**
2
4
  * MCP Server Registry
3
5
  *
@@ -156,6 +158,12 @@ export class McpServerRegistry {
156
158
  function buildServerConfig(server, provider) {
157
159
  const adapter = getProviderDefinition(provider)?.adapters.mcpInjection;
158
160
  switch (adapter) {
161
+ case 'antigravity': {
162
+ if (server.type === 'stdio') {
163
+ return { command: server.command, args: server.args || [], ...(server.env ? { env: server.env } : {}) };
164
+ }
165
+ return { serverUrl: server.url, ...(server.headers ? { headers: server.headers } : {}) };
166
+ }
159
167
  case 'claude-code': {
160
168
  if (server.type === 'stdio') {
161
169
  return {
@@ -258,9 +266,21 @@ function buildServerToml(server) {
258
266
  /**
259
267
  * Get the config file path for a provider.
260
268
  */
261
- export function getProviderConfigPath(provider, projectDir = '.') {
269
+ export function getProviderConfigPath(provider, projectDir = '.', options = {}) {
270
+ if ((provider === 'antigravity' || provider === 'agy') && options.scope === 'user') {
271
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '';
272
+ return resolve(homeDir, '.gemini/config/mcp_config.json');
273
+ }
274
+ if ((provider === 'omp' || provider === 'oh-my-pi') && options.scope !== undefined && !['user', 'project'].includes(options.scope))
275
+ throw new Error('OMP MCP scope must be user or project');
276
+ if ((provider === 'omp' || provider === 'oh-my-pi') && options.scope === 'user')
277
+ return resolve(resolveOmpPaths().agentDir, 'mcp.json');
262
278
  const homeDir = process.env.HOME || process.env.USERPROFILE || '';
263
279
  const pathMap = {
280
+ antigravity: resolve(projectDir, '.agents/mcp_config.json'),
281
+ agy: resolve(projectDir, '.agents/mcp_config.json'),
282
+ omp: resolve(projectDir, '.omp/mcp.json'),
283
+ 'oh-my-pi': resolve(projectDir, '.omp/mcp.json'),
264
284
  'claude-code': resolve(projectDir, '.claude/settings.local.json'),
265
285
  claude: resolve(projectDir, '.claude/settings.local.json'),
266
286
  cursor: resolve(projectDir, '.cursor/mcp.json'),
@@ -281,7 +301,7 @@ export function getProviderConfigPath(provider, projectDir = '.') {
281
301
  */
282
302
  export async function injectServers(registry, provider, options = {}) {
283
303
  const { servers: serverFilter, projectDir = '.', dryRun = false } = options;
284
- const configPath = getProviderConfigPath(provider, projectDir);
304
+ const configPath = getProviderConfigPath(provider, projectDir, options);
285
305
  const result = {
286
306
  provider,
287
307
  configPath,
@@ -297,6 +317,18 @@ export async function injectServers(registry, provider, options = {}) {
297
317
  result.error = 'No servers to inject. Use "aiwg mcp add" first.';
298
318
  return result;
299
319
  }
320
+ if (provider === 'omp' || provider === 'oh-my-pi') {
321
+ try {
322
+ const managed = await manageOmpMcp(configPath, allServers, { dryRun });
323
+ if (!dryRun)
324
+ for (const server of allServers)
325
+ await registry.recordInjection(server.name, 'omp');
326
+ return { ...result, ...managed };
327
+ }
328
+ catch (error) {
329
+ return { ...result, error: error instanceof Error ? error.message : String(error) };
330
+ }
331
+ }
300
332
  // Handle TOML-based providers (Codex/OpenAI) separately
301
333
  if (provider === 'codex' || provider === 'openai') {
302
334
  return injectToml(registry, allServers, configPath, provider, dryRun, result);
@@ -311,8 +343,10 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
311
343
  const content = await readFile(configPath, 'utf-8');
312
344
  existing = JSON.parse(content);
313
345
  }
314
- catch {
315
- // File doesn't exist, start fresh
346
+ catch (error) {
347
+ if ((provider === 'antigravity' || provider === 'agy') && error?.code !== 'ENOENT') {
348
+ throw new Error(`Refusing to overwrite malformed MCP config ${configPath}: ${error.message}`);
349
+ }
316
350
  }
317
351
  // Determine the MCP servers key for this provider
318
352
  const mcpKey = provider === 'opencode' ? 'mcp' : 'mcpServers';
@@ -323,6 +357,8 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
323
357
  if (existingServers[server.name]) {
324
358
  // Update existing entry in place
325
359
  result.alreadyPresent.push(server.name);
360
+ if (provider === 'antigravity' || provider === 'agy')
361
+ continue;
326
362
  }
327
363
  newServers[server.name] = buildServerConfig(server, provider);
328
364
  result.serversInjected.push(server.name);
@@ -334,7 +370,9 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
334
370
  await writeFile(configPath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
335
371
  // Record injection in registry
336
372
  for (const server of servers) {
337
- await registry.recordInjection(server.name, provider);
373
+ if ((provider === 'antigravity' || provider === 'agy') && !result.serversInjected.includes(server.name))
374
+ continue;
375
+ await registry.recordInjection(server.name, provider === 'agy' ? 'antigravity' : provider);
338
376
  }
339
377
  }
340
378
  return result;
@@ -378,6 +416,8 @@ function escapeRegex(str) {
378
416
  }
379
417
  /** All supported provider names for injection */
380
418
  export const SUPPORTED_PROVIDERS = [
419
+ 'antigravity',
420
+ 'omp',
381
421
  'claude-code',
382
422
  'cursor',
383
423
  'factory',
@@ -1,3 +1,5 @@
1
+ import { manageOmpMcp } from './omp-config.mjs';
2
+ import { resolveOmpPaths } from '../providers/omp-paths.mjs';
1
3
  /**
2
4
  * MCP Server Registry (Runtime ESM)
3
5
  *
@@ -206,6 +208,12 @@ function buildServerConfig(server, provider) {
206
208
  const mcpDefinition = getMcpInjectionDefinition(provider);
207
209
 
208
210
  switch (mcpDefinition?.serverConfigFormat) {
211
+ case 'antigravity': {
212
+ if (server.type === 'stdio') {
213
+ return { command: server.command, args: server.args || [], ...(server.env ? { env: server.env } : {}) };
214
+ }
215
+ return { serverUrl: server.url, ...(server.headers ? { headers: server.headers } : {}) };
216
+ }
209
217
  case 'standard': {
210
218
  if (server.type === 'stdio') {
211
219
  return {
@@ -281,15 +289,16 @@ function buildServerToml(server) {
281
289
  return lines.join('\n');
282
290
  }
283
291
 
284
- export function getProviderConfigPath(provider, projectDir = '.') {
285
- return resolveMcpConfigPath(provider, projectDir);
292
+ export function getProviderConfigPath(provider, projectDir = '.', options = {}) {
293
+ if (normalizeRuntimeProviderId(provider) === 'omp' && options.scope !== undefined && !['user', 'project'].includes(options.scope)) throw new Error('OMP MCP scope must be user or project');
294
+ return resolveMcpConfigPath(provider, projectDir, options);
286
295
  }
287
296
 
288
297
  export async function injectServers(registry, provider, options = {}) {
289
298
  const { servers: serverFilter, projectDir = '.', dryRun = false } = options;
290
299
  const normalizedProvider = normalizeRuntimeProviderId(provider);
291
300
  const mcpDefinition = getMcpInjectionDefinition(provider);
292
- const configPath = getProviderConfigPath(provider, projectDir);
301
+ const configPath = getProviderConfigPath(provider, projectDir, options);
293
302
  const result = {
294
303
  provider,
295
304
  configPath,
@@ -312,6 +321,16 @@ export async function injectServers(registry, provider, options = {}) {
312
321
  return result;
313
322
  }
314
323
 
324
+ if (normalizedProvider === 'omp') {
325
+ try {
326
+ const managed = await manageOmpMcp(configPath, allServers, { dryRun });
327
+ if (!dryRun) for (const server of allServers) await registry.recordInjection(server.name, 'omp');
328
+ return { ...result, ...managed };
329
+ } catch (error) {
330
+ return { ...result, error: error instanceof Error ? error.message : String(error) };
331
+ }
332
+ }
333
+
315
334
  if (mcpDefinition?.configFormat === 'toml') {
316
335
  return injectToml(registry, allServers, configPath, provider, dryRun, result);
317
336
  }
@@ -324,8 +343,10 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
324
343
  try {
325
344
  const content = await readFile(configPath, 'utf-8');
326
345
  existing = JSON.parse(content);
327
- } catch {
328
- // File doesn't exist
346
+ } catch (error) {
347
+ if (normalizeRuntimeProviderId(provider) === 'antigravity' && error?.code !== 'ENOENT') {
348
+ throw new Error(`Refusing to overwrite malformed MCP config ${configPath}: ${error.message}`);
349
+ }
329
350
  }
330
351
 
331
352
  const mcpKey = getMcpInjectionDefinition(provider)?.serversKey || 'mcpServers';
@@ -335,6 +356,7 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
335
356
  for (const server of servers) {
336
357
  if (existingServers[server.name]) {
337
358
  result.alreadyPresent.push(server.name);
359
+ if (normalizeRuntimeProviderId(provider) === 'antigravity') continue;
338
360
  }
339
361
  newServers[server.name] = buildServerConfig(server, provider);
340
362
  result.serversInjected.push(server.name);
@@ -347,7 +369,8 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
347
369
  await writeFile(configPath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
348
370
 
349
371
  for (const server of servers) {
350
- await registry.recordInjection(server.name, provider);
372
+ if (normalizeRuntimeProviderId(provider) === 'antigravity' && !result.serversInjected.includes(server.name)) continue;
373
+ await registry.recordInjection(server.name, normalizeRuntimeProviderId(provider) || provider);
351
374
  }
352
375
  }
353
376
 
@@ -3,6 +3,17 @@
3
3
  "version": "1.0.0",
4
4
  "verifiedAt": "2026-07-20",
5
5
  "providers": {
6
+ "antigravity": {
7
+ "agent": "native", "skill": "native", "globalChild": "inherited",
8
+ "identifierSyntax": "model identifier accepted by Antigravity CLI 1.1.26 --model",
9
+ "effortValues": ["low", "medium", "high"],
10
+ "inheritance": "Omitted model and effort inherit the invoking Antigravity session",
11
+ "invalidPinFallback": "Provider validation/error; never assume fallback",
12
+ "configTarget": "agy launch arguments; custom-agent model tiers are not projected from generic AIWG model ids",
13
+ "artifactFormat": "YAML frontmatter plus Markdown; runtime --model and --effort flags",
14
+ "verification": "Pinned offline contract; authenticated model selection was not executed",
15
+ "sourceUrl": "https://antigravity.google/docs/cli/subagents/", "verifiedAt": "2026-09-04"
16
+ },
6
17
  "claude": {
7
18
  "agent": "native", "skill": "native", "globalChild": "inherited",
8
19
  "identifierSyntax": "Claude alias or accepted Anthropic model ID",
@@ -76,6 +87,37 @@
76
87
  "verification": "Inspect active profile and run metadata",
77
88
  "sourceUrl": "https://docs.warp.dev/agent-platform/capabilities/agent-profiles-permissions", "verifiedAt": "2026-07-20"
78
89
  },
90
+ "omp": {
91
+ "agent": "native",
92
+ "skill": "inherited",
93
+ "globalChild": "native",
94
+ "identifierSyntax": "provider/model identifier accepted by OMP --model",
95
+ "effortValues": [
96
+ "minimal",
97
+ "low",
98
+ "medium",
99
+ "high",
100
+ "xhigh"
101
+ ],
102
+ "inheritance": "Omitted model and thinking level inherit the invoking Pi session",
103
+ "invalidPinFallback": "Pi resolves against its configured catalog and exits on invalid explicit selection",
104
+ "configTarget": "OMP native agent frontmatter and runtime launch arguments",
105
+ "artifactFormat": "model and thinkingLevel frontmatter; --model and --thinking runtime flags",
106
+ "verification": "Pinned OMP 18.1.10 source and live OpenRouter smoke; individual role defaults remain operator configured",
107
+ "sourceUrl": "https://github.com/can1357/oh-my-pi/tree/5964a0f7649275bcde818f20073193fd032451f2/packages/coding-agent",
108
+ "verifiedAt": "2026-09-04"
109
+ },
110
+ "pi": {
111
+ "agent": "native", "skill": "inherited", "globalChild": "native",
112
+ "identifierSyntax": "provider/model identifier accepted by Pi --model",
113
+ "effortValues": ["minimal", "low", "medium", "high", "xhigh"],
114
+ "inheritance": "Omitted model and thinking level inherit the invoking Pi session",
115
+ "invalidPinFallback": "Pi resolves against its configured catalog and exits on invalid explicit selection",
116
+ "configTarget": "Pi headless launch arguments",
117
+ "artifactFormat": "Runtime --model and --thinking flags",
118
+ "verification": "Inspect strict JSONL state and the selected provider/model",
119
+ "sourceUrl": "https://github.com/earendil-works/pi/tree/main/packages/coding-agent#cli-reference", "verifiedAt": "2026-09-04"
120
+ },
79
121
  "windsurf": {
80
122
  "agent": "unsupported", "skill": "unsupported", "globalChild": "inherited",
81
123
  "identifierSyntax": "UI-selected provider model",
@@ -4,6 +4,14 @@
4
4
  "refreshedAt": "2026-07-20",
5
5
  "staleAfterDays": 90,
6
6
  "providers": {
7
+ "antigravity": {
8
+ "roles": {
9
+ "reasoning": { "id": "configured/reasoning", "status": "unverified", "observed": false },
10
+ "coding": { "id": "configured/coding", "status": "unverified", "observed": false },
11
+ "efficiency": { "id": "configured/efficiency", "status": "unverified", "observed": false }
12
+ },
13
+ "sourceUrl": "https://antigravity.google/docs/cli/overview/", "verifiedAt": "2026-09-04"
14
+ },
7
15
  "claude": {
8
16
  "roles": {
9
17
  "reasoning": { "id": "claude-opus-4-7", "status": "active", "observed": false },
@@ -52,6 +60,35 @@
52
60
  },
53
61
  "sourceUrl": "https://opencode.ai/docs/models", "verifiedAt": "2026-07-20"
54
62
  },
63
+ "omp": {
64
+ "roles": {
65
+ "reasoning": {
66
+ "id": "configured/reasoning",
67
+ "status": "unverified",
68
+ "observed": false
69
+ },
70
+ "coding": {
71
+ "id": "configured/coding",
72
+ "status": "unverified",
73
+ "observed": false
74
+ },
75
+ "efficiency": {
76
+ "id": "configured/efficiency",
77
+ "status": "unverified",
78
+ "observed": false
79
+ }
80
+ },
81
+ "sourceUrl": "https://github.com/can1357/oh-my-pi/tree/5964a0f7649275bcde818f20073193fd032451f2/packages/coding-agent",
82
+ "verifiedAt": "2026-09-04"
83
+ },
84
+ "pi": {
85
+ "roles": {
86
+ "reasoning": { "id": "configured/reasoning", "status": "unverified", "observed": false },
87
+ "coding": { "id": "configured/coding", "status": "unverified", "observed": false },
88
+ "efficiency": { "id": "configured/efficiency", "status": "unverified", "observed": false }
89
+ },
90
+ "sourceUrl": "https://github.com/earendil-works/pi/tree/main/packages/coding-agent#providers--models", "verifiedAt": "2026-09-04"
91
+ },
55
92
  "warp": {
56
93
  "roles": {
57
94
  "reasoning": { "id": "profile-selected", "status": "unverified", "observed": false },
@@ -67,6 +67,16 @@ export const PROVIDER_DISCOVERY_DECISIONS = {
67
67
  reason: 'OpenHuman profiles accept semantic model hints but expose no standardized local model-list command.',
68
68
  documentation: 'https://github.com/roctinam/openhuman',
69
69
  },
70
+ omp: { provider: 'omp', status: 'native', interface: 'omp models --json --no-extensions',
71
+ reason: 'OMP JSON catalog reports credential-available LLM models; ambient extensions are disabled by default.',
72
+ documentation: 'https://github.com/can1357/oh-my-pi/blob/5964a0f7649275bcde818f20073193fd032451f2/packages/coding-agent/src/commands/models.ts' },
73
+ pi: {
74
+ provider: 'pi',
75
+ status: 'native',
76
+ interface: 'pi --list-models',
77
+ reason: 'Pi exposes the configured provider/model catalog through a read-only non-interactive table.',
78
+ documentation: 'https://github.com/earendil-works/pi/tree/main/packages/coding-agent#cli-reference',
79
+ },
70
80
  warp: {
71
81
  provider: 'warp',
72
82
  status: 'unsupported',
@@ -96,7 +106,7 @@ export function classifyDiscoveryError(message) {
96
106
  export const runModelDiscoveryCommand = (command, args, options = {}) => new Promise(resolveCommand => {
97
107
  const child = spawn(command, args, {
98
108
  cwd: options.cwd,
99
- env: process.env,
109
+ env: options.env ?? process.env,
100
110
  stdio: ['ignore', 'pipe', 'pipe'],
101
111
  });
102
112
  let stdout = '';
@@ -109,8 +119,13 @@ export const runModelDiscoveryCommand = (command, args, options = {}) => new Pro
109
119
  clearTimeout(timer);
110
120
  resolveCommand({ stdout, stderr, exitCode });
111
121
  };
112
- child.stdout.on('data', chunk => { stdout += String(chunk); });
113
- child.stderr.on('data', chunk => { stderr += String(chunk); });
122
+ child.stdout.on('data', chunk => { stdout += String(chunk); if (Buffer.byteLength(stdout) > 8 * 1024 * 1024) {
123
+ stdout = '';
124
+ stderr = 'Model catalog exceeded 8 MiB limit';
125
+ child.kill('SIGKILL');
126
+ finish(1);
127
+ } });
128
+ child.stderr.on('data', chunk => { stderr = (stderr + String(chunk)).slice(-4096); });
114
129
  child.on('error', error => {
115
130
  stderr = `${stderr}${stderr ? '\n' : ''}${error.message}`;
116
131
  finish(127);
@@ -161,6 +176,30 @@ export async function discoverOpenCodeModels(command = 'opencode', runner = runM
161
176
  : {}),
162
177
  };
163
178
  }
179
+ export async function discoverPiModels(command = 'pi', runner = runModelDiscoveryCommand) {
180
+ const observedAt = new Date().toISOString();
181
+ const [version, result] = await Promise.all([
182
+ runtimeVersion(command, runner),
183
+ runner(command, ['--list-models'], { cwd: tmpdir(), timeoutMs: 15_000 }),
184
+ ]);
185
+ if (result.exitCode !== 0) {
186
+ const error = result.stderr.trim() || `Pi --list-models exited ${result.exitCode}`;
187
+ return { provider: 'pi', source: 'native', observedAt,
188
+ ...(version ? { runtimeVersion: version } : {}), accountScope: 'local-runtime',
189
+ models: [], errorKind: classifyDiscoveryError(error), error };
190
+ }
191
+ const lines = result.stdout.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
192
+ const models = lines.slice(1).flatMap(line => {
193
+ const columns = line.split(/\s{2,}/);
194
+ if (columns.length < 2 || !/^[a-z0-9][a-z0-9._-]*$/i.test(columns[0]))
195
+ return [];
196
+ return [{ id: `${columns[0]}/${columns[1]}` }];
197
+ });
198
+ return { provider: 'pi', source: 'native', observedAt,
199
+ ...(version ? { runtimeVersion: version } : {}), accountScope: 'local-runtime', models,
200
+ ...(models.length === 0 ? { errorKind: 'invalid-output',
201
+ error: 'Pi returned no parseable provider/model rows.' } : {}) };
202
+ }
164
203
  export async function discoverOpenClawModels(command = 'openclaw', runner = runModelDiscoveryCommand) {
165
204
  const observedAt = new Date().toISOString();
166
205
  const [version, result] = await Promise.all([
@@ -394,7 +433,7 @@ export async function resolveDynamicModelCatalog(options) {
394
433
  const homeDir = options.homeDir ?? homedir();
395
434
  const cacheFile = options.cacheFile ?? join(homeDir, '.cache/aiwg/model-catalog.v1.json');
396
435
  const staticFile = join(options.aiwgRoot, 'agentic/code/providers/model-catalog.v1.json');
397
- const signature = inventorySignature(options.inventory);
436
+ const signature = inventorySignature(options.inventory) + JSON.stringify({ omp: options.omp, profile: process.env.OMP_PROFILE ?? process.env.PI_PROFILE, config: process.env.PI_CONFIG_DIR, data: process.env.XDG_DATA_HOME, cache: process.env.XDG_CACHE_HOME, agent: process.env.PI_CODING_AGENT_DIR });
398
437
  const staticCatalog = await readCatalog(staticFile);
399
438
  if (!staticCatalog)
400
439
  throw new Error(`Invalid or missing static model catalog: ${staticFile}`);
@@ -455,6 +494,8 @@ export async function resolveDynamicModelCatalog(options) {
455
494
  codex: () => discoverCodexModels(),
456
495
  opencode: () => discoverOpenCodeModels(),
457
496
  openclaw: () => discoverOpenClawModels(),
497
+ pi: () => discoverPiModels(),
498
+ omp: () => discoverOmpModels(process.env.AIWG_OMP_BIN || 'omp', runModelDiscoveryCommand, options.omp),
458
499
  };
459
500
  const providerDiscovery = {};
460
501
  for (const provider of available) {
@@ -462,7 +503,7 @@ export async function resolveDynamicModelCatalog(options) {
462
503
  if (discoverer) {
463
504
  try {
464
505
  providerDiscovery[provider] = await discoverer();
465
- const selected = selectRoleModels(providerDiscovery[provider].models);
506
+ const selected = provider === 'omp' ? {} : selectRoleModels(providerDiscovery[provider].models);
466
507
  const providerCatalog = catalog.providers[provider];
467
508
  if (providerCatalog) {
468
509
  for (const role of ['reasoning', 'coding', 'efficiency']) {
@@ -518,4 +559,36 @@ export async function resolveDynamicModelCatalog(options) {
518
559
  await atomicWrite(cacheFile, `${JSON.stringify(resolved, null, 2)}\n`);
519
560
  return resolved;
520
561
  }
562
+ /** Explicit OMP discovery; no ambient extension execution unless requested. */
563
+ export async function discoverOmpModels(command = 'omp', runner = runModelDiscoveryCommand, options = {}) {
564
+ const base = { provider: 'omp', source: 'native', observedAt: new Date().toISOString(), accountScope: 'local-runtime', models: [] };
565
+ const args = [...(options.profile ? ['--profile', options.profile] : []), 'models', '--json'];
566
+ if (!options.extensions)
567
+ args.push('--no-extensions');
568
+ for (const config of options.config ?? [])
569
+ args.push('--config', config);
570
+ try {
571
+ const result = await runner(command, args, { cwd: options.cwd ?? tmpdir(), timeoutMs: 15000 });
572
+ base.runtimeVersion = await runtimeVersion(command, runner);
573
+ if (result.exitCode !== 0)
574
+ return { ...base, errorKind: result.exitCode === 124 ? 'timeout' : /unknown|unsupported|not found/i.test(result.stderr) ? 'unsupported' : classifyDiscoveryError(result.stderr), error: 'OMP model discovery failed; check CLI version, selected profile/config, and credential availability.' };
575
+ const payload = JSON.parse(result.stdout);
576
+ if (!Array.isArray(payload.models) || payload.models.some((m) => !m || typeof m.id !== 'string' || !m.id || typeof m.provider !== 'string' || !m.provider))
577
+ throw new Error('shape');
578
+ base.models = payload.models.map((m) => ({ id: `${m.provider}/${m.id}`, llmProvider: m.provider, displayName: m.name, reasoningEfforts: Array.isArray(m.thinking) ? m.thinking : undefined }));
579
+ return base;
580
+ }
581
+ catch {
582
+ return { ...base, errorKind: 'invalid-output', error: 'OMP returned invalid model catalog JSON or discovery could not run.' };
583
+ }
584
+ }
585
+ /** Never guess an unrelated model when a requested mapping is unavailable. */
586
+ export function resolveOmpRoleModel(catalog, role, mappings, explicit) {
587
+ if (explicit)
588
+ return explicit;
589
+ const id = mappings[role];
590
+ if (!id || !catalog.models.some(model => model.id === id))
591
+ throw new Error(`OMP ${role} model unavailable; configure a model from omp models --json or supply an explicit model.`);
592
+ return id;
593
+ }
521
594
  //# sourceMappingURL=model-discovery.js.map