diff --git a/cli/__tests__/adapters.qwen.test.mjs b/cli/__tests__/adapters.qwen.test.mjs index a26b702e55..b8877b2c0b 100644 --- a/cli/__tests__/adapters.qwen.test.mjs +++ b/cli/__tests__/adapters.qwen.test.mjs @@ -99,6 +99,7 @@ test('public launch isolates HOME and config, strips shadows, and never serializ const executable = join(realpathSync(dirname(ctx._binaryPath)), basename(ctx._binaryPath)); expect(cmd).toBe(executable); expect(args).toEqual(expect.arrayContaining(['--bare', '--safe-mode', '--auth-type', 'openai', '-o', 'stream-json'])); + expect(args).not.toContain('--allowed-tools'); expect(args[args.indexOf('--exclude-tools') + 1].split(',')) .toEqual(expect.arrayContaining([ 'agent', 'workflow', 'advisor', 'replace', 'task', 'search_file_content', @@ -241,6 +242,47 @@ test('an explicit MCP server reaches --mcp-config alongside --safe-mode', async }); }); +test('private Qwen approves only declared Commonly MCP tools in the non-interactive run', async () => { + await withSeat(async (ctx) => { + let observed; + await qwen.spawn('hello', { + ...ctx, + environment: { + ...ctx.environment, + sandbox: undefined, + mcp: [ + { name: 'commonly', transport: 'stdio', command: ['/usr/bin/true'] }, + { name: 'other', transport: 'stdio', command: ['/usr/bin/true'] }, + ], + }, + _spawnImpl: (_cmd, args) => { + observed = { + args, + config: JSON.parse(readFileSync(args[args.indexOf('--mcp-config') + 1], 'utf8')), + }; + return fakeChild(); + }, + }); + expect(Object.keys(observed.config.mcpServers)).toEqual(['commonly', 'other']); + expect(observed.args[observed.args.indexOf('--allowed-tools') + 1]).toBe('mcp__commonly__*'); + expect(observed.args).toContain('--safe-mode'); + expect(observed.args).not.toContain('--approval-mode'); + expect(observed.args).not.toContain('--yolo'); + }); +}); + +test('private Qwen does not approve Commonly tools when that server was not configured', async () => { + await withSeat(async (ctx) => { + let args; + await qwen.spawn('hello', { + ...ctx, + environment: { ...ctx.environment, sandbox: undefined }, + _spawnImpl: (_cmd, argv) => { args = argv; return fakeChild(); }, + }); + expect(args).not.toContain('--allowed-tools'); + }); +}); + // TASK-273: a public seat whose MCP did not start used to run with a notice, // and the model narrated the failure to a stranger (2026-10-15). It now // refuses the turn before Qwen runs. @@ -286,6 +328,7 @@ test('a warm public Qwen spawn grants only the selected MCP version directory', try { let profileOptions; let config; + let launchArgs; await qwen.spawn('hello', { ...ctx, environment: { ...ctx.environment, mcp: [{ @@ -293,10 +336,12 @@ test('a warm public Qwen spawn grants only the selected MCP version directory', }] }, _wrapArgvWithSeatbelt: (argv, options) => { profileOptions = options; return argv; }, _spawnImpl: (_cmd, args) => { + launchArgs = args; config = JSON.parse(readFileSync(args[args.indexOf('--mcp-config') + 1], 'utf8')); return fakeChild(); }, }); + expect(launchArgs).not.toContain('--allowed-tools'); expect(config.mcpServers.commonly).toMatchObject({ command: 'node', args: [join(packageDir, 'src', 'index.js')], }); diff --git a/cli/package-lock.json b/cli/package-lock.json index 1592188a18..df4dd4df33 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "@commonlyai/cli", - "version": "0.1.138", + "version": "0.1.139", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@commonlyai/cli", - "version": "0.1.138", + "version": "0.1.139", "license": "Apache-2.0", "dependencies": { "commander": "^12.0.0" diff --git a/cli/package.json b/cli/package.json index d595dcbd46..a7d7f5bedc 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@commonlyai/cli", - "version": "0.1.138", + "version": "0.1.139", "license": "Apache-2.0", "description": "The Commonly CLI — connect agents, manage pods, iterate fast", "type": "module", diff --git a/cli/src/lib/adapters/qwen.js b/cli/src/lib/adapters/qwen.js index be3bc32557..f6705a67fb 100644 --- a/cli/src/lib/adapters/qwen.js +++ b/cli/src/lib/adapters/qwen.js @@ -308,6 +308,11 @@ export default { '--mcp-config', mcpPath, '--exclude-tools', [ ...EXCLUDED_TOOLS, ...(isPublic ? PUBLIC_READ_TOOLS : []), ].join(','), + // Qwen ignores settings permissions in bare/safe mode, but still + // honours CLI allow rules. A private seat needs its declared Commonly + // tools approved before non-interactive execution begins. + ...(!isPublic && Object.hasOwn(mcp.mcpServers, 'commonly') + ? ['--allowed-tools', 'mcp__commonly__*'] : []), '-o', 'stream-json', '-p', fullPrompt, ]; let cmd = executable;