mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 12:19:35 +00:00
fix(copilot): refine ownership checks and command error handling
This commit is contained in:
+3
-6
@@ -30,7 +30,7 @@ import { getGlobalEnvConfig } from './config/unified-config-loader';
|
|||||||
import { ensureProfileHooks as ensureImageAnalyzerHooks } from './utils/hooks/image-analyzer-profile-hook-injector';
|
import { ensureProfileHooks as ensureImageAnalyzerHooks } from './utils/hooks/image-analyzer-profile-hook-injector';
|
||||||
import { getImageAnalysisHookEnv } from './utils/hooks';
|
import { getImageAnalysisHookEnv } from './utils/hooks';
|
||||||
import { fail, info, warn } from './utils/ui';
|
import { fail, info, warn } from './utils/ui';
|
||||||
import { isCopilotSubcommandToken, isLikelyCopilotFlagAlias } from './copilot/constants';
|
import { isCopilotSubcommandToken } from './copilot/constants';
|
||||||
|
|
||||||
// Import centralized error handling
|
// Import centralized error handling
|
||||||
import { handleError, runCleanup } from './errors';
|
import { handleError, runCleanup } from './errors';
|
||||||
@@ -573,13 +573,10 @@ async function main(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special case: copilot command (GitHub Copilot integration)
|
// Special case: copilot command (GitHub Copilot integration)
|
||||||
// Route known subcommands and likely mistyped aliases (`--statu`) to command handler.
|
// Route known subcommands to command handler, keep all other args as profile passthrough.
|
||||||
// Non-command Claude args still pass through profile flow.
|
|
||||||
if (firstArg === 'copilot' && args.length > 1) {
|
if (firstArg === 'copilot' && args.length > 1) {
|
||||||
const copilotToken = args[1];
|
const copilotToken = args[1];
|
||||||
const shouldRouteToCopilotCommand =
|
const shouldRouteToCopilotCommand = isCopilotSubcommandToken(copilotToken);
|
||||||
isCopilotSubcommandToken(copilotToken) ||
|
|
||||||
(args.length === 2 && isLikelyCopilotFlagAlias(copilotToken));
|
|
||||||
|
|
||||||
if (shouldRouteToCopilotCommand) {
|
if (shouldRouteToCopilotCommand) {
|
||||||
const { handleCopilotCommand } = await import('./commands/copilot-command');
|
const { handleCopilotCommand } = await import('./commands/copilot-command');
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ export async function handleCopilotCommand(args: string[]): Promise<number> {
|
|||||||
default:
|
default:
|
||||||
console.error(fail(`Unknown subcommand: ${subcommand}`));
|
console.error(fail(`Unknown subcommand: ${subcommand}`));
|
||||||
console.error('');
|
console.error('');
|
||||||
return handleHelp();
|
handleHelp();
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,23 +45,3 @@ export function normalizeCopilotSubcommand(token?: string): string | undefined {
|
|||||||
export function isCopilotSubcommandToken(token?: string): boolean {
|
export function isCopilotSubcommandToken(token?: string): boolean {
|
||||||
return Boolean(token) && COPILOT_SUBCOMMAND_TOKENS.includes(token as string);
|
return Boolean(token) && COPILOT_SUBCOMMAND_TOKENS.includes(token as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect likely mistyped copilot flag aliases (e.g. `--statu`).
|
|
||||||
* This helps entrypoint routing show command help instead of falling through
|
|
||||||
* to profile execution for obvious copilot-command typos.
|
|
||||||
*/
|
|
||||||
export function isLikelyCopilotFlagAlias(token?: string): boolean {
|
|
||||||
if (!token || !token.startsWith('--') || token === '--') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const alias = token.slice(2).toLowerCase();
|
|
||||||
if (!/^[a-z][a-z0-9-]*$/.test(alias)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return COPILOT_SUBCOMMANDS.some(
|
|
||||||
(subcommand) => subcommand.startsWith(alias) || alias.startsWith(subcommand)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -289,12 +289,11 @@ export async function stopDaemon(): Promise<{ success: boolean; error?: string }
|
|||||||
try {
|
try {
|
||||||
const ownership = verifyProcessOwnership(pid, (commandLine) => {
|
const ownership = verifyProcessOwnership(pid, (commandLine) => {
|
||||||
const lower = commandLine.toLowerCase();
|
const lower = commandLine.toLowerCase();
|
||||||
const hasCopilotApiBinary = /(^|[\\/\s])copilot-api(\.cmd|\.exe)?(\s|$)/.test(lower);
|
const hasCopilotApiBinary = /copilot-api(\.cmd|\.exe)?/.test(lower);
|
||||||
const hasStartCommand = /\bstart\b/.test(lower);
|
const hasStartCommand = /\bstart\b/.test(lower);
|
||||||
const hasExpectedPort =
|
const hasPortArgument = /--port(?:\s+|=)\d+\b/.test(lower);
|
||||||
lower.includes(`--port ${configuredPort}`) || lower.includes(`--port=${configuredPort}`);
|
|
||||||
// copilot-api is launched as `... copilot-api start --port <n>`
|
// copilot-api is launched as `... copilot-api start --port <n>`
|
||||||
return hasCopilotApiBinary && hasStartCommand && hasExpectedPort;
|
return hasCopilotApiBinary && hasStartCommand && hasPortArgument;
|
||||||
});
|
});
|
||||||
if (ownership === 'not-running') {
|
if (ownership === 'not-running') {
|
||||||
removePidFile();
|
removePidFile();
|
||||||
|
|||||||
@@ -92,6 +92,25 @@ router.put('/config', (req: Request, res: Response): void => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const payload = updates as Record<string, unknown>;
|
const payload = updates as Record<string, unknown>;
|
||||||
|
const allowedKeys = new Set([
|
||||||
|
'enabled',
|
||||||
|
'auto_start',
|
||||||
|
'port',
|
||||||
|
'account_type',
|
||||||
|
'rate_limit',
|
||||||
|
'wait_on_limit',
|
||||||
|
'model',
|
||||||
|
'opus_model',
|
||||||
|
'sonnet_model',
|
||||||
|
'haiku_model',
|
||||||
|
]);
|
||||||
|
const unknownKeys = Object.keys(payload).filter((key) => !allowedKeys.has(key));
|
||||||
|
if (unknownKeys.length > 0) {
|
||||||
|
res.status(400).json({
|
||||||
|
error: `Unknown copilot config field(s): ${unknownKeys.join(', ')}`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ('port' in payload) {
|
if ('port' in payload) {
|
||||||
if (typeof payload.port !== 'number' || !Number.isInteger(payload.port)) {
|
if (typeof payload.port !== 'number' || !Number.isInteger(payload.port)) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { describe, expect, it } from 'bun:test';
|
|||||||
import {
|
import {
|
||||||
COPILOT_SUBCOMMANDS,
|
COPILOT_SUBCOMMANDS,
|
||||||
COPILOT_SUBCOMMAND_TOKENS,
|
COPILOT_SUBCOMMAND_TOKENS,
|
||||||
isLikelyCopilotFlagAlias,
|
|
||||||
normalizeCopilotSubcommand,
|
normalizeCopilotSubcommand,
|
||||||
} from '../../../src/copilot/constants';
|
} from '../../../src/copilot/constants';
|
||||||
|
|
||||||
@@ -24,13 +23,6 @@ describe('copilot command aliases', () => {
|
|||||||
expect(normalizeCopilotSubcommand('unknown')).toBe('unknown');
|
expect(normalizeCopilotSubcommand('unknown')).toBe('unknown');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('detects likely mistyped command aliases for entrypoint routing', () => {
|
|
||||||
expect(isLikelyCopilotFlagAlias('--statu')).toBe(true);
|
|
||||||
expect(isLikelyCopilotFlagAlias('--enabl')).toBe(true);
|
|
||||||
expect(isLikelyCopilotFlagAlias('--print')).toBe(false);
|
|
||||||
expect(isLikelyCopilotFlagAlias('--')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('exposes complete routing token list for ccs entrypoint', () => {
|
it('exposes complete routing token list for ccs entrypoint', () => {
|
||||||
for (const subcommand of COPILOT_SUBCOMMANDS) {
|
for (const subcommand of COPILOT_SUBCOMMANDS) {
|
||||||
expect(COPILOT_SUBCOMMAND_TOKENS).toContain(subcommand);
|
expect(COPILOT_SUBCOMMAND_TOKENS).toContain(subcommand);
|
||||||
|
|||||||
Reference in New Issue
Block a user