@harness-mix/cli 0.2.3 → 0.3.0

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 (72) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +469 -467
  3. package/docs/harness-management.md +1 -1
  4. package/docs/multi-agent-collaboration.md +3 -1
  5. package/docs/native-acp.md +138 -127
  6. package/output/native-build/desktop-controller.mjs +1 -1
  7. package/output/native-build/renderer-extension.js +172 -23
  8. package/package.json +13 -9
  9. package/scripts/acp-image-test.cjs +69 -0
  10. package/scripts/adapters-test.cjs +25 -0
  11. package/scripts/antigravity-adapter-test.cjs +647 -626
  12. package/scripts/codex-accounts-test.cjs +34 -0
  13. package/scripts/codex-adapter-test.cjs +238 -127
  14. package/scripts/collaboration-test.cjs +577 -262
  15. package/scripts/collaboration-ui-smoke.cjs +25 -3
  16. package/scripts/e2e-delegate.cjs +1 -1
  17. package/scripts/e2e-hermes-image.cjs +60 -0
  18. package/scripts/jsonl-stdin-test.cjs +40 -31
  19. package/scripts/kiro-cursor-adapters-test.cjs +124 -100
  20. package/scripts/native-acp-depth-test.cjs +30 -5
  21. package/scripts/native-update-apply-test.cjs +269 -215
  22. package/scripts/native-update.cjs +78 -0
  23. package/scripts/native-vendor-adapters-test.cjs +196 -154
  24. package/scripts/openclaw-adapter-test.cjs +121 -3
  25. package/scripts/openclaw-mcp-probe.cjs +100 -0
  26. package/scripts/openclaw-mcp-tool-probe.cjs +55 -0
  27. package/scripts/openclaw-thinking-probe.cjs +73 -0
  28. package/scripts/salvage-rollout-writes.cjs +72 -0
  29. package/scripts/storage-verification-test.cjs +11 -0
  30. package/scripts/zcode-adapter-test.cjs +329 -0
  31. package/scripts/zcode-live-probe.cjs +66 -0
  32. package/src/main/adapters/antigravity.js +1428 -1418
  33. package/src/main/adapters/claude.js +15 -7
  34. package/src/main/adapters/codex-app-server.js +29 -11
  35. package/src/main/adapters/codex.js +690 -649
  36. package/src/main/adapters/native-acp-command.js +51 -48
  37. package/src/main/adapters/native-acp.js +47 -12
  38. package/src/main/adapters/omp.js +7 -2
  39. package/src/main/adapters/openclaw.js +534 -343
  40. package/src/main/adapters/pi-family.js +35 -8
  41. package/src/main/adapters/qoder.js +12 -8
  42. package/src/main/adapters/zcode.js +925 -10
  43. package/src/main/host/collaboration-tools.js +1 -1
  44. package/src/main/host/collaboration.js +971 -714
  45. package/src/main/host/jsonl.js +130 -120
  46. package/src/main/host/runtime.js +9 -3
  47. package/src/main/host/verification-gates.js +14 -2
  48. package/src/main/native/codex-accounts.js +15 -4
  49. package/src/main/native/config.js +9 -9
  50. package/src/main/native/launcher.js +252 -237
  51. package/src/main/native/process-utils.js +157 -57
  52. package/src/main/native/protocol.js +1227 -1187
  53. package/src/main/native/update-state.js +123 -110
  54. package/src/main/native/updater.js +460 -394
  55. package/src/native-ui/desktop-control/dist/renderer-cdp-control-session.js +3 -1
  56. package/src/native-ui/desktop-control/dist/renderer-cdp-control-session.js.map +1 -1
  57. package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -1
  58. package/src/native-ui/desktop-control/src/renderer-cdp-control-session.ts +358 -358
  59. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
  60. package/src/native-ui/renderer-extension/dist/types/renderer-collab-cards.d.ts +1 -0
  61. package/src/native-ui/renderer-extension/dist/types/renderer-collab-cards.d.ts.map +1 -1
  62. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +29 -0
  63. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
  64. package/src/native-ui/renderer-extension/dist/types/renderer-team-cards.d.ts +4 -0
  65. package/src/native-ui/renderer-extension/dist/types/renderer-team-cards.d.ts.map +1 -1
  66. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
  67. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +3224 -3181
  68. package/src/native-ui/renderer-extension/src/renderer-collab-cards.ts +25 -0
  69. package/src/native-ui/renderer-extension/src/renderer-model-client.ts +27 -0
  70. package/src/native-ui/renderer-extension/src/renderer-team-cards.ts +93 -13
  71. package/src/native-ui/renderer-extension/src/settings/connections-page.ts +2 -2
  72. package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +47 -1
@@ -1,48 +1,51 @@
1
- const fs = require('node:fs');
2
- const path = require('node:path');
3
- const os = require('node:os');
4
- const { cliSpawn } = require('../host/jsonl');
5
-
6
- // Resolve only the named native CLI. In particular, `agent` can belong to Grok.
7
- function nativeCommand(id, argv) {
8
- const cursor = id === 'cursor-cli';
9
- const buddy = id === 'codebuddy';
10
- const specs = {
11
- codebuddy: ['codebuddy', 'HARNESS_MIX_CODEBUDDY_EXECUTABLE', 'https://www.codebuddy.ai/docs/cli/overview'],
12
- 'cursor-cli': ['cursor-agent', 'HARNESS_MIX_CURSOR_EXECUTABLE', 'https://cursor.com/docs/cli/installation'],
13
- 'kiro-cli': ['kiro-cli', 'HARNESS_MIX_KIRO_EXECUTABLE', 'https://kiro.dev/docs/cli/'],
14
- qoder: ['qodercli', 'HARNESS_MIX_QODER_EXECUTABLE', 'https://docs.qoder.com/cli/acp'],
15
- trae: ['traecli', 'HARNESS_MIX_TRAE_EXECUTABLE', 'https://github.com/bytedance/trae-agent'],
16
- cline: ['cline', 'HARNESS_MIX_CLINE_EXECUTABLE', 'https://docs.cline.bot/usage/cli-overview'],
17
- zcode: ['zcode-acp-server', 'HARNESS_MIX_ZCODE_ACP_EXECUTABLE', 'https://zcode.z.ai/'],
18
- };
19
- if (!specs[id]) throw new Error(`Unknown native CLI: ${id}`);
20
- const [bin, variable, url] = specs[id];
21
- const override = process.env[variable] || (buddy ? process.env.HARNESS_MIX_WORKBUDDY_EXECUTABLE : undefined);
22
- if (id === 'trae' && !override) throw new Error('Trae 官方仓库未确认 ACP 服务入口;请设置已验证的 HARNESS_MIX_TRAE_EXECUTABLE,不会猜测 traecli acp serve');
23
- if (id === 'zcode' && !override) throw new Error('ZCode 原生 CLI 提供 app-server 而非 ACP;需要显式配置 HARNESS_MIX_ZCODE_ACP_EXECUTABLE 指向兼容 ACP 桥接器');
24
- const roots = cursor
25
- ? [path.join(process.env.LOCALAPPDATA || os.homedir(), 'cursor-agent'), path.join(os.homedir(), '.local', 'bin')]
26
- : [path.join(process.env.LOCALAPPDATA || os.homedir(), 'Kiro-Cli'), path.join(os.homedir(), '.kiro', 'bin'), path.join(os.homedir(), '.local', 'bin')];
27
- const dirs = [...roots, path.join(process.env.APPDATA || os.homedir(), 'npm'), ...(process.env.PATH || '').split(path.delimiter).filter(Boolean)];
28
- const suffixes = process.platform === 'win32' ? ['.exe', '.cmd', '.ps1', ''] : [''];
29
- const bundled = buddy ? [path.join(process.env.ProgramFiles || 'C:/Program Files', 'WorkBuddy/resources/app.asar.unpacked/cli/bin/codebuddy'), path.join(process.env.LOCALAPPDATA || os.homedir(), 'Programs/WorkBuddy/resources/app.asar.unpacked/cli/bin/codebuddy')] : [];
30
- const bins = id === 'qoder' ? [bin, 'qoder'] : [bin];
31
- const found = override || [...dirs.flatMap(dir => bins.flatMap(name => suffixes.map(ext => path.join(dir, name + ext)))), ...bundled].find(file => fs.existsSync(file) && fs.statSync(file).isFile());
32
- if (!found || !fs.existsSync(found)) throw new Error(`${bin} 未安装;请从 ${url} 安装并原生登录`);
33
- if ((buddy && bundled.includes(found)) || /\.(cjs|mjs|js)$/i.test(found)) return { command: process.execPath, args: [found, ...argv] };
34
- if (id === 'qoder' && /\.(cmd|ps1)$/i.test(found)) {
35
- const bundle = path.join(path.dirname(found), 'node_modules/@qoder-ai/qodercli/bundle/qodercli.js');
36
- if (fs.existsSync(bundle)) return { command: process.execPath, args: [bundle, ...argv] };
37
- }
38
- if (cursor && /\.(cmd|ps1)$/i.test(found)) {
39
- const versions = path.join(path.dirname(found), 'versions');
40
- const version = fs.existsSync(versions) && fs.readdirSync(versions).filter(v => /^\d{4}\.\d{2}\.\d{2}-[a-z0-9-]+$/i.test(v)).sort().reverse().find(v => ['node.exe', 'index.js'].every(f => fs.existsSync(path.join(versions, v, f))));
41
- if (!version) throw new Error('Cursor launcher 缺少原生 bundle;请重新安装 Cursor CLI');
42
- return { command: path.join(versions, version, 'node.exe'), args: [path.join(versions, version, 'index.js'), ...argv] };
43
- }
44
- if (/\.(cmd|bat)$/i.test(found)) return cliSpawn(found.replace(/\.(cmd|bat)$/i, ''), argv);
45
- if (/\.ps1$/i.test(found)) return { command: 'pwsh.exe', args: ['-NoLogo', '-NoProfile', '-File', found, ...argv] };
46
- return { command: found, args: argv };
47
- }
48
- module.exports = { nativeCommand };
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const os = require('node:os');
4
+ const { cliSpawn } = require('../host/jsonl');
5
+
6
+ // Resolve only the named native CLI. In particular, `agent` can belong to Grok.
7
+ function nativeCommand(id, argv) {
8
+ const cursor = id === 'cursor-cli';
9
+ const buddy = id === 'codebuddy';
10
+ const specs = {
11
+ codebuddy: ['codebuddy', 'HARNESS_MIX_CODEBUDDY_EXECUTABLE', 'https://www.codebuddy.ai/docs/cli/overview'],
12
+ 'cursor-cli': ['cursor-agent', 'HARNESS_MIX_CURSOR_EXECUTABLE', 'https://cursor.com/docs/cli/installation'],
13
+ 'kiro-cli': ['kiro-cli', 'HARNESS_MIX_KIRO_EXECUTABLE', 'https://kiro.dev/docs/cli/'],
14
+ qoder: ['qodercli', 'HARNESS_MIX_QODER_EXECUTABLE', 'https://docs.qoder.com/cli/acp'],
15
+ trae: ['traecli', 'HARNESS_MIX_TRAE_EXECUTABLE', 'https://github.com/bytedance/trae-agent'],
16
+ cline: ['cline', 'HARNESS_MIX_CLINE_EXECUTABLE', 'https://docs.cline.bot/usage/cli-overview'],
17
+ };
18
+ if (!specs[id]) throw new Error(`Unknown native CLI: ${id}`);
19
+ const [bin, variable, url] = specs[id];
20
+ const override = process.env[variable] || (buddy ? process.env.HARNESS_MIX_WORKBUDDY_EXECUTABLE : undefined);
21
+ if (id === 'trae' && !override) throw new Error('Trae 官方仓库未确认 ACP 服务入口;请设置已验证的 HARNESS_MIX_TRAE_EXECUTABLE,不会猜测 traecli acp serve');
22
+ const roots = cursor
23
+ ? [path.join(process.env.LOCALAPPDATA || os.homedir(), 'cursor-agent'), path.join(os.homedir(), '.local', 'bin')]
24
+ : [path.join(process.env.LOCALAPPDATA || os.homedir(), 'Kiro-Cli'), path.join(os.homedir(), '.kiro', 'bin'), path.join(os.homedir(), '.local', 'bin')];
25
+ const dirs = [...roots, path.join(process.env.APPDATA || os.homedir(), 'npm'), ...(process.env.PATH || '').split(path.delimiter).filter(Boolean)];
26
+ const suffixes = process.platform === 'win32' ? ['.exe', '.cmd', '.ps1', ''] : [''];
27
+ const bundled = buddy ? [path.join(process.env.ProgramFiles || 'C:/Program Files', 'WorkBuddy/resources/app.asar.unpacked/cli/bin/codebuddy'), path.join(process.env.LOCALAPPDATA || os.homedir(), 'Programs/WorkBuddy/resources/app.asar.unpacked/cli/bin/codebuddy')] : [];
28
+ // Only the headless qodercli speaks ACP over stdio. The bare `qoder` on PATH
29
+ // is the IDE launcher: it passes the --version availability probe, but every
30
+ // --acp session open becomes a full GUI launch that never completes the ACP
31
+ // handshake. A working custom entry can still be set via
32
+ // HARNESS_MIX_QODER_EXECUTABLE.
33
+ const bins = [bin];
34
+ const found = override || [...dirs.flatMap(dir => bins.flatMap(name => suffixes.map(ext => path.join(dir, name + ext)))), ...bundled].find(file => fs.existsSync(file) && fs.statSync(file).isFile());
35
+ if (!found || !fs.existsSync(found)) throw new Error(`${bin} 未安装;请从 ${url} 安装并原生登录`);
36
+ if ((buddy && bundled.includes(found)) || /\.(cjs|mjs|js)$/i.test(found)) return { command: process.execPath, args: [found, ...argv] };
37
+ if (id === 'qoder' && /\.(cmd|ps1)$/i.test(found)) {
38
+ const bundle = path.join(path.dirname(found), 'node_modules/@qoder-ai/qodercli/bundle/qodercli.js');
39
+ if (fs.existsSync(bundle)) return { command: process.execPath, args: [bundle, ...argv] };
40
+ }
41
+ if (cursor && /\.(cmd|ps1)$/i.test(found)) {
42
+ const versions = path.join(path.dirname(found), 'versions');
43
+ const version = fs.existsSync(versions) && fs.readdirSync(versions).filter(v => /^\d{4}\.\d{2}\.\d{2}-[a-z0-9-]+$/i.test(v)).sort().reverse().find(v => ['node.exe', 'index.js'].every(f => fs.existsSync(path.join(versions, v, f))));
44
+ if (!version) throw new Error('Cursor launcher 缺少原生 bundle;请重新安装 Cursor CLI');
45
+ return { command: path.join(versions, version, 'node.exe'), args: [path.join(versions, version, 'index.js'), ...argv] };
46
+ }
47
+ if (/\.(cmd|bat)$/i.test(found)) return cliSpawn(found.replace(/\.(cmd|bat)$/i, ''), argv);
48
+ if (/\.ps1$/i.test(found)) return { command: 'pwsh.exe', args: ['-NoLogo', '-NoProfile', '-File', found, ...argv] };
49
+ return { command: found, args: argv };
50
+ }
51
+ module.exports = { nativeCommand };
@@ -27,7 +27,8 @@ function extractToolOutput(tool, update) {
27
27
  const options = list => (list || []).flatMap(o => Array.isArray(o.options) ? options(o.options) : typeof o.value === 'string' ? [o] : []);
28
28
  const finite = n => Number.isFinite(n) && n >= 0;
29
29
  function config(s, kind) {
30
- const ids = { model: ['model'], thinking: ['effortLevel', 'thought_level'], mode: ['mode'] }[kind];
30
+ // qoder names its effort selector `reasoning_effort` and files it under the model category.
31
+ const ids = { model: ['model'], thinking: ['effortLevel', 'thought_level', 'reasoning_effort'], mode: ['mode'] }[kind];
31
32
  return s.state.configOptions.find(o => ids.includes(o.id) || o.category === (kind === 'thinking' ? 'thought_level' : kind));
32
33
  }
33
34
  function catalog(s) {
@@ -237,12 +238,12 @@ function nativeAcp({ id, name, args, aliases = [], command = argv => nativeComma
237
238
  async describe() { return { models: null, thinkingLevels: [], permissionModes: [] }; },
238
239
  async describeFor(s) { return catalog(s); },
239
240
  async listModelsFor(s) { return catalog(s).models; },
240
- async open({ thread, emit, diagnostic = () => {}, collaboration, managedMcp = [] }) {
241
+ async open({ thread, emit, diagnostic = () => {}, collaboration, managedMcp = [] }) {
241
242
  const s = { vendor: id, cwd: thread.cwd, nativeSessionId: thread.restore ? thread.nativeSessionId : null,
242
243
  generation: 0, loading: true, active: false, closed: false, fault: null, emit, diagnostic,
243
244
  environment: thread.environment || {}, tools: new Map(), charges: new Map(), confirmed: {},
244
245
  state: { configOptions: [], models: null, modes: null, commands: [], usage: {} },
245
- mcpServers: require('./managed-mcp').acpServers(managedMcp, collaboration),
246
+ mcpServers: require('./managed-mcp').acpServers(managedMcp, collaboration),
246
247
  collaborationEnabled: !!collaboration,
247
248
  };
248
249
  s.interactions = new AcpInteractions(s, id);
@@ -256,7 +257,28 @@ function nativeAcp({ id, name, args, aliases = [], command = argv => nativeComma
256
257
  } catch (error) { await adapter.close(s); throw error; }
257
258
  },
258
259
  async send(s, prompt, hooks, attachments) {
259
- if (s.active || s.configuring || s.closed || s.fault) throw s.fault || new Error('Native session is busy or closed');
260
+ if (s.active || s.configuring) throw s.fault || new Error('Native session is busy or closed');
261
+ // A native CLI that died between turns (crash-on-exit bugs, idle
262
+ // reaping) used to poison the thread until a desktop restart: the
263
+ // session stayed cached with its fault and every later send rethrew
264
+ // it. Restore the native session once instead, replaying the
265
+ // confirmed model/thinking/permission selections.
266
+ if (s.fault || s.closed) {
267
+ const confirmed = { ...s.confirmed };
268
+ try {
269
+ ++s.generation;
270
+ s.fault = null; s.closed = false;
271
+ s.interactions.close();
272
+ s.diagnostic?.(`${name}: native session disconnected between turns, restoring ${s.nativeSessionId}`);
273
+ await connect(s, true);
274
+ if (confirmed.model) await adapter.setModel(s, confirmed.model).catch(error => s.diagnostic?.(`${name}: model restore after reconnect failed: ${error.message}`));
275
+ if (confirmed.thinking) await adapter.setThinkingLevel(s, confirmed.thinking).catch(error => s.diagnostic?.(`${name}: thinking restore after reconnect failed: ${error.message}`));
276
+ if (confirmed.mode) await adapter.setPermissionMode(s, confirmed.mode).catch(error => s.diagnostic?.(`${name}: permission mode restore after reconnect failed: ${error.message}`));
277
+ } catch (error) {
278
+ s.fault = s.fault || error;
279
+ throw s.fault;
280
+ }
281
+ }
260
282
  if (attachments?.images?.length && (!manifest.capabilities.attachments || !s.state.agentCapabilities.promptCapabilities?.image)) throw new Error(`${name} native image input is not supported`);
261
283
  s.active = true; s.tools.clear(); s.cancelRequested = false; s.turnAnswer = ''; s.turnProgressSeen = false; s.turnTextSeen = false;
262
284
  s.turnSettling = false; s.turnDrainWake = null;
@@ -385,7 +407,19 @@ function nativeAcp({ id, name, args, aliases = [], command = argv => nativeComma
385
407
  } else throw new Error(`Native ${kind} option unavailable`);
386
408
  } finally { s.configuring = false; }
387
409
  },
388
- async setModel(s, model) { await adapter.configure(s, 'model', model.id); s.model = catalog(s).models.find(m => m.id === model.id); s.confirmed.model = s.model; delete s.confirmed.thinking; return s.model; },
410
+ async setModel(s, model) {
411
+ const previousThinking = s.confirmed.thinking;
412
+ await adapter.configure(s, 'model', model.id); s.model = catalog(s).models.find(m => m.id === model.id); s.confirmed.model = s.model;
413
+ // Some natives reset the effort selector on model switches (qodercli
414
+ // Max→Flash resets reasoning_effort to its default). Replay the
415
+ // confirmed level while the new model still offers it, so the UI
416
+ // selection and the native session stay in sync.
417
+ if (previousThinking && catalog(s).thinkingLevels.some(o => o.id === previousThinking)) {
418
+ try { await adapter.configure(s, 'thinking', previousThinking); s.confirmed.thinking = previousThinking; }
419
+ catch (error) { delete s.confirmed.thinking; s.diagnostic?.(`${name}: thinking level not restored after model switch: ${error.message}`); }
420
+ } else delete s.confirmed.thinking;
421
+ return s.model;
422
+ },
389
423
  async setThinkingLevel(s, level) {
390
424
  if (!catalog(s).thinkingLevels.some(o => o.id === level)) throw new Error('Native effort unavailable for this model');
391
425
  await adapter.configure(s, 'thinking', level); s.confirmed.thinking = level;
@@ -415,13 +449,14 @@ function nativeAcp({ id, name, args, aliases = [], command = argv => nativeComma
415
449
  } else await adapter.send(s, '/' + name, hooks);
416
450
  },
417
451
  async fork(source, context) {
418
- if (!kiro || context.message) throw new Error('Native fork boundary unsupported');
419
- const checkpoint = await require('./kiro-history').latestKiroCheckpoint(source.cwd, source.nativeSessionId);
452
+ if (!manifest.capabilities.fork || context.message) throw new Error('Native fork boundary unsupported');
453
+ // Kiro forks at a message checkpoint; Qoder forks at the session head with the standard params.
454
+ const checkpoint = kiro ? await require('./kiro-history').latestKiroCheckpoint(source.cwd, source.nativeSessionId) : null;
420
455
  const probe = await adapter.open({ thread: { ...source, restore: true }, emit: () => {}, diagnostic: context.diagnostic });
421
456
  try {
422
- if (!probe.state.agentCapabilities.sessionCapabilities?.fork) throw new Error('Kiro did not advertise session/fork');
423
- const result = await probe.request('session/fork', { sessionId: source.nativeSessionId, cwd: source.cwd, _meta: { kiro: { messageId: checkpoint } } });
424
- if (!result.sessionId || result.sessionId === source.nativeSessionId) throw new Error('Kiro fork did not return a new native session');
457
+ if (!probe.state.agentCapabilities.sessionCapabilities?.fork) throw new Error(`${name} did not advertise session/fork`);
458
+ const result = await probe.request('session/fork', { sessionId: source.nativeSessionId, cwd: source.cwd, ...(kiro ? { _meta: { kiro: { messageId: checkpoint } } } : {}) });
459
+ if (!result.sessionId || result.sessionId === source.nativeSessionId) throw new Error(`${name} fork did not return a new native session`);
425
460
  return { session: await adapter.open({ thread: { ...source, nativeSessionId: result.sessionId, restore: true }, emit: context.emit, diagnostic: context.diagnostic }) };
426
461
  } finally { await adapter.close(probe); }
427
462
  },
@@ -429,7 +464,7 @@ function nativeAcp({ id, name, args, aliases = [], command = argv => nativeComma
429
464
  };
430
465
  return adapter;
431
466
  }
432
- manifest.integrations = { mcp: true };
433
- return { manifest, create };
467
+ manifest.integrations = { mcp: true };
468
+ return { manifest, create };
434
469
  }
435
470
  module.exports = { nativeAcp, catalog, project };
@@ -1,6 +1,9 @@
1
1
  // OMP(Oh My Pi)Adapter:Pi 的 fork,CLI 与 --mode rpc 协议同源。
2
- // 实现共享自 Pi 家族工厂(见 pi-family.js);OMP 侧差异(如有)在此覆盖。
3
- const { piFamily } = require('./pi-family');
2
+ // 实现共享自 Pi 家族工厂(见 pi-family.js);OMP 侧差异在此覆盖。
3
+ // 权限模型已分叉:OMP --approval-mode always-ask|write|yolo(无 Pi 的
4
+ // --approve/--no-approve 项目信任旗标),目录与启动旗标见 pi-family.js 的
5
+ // OMP_APPROVAL_MODES(实测 @oh-my-pi/pi-coding-agent 18.1.19 旗标表与设置 schema)。
6
+ const { piFamily, OMP_APPROVAL_MODES, ompPermissionLaunchArgs } = require('./pi-family');
4
7
 
5
8
  module.exports = piFamily({
6
9
  id: 'omp',
@@ -9,6 +12,8 @@ module.exports = piFamily({
9
12
  bin: 'omp',
10
13
  packageHint: 'npm i -g @oh-my-pi/pi-coding-agent 或 https://omp.sh/install',
11
14
  aliases: ['omp', 'oh-my-pi'],
15
+ permissionModes: OMP_APPROVAL_MODES,
16
+ permissionLaunchArgs: ompPermissionLaunchArgs,
12
17
  });
13
18
  module.exports.manifest.integrations = {
14
19
  mcp: false,