From 5ed1299c560cfc45288b87ac4593356fc5e1b297 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 23 Oct 2026 07:59:07 -0700 Subject: [PATCH 1/2] fix(cli): approve private Qwen Commonly MCP tools Co-authored-by: kai Agent-Model: codex/gpt-6-sol/medium --- cli/__tests__/adapters.qwen.test.mjs | 42 ++++++++++++++++++++++++++++ cli/package-lock.json | 4 +-- cli/package.json | 2 +- cli/src/lib/adapters/qwen.js | 5 ++++ 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/cli/__tests__/adapters.qwen.test.mjs b/cli/__tests__/adapters.qwen.test.mjs index a26b702e55..2f27568bf6 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. diff --git a/cli/package-lock.json b/cli/package-lock.json index f7f87ac264..df4dd4df33 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "@commonlyai/cli", - "version": "0.1.133", + "version": "0.1.139", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@commonlyai/cli", - "version": "0.1.133", + "version": "0.1.139", "license": "Apache-2.0", "dependencies": { "commander": "^12.0.0" diff --git a/cli/package.json b/cli/package.json index 1c4ad89f7b..90cde7ea88 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@commonlyai/cli", - "version": "0.1.133", + "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 92bf318dc1..cd624ccdca 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; From 11498b1f6e5a490cfad81ee332fd9f03a4d4e7e7 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 23 Oct 2026 08:07:31 -0700 Subject: [PATCH 2/2] test(cli): guard public Qwen MCP approval boundary Co-authored-by: kai Agent-Model: codex/gpt-6-sol/medium --- cli/__tests__/adapters.qwen.test.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/__tests__/adapters.qwen.test.mjs b/cli/__tests__/adapters.qwen.test.mjs index 2f27568bf6..b8877b2c0b 100644 --- a/cli/__tests__/adapters.qwen.test.mjs +++ b/cli/__tests__/adapters.qwen.test.mjs @@ -328,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: [{ @@ -335,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')], });