@aiwg/cli 2026.9.2 → 2026.9.4

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 (68) hide show
  1. package/README.md +21 -0
  2. package/agentic/code/providers/antigravity/provider-contract.v1.json +121 -0
  3. package/agentic/code/providers/capability-matrix.yaml +87 -1
  4. package/agentic/code/providers/model-capabilities.v1.json +31 -0
  5. package/agentic/code/providers/model-catalog.v1.json +29 -0
  6. package/agentic/code/providers/omp/README.md +58 -0
  7. package/agentic/code/providers/omp/aiwg-bridge.ts +52 -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/cli/agent-spawn.js +13 -2
  13. package/dist/src/cli/handlers/help.js +3 -1
  14. package/dist/src/cli/handlers/init.js +2 -0
  15. package/dist/src/cli/handlers/models.js +1 -1
  16. package/dist/src/cli/handlers/runtime-info.js +8 -1
  17. package/dist/src/cli/handlers/session.js +5 -4
  18. package/dist/src/cli/handlers/sessions.js +26 -9
  19. package/dist/src/cli/handlers/setup.js +9 -2
  20. package/dist/src/cli/handlers/steward.js +13 -2
  21. package/dist/src/cli/handlers/subcommands.js +11 -0
  22. package/dist/src/cli/handlers/team.js +68 -7
  23. package/dist/src/cli/handlers/use.js +158 -9
  24. package/dist/src/cli/scope-resolver.js +7 -0
  25. package/dist/src/config/aiwg-config.js +1 -0
  26. package/dist/src/dataset/fortemi-live-qualification.d.ts +53 -0
  27. package/dist/src/dataset/fortemi-live-qualification.js +297 -0
  28. package/dist/src/dataset/index.d.ts +1 -0
  29. package/dist/src/dataset/index.js +1 -0
  30. package/dist/src/mcp/cli.mjs +30 -1
  31. package/dist/src/mcp/omp-config.mjs +128 -0
  32. package/dist/src/mcp/registry.js +45 -5
  33. package/dist/src/mcp/registry.mjs +29 -6
  34. package/dist/src/models/model-capabilities.v1.json +31 -0
  35. package/dist/src/models/model-catalog.v1.json +29 -0
  36. package/dist/src/models/model-discovery.js +46 -5
  37. package/dist/src/models/provider-policy.js +5 -3
  38. package/dist/src/plugin/skill-command-translator.js +2 -0
  39. package/dist/src/providers/capability-matrix.yaml +87 -1
  40. package/dist/src/providers/omp-agent.mjs +40 -0
  41. package/dist/src/providers/omp-diagnostics.mjs +15 -0
  42. package/dist/src/providers/omp-paths.mjs +38 -0
  43. package/dist/src/providers/provider-definitions.js +83 -0
  44. package/dist/src/providers/provider-definitions.mjs +25 -1
  45. package/dist/src/providers/provider-inventory.js +2 -0
  46. package/dist/src/sessions/adapters/omp.js +203 -0
  47. package/dist/src/sessions/batch-import.js +7 -0
  48. package/dist/src/sessions/contracts.js +1 -1
  49. package/dist/src/sessions/importer.js +4 -3
  50. package/dist/src/sessions/index.js +1 -0
  51. package/dist/src/sessions/readers.js +4 -3
  52. package/dist/src/sessions/workspace-discovery.js +12 -2
  53. package/dist/src/skills/deployer.js +21 -1
  54. package/dist/src/smiths/agentsmith/generator.js +1 -0
  55. package/dist/src/smiths/context-pipeline/parallelism-section.js +2 -0
  56. package/dist/src/smiths/context-pipeline/provider-policy.js +2 -2
  57. package/dist/src/smiths/context-pipeline/workspace-context.js +2 -2
  58. package/dist/src/storage/backends/fortemi.js +142 -17
  59. package/dist/src/storage/fortemi-qualification-receipt.js +206 -0
  60. package/dist/src/storage/fortemi-qualification.js +67 -6
  61. package/dist/src/storage/index.js +1 -0
  62. package/package.json +2 -1
  63. package/schemas/dataset/fortemi-live-qualification-receipt.v1.schema.json +132 -0
  64. package/tools/agents/deploy-agents.mjs +6 -3
  65. package/tools/agents/providers/antigravity.mjs +147 -0
  66. package/tools/agents/providers/omp.d.mts +4 -0
  67. package/tools/agents/providers/omp.mjs +256 -0
  68. 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,26 @@
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
+ },
79
110
  "pi": {
80
111
  "agent": "native", "skill": "inherited", "globalChild": "native",
81
112
  "identifierSyntax": "provider/model identifier accepted by Pi --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,27 @@
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
+ },
55
84
  "pi": {
56
85
  "roles": {
57
86
  "reasoning": { "id": "configured/reasoning", "status": "unverified", "observed": false },
@@ -67,6 +67,9 @@ 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' },
70
73
  pi: {
71
74
  provider: 'pi',
72
75
  status: 'native',
@@ -103,7 +106,7 @@ export function classifyDiscoveryError(message) {
103
106
  export const runModelDiscoveryCommand = (command, args, options = {}) => new Promise(resolveCommand => {
104
107
  const child = spawn(command, args, {
105
108
  cwd: options.cwd,
106
- env: process.env,
109
+ env: options.env ?? process.env,
107
110
  stdio: ['ignore', 'pipe', 'pipe'],
108
111
  });
109
112
  let stdout = '';
@@ -116,8 +119,13 @@ export const runModelDiscoveryCommand = (command, args, options = {}) => new Pro
116
119
  clearTimeout(timer);
117
120
  resolveCommand({ stdout, stderr, exitCode });
118
121
  };
119
- child.stdout.on('data', chunk => { stdout += String(chunk); });
120
- 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); });
121
129
  child.on('error', error => {
122
130
  stderr = `${stderr}${stderr ? '\n' : ''}${error.message}`;
123
131
  finish(127);
@@ -425,7 +433,7 @@ export async function resolveDynamicModelCatalog(options) {
425
433
  const homeDir = options.homeDir ?? homedir();
426
434
  const cacheFile = options.cacheFile ?? join(homeDir, '.cache/aiwg/model-catalog.v1.json');
427
435
  const staticFile = join(options.aiwgRoot, 'agentic/code/providers/model-catalog.v1.json');
428
- 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 });
429
437
  const staticCatalog = await readCatalog(staticFile);
430
438
  if (!staticCatalog)
431
439
  throw new Error(`Invalid or missing static model catalog: ${staticFile}`);
@@ -487,6 +495,7 @@ export async function resolveDynamicModelCatalog(options) {
487
495
  opencode: () => discoverOpenCodeModels(),
488
496
  openclaw: () => discoverOpenClawModels(),
489
497
  pi: () => discoverPiModels(),
498
+ omp: () => discoverOmpModels(process.env.AIWG_OMP_BIN || 'omp', runModelDiscoveryCommand, options.omp),
490
499
  };
491
500
  const providerDiscovery = {};
492
501
  for (const provider of available) {
@@ -494,7 +503,7 @@ export async function resolveDynamicModelCatalog(options) {
494
503
  if (discoverer) {
495
504
  try {
496
505
  providerDiscovery[provider] = await discoverer();
497
- const selected = selectRoleModels(providerDiscovery[provider].models);
506
+ const selected = provider === 'omp' ? {} : selectRoleModels(providerDiscovery[provider].models);
498
507
  const providerCatalog = catalog.providers[provider];
499
508
  if (providerCatalog) {
500
509
  for (const role of ['reasoning', 'coding', 'efficiency']) {
@@ -550,4 +559,36 @@ export async function resolveDynamicModelCatalog(options) {
550
559
  await atomicWrite(cacheFile, `${JSON.stringify(resolved, null, 2)}\n`);
551
560
  return resolved;
552
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
+ }
553
594
  //# sourceMappingURL=model-discovery.js.map
@@ -26,8 +26,8 @@ function requireModelResource(filename) {
26
26
  const capabilityData = requireModelResource('model-capabilities.v1.json');
27
27
  const catalogData = requireModelResource('model-catalog.v1.json');
28
28
  const ProviderSchema = z.enum([
29
- 'claude', 'codex', 'copilot', 'cursor', 'factory', 'hermes',
30
- 'opencode', 'openclaw', 'openhuman', 'pi', 'warp', 'windsurf',
29
+ 'antigravity', 'claude', 'codex', 'copilot', 'cursor', 'factory', 'hermes',
30
+ 'opencode', 'openclaw', 'openhuman', 'omp', 'pi', 'warp', 'windsurf',
31
31
  ]);
32
32
  const OutcomeSchema = z.enum([
33
33
  'native', 'compiled', 'inherited', 'global-only', 'informational', 'unsupported',
@@ -107,7 +107,7 @@ export function loadProviderModelCapabilities() {
107
107
  const expected = new Set(ProviderSchema.options);
108
108
  const actual = new Set(Object.keys(registryCache.providers));
109
109
  if (actual.size !== expected.size || [...expected].some(id => !actual.has(id))) {
110
- throw new Error('Provider model capability registry must cover all 12 providers');
110
+ throw new Error('Provider model capability registry must cover all registered providers');
111
111
  }
112
112
  }
113
113
  return registryCache;
@@ -153,12 +153,14 @@ function catalogRole(policy) {
153
153
  }
154
154
  function fieldNames(provider) {
155
155
  switch (provider) {
156
+ case 'antigravity': return { model: 'model', effort: 'effort' };
156
157
  case 'codex': return { model: 'model', effort: 'model_reasoning_effort' };
157
158
  case 'factory': return { model: 'model', effort: 'reasoningEffort' };
158
159
  case 'claude': return { model: 'model', effort: 'effort' };
159
160
  case 'copilot':
160
161
  case 'cursor':
161
162
  case 'opencode': return { model: 'model' };
163
+ case 'omp': return { model: 'model', effort: 'thinkingLevel' };
162
164
  case 'pi': return { model: 'model', effort: 'thinking' };
163
165
  case 'openhuman': return { model: 'model_hint' };
164
166
  case 'openclaw': return { model: 'subagents.model' };
@@ -34,11 +34,13 @@ const PROVIDERS_NEEDING_COMMANDS = new Set([
34
34
  * Providers where skills are deployed natively (no command generation needed).
35
35
  */
36
36
  const SKILLS_ONLY_PROVIDERS = new Set([
37
+ 'antigravity',
37
38
  'claude',
38
39
  'cursor',
39
40
  'hermes',
40
41
  'openhuman',
41
42
  'pi',
43
+ 'omp',
42
44
  ]);
43
45
  /**
44
46
  * Check if a provider needs command files generated from skills.