mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 20:17:45 +00:00
* fix(cliproxy): pass variant port to executor for isolation Variants configured with dedicated ports (8318-8417) were not using their assigned port. The executor always defaulted to 8317. Changes: - Add port field to ProfileDetectionResult interface - Pass variant.port from profile-detector to ccs.ts - Forward port to execClaudeWithCLIProxy options - Update executor priority: CLI flags > variant port > config.yaml > default Closes #228 * fix(cliproxy): propagate port in unified config and UI preset handlers Edge case fixes identified in codebase review: - Unified config variant detection: add settingsPath and port fields - Provider editor: use variant port in handleApplyPreset/handleCustomPresetApply - preset-utils: add optional port parameter to applyDefaultPreset() * chore(release): 7.11.1-dev.1 [skip ci] * fix(cliproxy): use correct default port (8317) for remote HTTP connections Root cause: Inconsistent default port logic across code paths. - Test Connection used 8317 (correct) - Actual API calls used 80 (wrong) Changes: - Add centralized getRemoteDefaultPort() helper in config-generator.ts - Fix proxy-target-resolver.ts to use shared helper - Fix rewriteLocalhostUrls() and getRemoteEnvVars() in config-generator.ts - Update remote-proxy-client.ts to use shared helper (DRY) Fixes all 3 reported issues: 1. Port empty → now correctly uses :8317 instead of :80 2. BASEURL construction → now includes correct port 3. CLIProxy Plus auth → now fetches from remote on correct port * chore(release): 7.11.1-dev.2 [skip ci] * feat(delegation): add Claude Code CLI flag passthrough Add explicit passthrough support for key Claude Code CLI flags: - --max-turns: Limit agentic turns (prevents infinite loops) - --fallback-model: Auto-fallback when model overloaded - --agents: Dynamic subagent JSON injection - --betas: Enable experimental features Maintain extraArgs catch-all for future Claude Code flags. Update help command with new "Delegation Flags" section. Closes #89 * test(delegation): add comprehensive CLI flag passthrough tests Add 45 test cases covering all edge cases for CLI flag passthrough: - DelegationHandler: timeout/max-turns/fallback-model/agents/betas validation - HeadlessExecutor: duplicate flag filtering, undefined vs truthy checks * chore(release): 7.11.1-dev.3 [skip ci] * fix(ui): enable cancel button during OAuth authentication Resolves #234 - Cancel button was disabled during authentication flow, preventing users from canceling the OAuth process. Changes: - Add auth-session-manager.ts for tracking active OAuth sessions - Add POST /cliproxy/auth/:provider/cancel endpoint to abort sessions - Kill spawned CLIProxy auth process when cancel is triggered - Enable Cancel button in AddAccountDialog during authentication - Add cancel support to QuickSetupWizard auth step - Update useCancelAuth hook to call backend cancel endpoint * chore(release): 7.11.1-dev.4 [skip ci] * fix(prompt): add stdin.pause() to prevent process hang after password input Fixes #236. The password() method called resume() on stdin but never paused it in cleanup, keeping the event loop alive indefinitely. * chore(release): 7.11.1-dev.5 [skip ci] * feat(cliproxy): add --allow-self-signed flag for HTTPS connections (#227) Previously, allowSelfSigned was hardcoded to true for all HTTPS protocol connections, forcing use of the native https module which has issues with Cloudflare-proxied connections causing timeouts. This change: - Adds --allow-self-signed CLI flag (default: false) - Adds CCS_ALLOW_SELF_SIGNED environment variable - Uses standard fetch API by default for HTTPS (works with valid certs) - Only uses native https module when --allow-self-signed is specified Usage: - For production HTTPS proxies with valid certs: no flag needed - For dev proxies with self-signed certs: use --allow-self-signed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * chore(release): 7.11.1-dev.6 [skip ci] --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Shun Kakinoki <39187513+shunkakinoki@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
170 lines
5.7 KiB
TypeScript
170 lines
5.7 KiB
TypeScript
/**
|
|
* Tests for headless-executor CLI flag passthrough feature
|
|
* Covers: duplicate flag filtering, undefined checks, flag construction
|
|
*/
|
|
|
|
import { describe, it, expect } from 'bun:test';
|
|
|
|
describe('HeadlessExecutor flag construction', () => {
|
|
// Test the flag construction logic by simulating the filtering behavior
|
|
// Since HeadlessExecutor.execute() spawns a process, we test the logic directly
|
|
|
|
describe('Duplicate flag filtering', () => {
|
|
function filterExtraArgs(
|
|
extraArgs: string[],
|
|
explicitFlags: Set<string>
|
|
): string[] {
|
|
const filteredExtras: string[] = [];
|
|
for (let i = 0; i < extraArgs.length; i++) {
|
|
if (explicitFlags.has(extraArgs[i])) {
|
|
// Skip this flag and its value (next element)
|
|
if (i + 1 < extraArgs.length && !extraArgs[i + 1].startsWith('-')) {
|
|
i++; // Skip value too
|
|
}
|
|
continue;
|
|
}
|
|
filteredExtras.push(extraArgs[i]);
|
|
}
|
|
return filteredExtras;
|
|
}
|
|
|
|
const explicitFlags = new Set(['--max-turns', '--fallback-model', '--agents', '--betas']);
|
|
|
|
it('removes duplicate --max-turns from extraArgs', () => {
|
|
const result = filterExtraArgs(['--max-turns', '10', '--verbose'], explicitFlags);
|
|
expect(result).not.toContain('--max-turns');
|
|
expect(result).not.toContain('10');
|
|
expect(result).toContain('--verbose');
|
|
});
|
|
|
|
it('removes duplicate --fallback-model from extraArgs', () => {
|
|
const result = filterExtraArgs(['--fallback-model', 'opus', '--debug'], explicitFlags);
|
|
expect(result).not.toContain('--fallback-model');
|
|
expect(result).not.toContain('opus');
|
|
expect(result).toContain('--debug');
|
|
});
|
|
|
|
it('removes duplicate --agents from extraArgs', () => {
|
|
const result = filterExtraArgs(['--agents', '{"x":1}', '--json'], explicitFlags);
|
|
expect(result).not.toContain('--agents');
|
|
expect(result).not.toContain('{"x":1}');
|
|
expect(result).toContain('--json');
|
|
});
|
|
|
|
it('removes duplicate --betas from extraArgs', () => {
|
|
const result = filterExtraArgs(['--betas', 'feature1', '--output', 'json'], explicitFlags);
|
|
expect(result).not.toContain('--betas');
|
|
expect(result).not.toContain('feature1');
|
|
expect(result).toContain('--output');
|
|
expect(result).toContain('json');
|
|
});
|
|
|
|
it('handles flag at end of array without value', () => {
|
|
const result = filterExtraArgs(['--verbose', '--max-turns'], explicitFlags);
|
|
expect(result).toContain('--verbose');
|
|
expect(result).not.toContain('--max-turns');
|
|
});
|
|
|
|
it('handles flag with dash-prefixed next arg (not a value)', () => {
|
|
const result = filterExtraArgs(['--max-turns', '--verbose'], explicitFlags);
|
|
// --max-turns is filtered, but --verbose is kept because it starts with -
|
|
expect(result).not.toContain('--max-turns');
|
|
expect(result).toContain('--verbose');
|
|
});
|
|
|
|
it('preserves unknown flags', () => {
|
|
const result = filterExtraArgs(['--custom-flag', 'value', '--another'], explicitFlags);
|
|
expect(result).toEqual(['--custom-flag', 'value', '--another']);
|
|
});
|
|
|
|
it('handles empty array', () => {
|
|
const result = filterExtraArgs([], explicitFlags);
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('removes multiple duplicate flags', () => {
|
|
const result = filterExtraArgs(
|
|
['--max-turns', '5', '--agents', '{}', '--fallback-model', 'sonnet'],
|
|
explicitFlags
|
|
);
|
|
expect(result).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('Flag undefined vs truthy checks', () => {
|
|
// Simulate the flag construction logic
|
|
function buildArgs(options: {
|
|
maxTurns?: number;
|
|
fallbackModel?: string;
|
|
agents?: string;
|
|
betas?: string;
|
|
}): string[] {
|
|
const args: string[] = [];
|
|
const { maxTurns, fallbackModel, agents, betas } = options;
|
|
|
|
if (maxTurns !== undefined && maxTurns > 0) {
|
|
args.push('--max-turns', String(maxTurns));
|
|
}
|
|
if (fallbackModel !== undefined && fallbackModel) {
|
|
args.push('--fallback-model', fallbackModel);
|
|
}
|
|
if (agents !== undefined && agents) {
|
|
args.push('--agents', agents);
|
|
}
|
|
if (betas !== undefined && betas) {
|
|
args.push('--betas', betas);
|
|
}
|
|
|
|
return args;
|
|
}
|
|
|
|
it('handles all undefined options', () => {
|
|
const args = buildArgs({});
|
|
expect(args).toEqual([]);
|
|
});
|
|
|
|
it('handles maxTurns = 0 (should not add flag)', () => {
|
|
const args = buildArgs({ maxTurns: 0 });
|
|
expect(args).toEqual([]);
|
|
});
|
|
|
|
it('handles maxTurns > 0', () => {
|
|
const args = buildArgs({ maxTurns: 10 });
|
|
expect(args).toEqual(['--max-turns', '10']);
|
|
});
|
|
|
|
it('handles empty string fallbackModel (should not add flag)', () => {
|
|
const args = buildArgs({ fallbackModel: '' });
|
|
expect(args).toEqual([]);
|
|
});
|
|
|
|
it('handles valid fallbackModel', () => {
|
|
const args = buildArgs({ fallbackModel: 'sonnet' });
|
|
expect(args).toEqual(['--fallback-model', 'sonnet']);
|
|
});
|
|
|
|
it('handles empty string agents (should not add flag)', () => {
|
|
const args = buildArgs({ agents: '' });
|
|
expect(args).toEqual([]);
|
|
});
|
|
|
|
it('handles valid JSON agents', () => {
|
|
const args = buildArgs({ agents: '{"name":"test"}' });
|
|
expect(args).toEqual(['--agents', '{"name":"test"}']);
|
|
});
|
|
|
|
it('handles all valid options', () => {
|
|
const args = buildArgs({
|
|
maxTurns: 5,
|
|
fallbackModel: 'opus',
|
|
agents: '{}',
|
|
betas: 'feature1',
|
|
});
|
|
expect(args).toContain('--max-turns');
|
|
expect(args).toContain('--fallback-model');
|
|
expect(args).toContain('--agents');
|
|
expect(args).toContain('--betas');
|
|
});
|
|
});
|
|
});
|