Skip to content

Commit 489fcd7

Browse files
committed
feat(core): introduce TrustGateError for setApprovalMode (#4175 Wave 4 PR 17)
Adds a named subclass `TrustGateError` thrown by `Config.setApprovalMode` when the requested mode would grant privileged tool autonomy in a folder the user has not marked as trusted. Daemon mutation routes can now recognize this rejection class without depending on message text. Extends `mapDomainErrorToErrorKind` in `packages/cli/src/serve/status.ts` to map `TrustGateError → 'auth_env_error'`. Matches by `err.name` rather than `instanceof` because cross-package bundling can produce duplicate class instances where `instanceof` returns false. Test covers both the real class and a name-synthesized instance. Foundation for the `POST /session/:id/approval-mode` route landing in a follow-up commit in this PR. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
1 parent 33b2e0d commit 489fcd7

3 files changed

Lines changed: 34 additions & 2 deletions

File tree

packages/cli/src/serve/status.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import { SkillError } from '@qwen-code/qwen-code-core';
7+
import { SkillError, TrustGateError } from '@qwen-code/qwen-code-core';
88
import { describe, expect, it } from 'vitest';
99
import {
1010
BridgeTimeoutError,
@@ -87,6 +87,17 @@ describe('mapDomainErrorToErrorKind', () => {
8787
);
8888
});
8989

90+
it('classifies TrustGateError as auth_env_error (recognized via .name across package boundaries)', () => {
91+
const err = new TrustGateError('untrusted folder rejects YOLO');
92+
expect(mapDomainErrorToErrorKind(err)).toBe('auth_env_error');
93+
// Synthesize the same class by name alone — verifies the matcher works
94+
// even when a bundled-twice instance breaks `instanceof` symmetry.
95+
const synthetic = Object.assign(new Error('synthetic'), {
96+
name: 'TrustGateError',
97+
});
98+
expect(mapDomainErrorToErrorKind(synthetic)).toBe('auth_env_error');
99+
});
100+
90101
it('classifies ModelConfigError subclasses (recognized via .name) as auth_env_error', () => {
91102
for (const name of [
92103
'StrictMissingCredentialsError',

packages/cli/src/serve/status.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,10 @@ export function mapDomainErrorToErrorKind(
563563
}
564564
if (err instanceof SyntaxError) return 'parse_error';
565565
if (!(err instanceof Error)) return undefined;
566+
// `TrustGateError` is defined in `@qwen-code/qwen-code-core/config`; we
567+
// match by `.name` rather than `instanceof` because cross-package bundling
568+
// can produce duplicate class instances where `instanceof` returns false.
569+
if (err.name === 'TrustGateError') return 'auth_env_error';
566570
if (MODEL_CONFIG_ERROR_NAMES.has(err.name)) return 'auth_env_error';
567571
const code = (err as { code?: unknown }).code;
568572
if (typeof code === 'string' && FS_MISSING_CODES.has(code)) {

packages/core/src/config/config.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,23 @@ export enum ApprovalMode {
175175

176176
export const APPROVAL_MODES = Object.values(ApprovalMode);
177177

178+
/**
179+
* Thrown by `Config.setApprovalMode` when the requested mode would grant
180+
* privileged tool autonomy in a folder the user has not marked as trusted.
181+
*
182+
* Why: the daemon mutation route at `POST /session/:id/approval-mode` needs
183+
* to recognize this specific class of rejection and translate it into a
184+
* structured `errorKind: 'auth_env_error'` rather than a generic 500.
185+
* Using a named subclass lets the bridge match by `err.name` without
186+
* depending on the message text (which would drift across i18n).
187+
*/
188+
export class TrustGateError extends Error {
189+
constructor(message: string) {
190+
super(message);
191+
this.name = 'TrustGateError';
192+
}
193+
}
194+
178195
/**
179196
* Information about an approval mode including display name and description.
180197
*/
@@ -2474,7 +2491,7 @@ export class Config {
24742491
mode !== ApprovalMode.DEFAULT &&
24752492
mode !== ApprovalMode.PLAN
24762493
) {
2477-
throw new Error(
2494+
throw new TrustGateError(
24782495
'Cannot enable privileged approval modes in an untrusted folder.',
24792496
);
24802497
}

0 commit comments

Comments
 (0)