mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
feat(targets,management): instrument adapter spawn lifecycle and instance manager
Each target adapter (claude, codex, droid) now emits dispatch on child spawn and respond on child exit with latencyMs. instance-manager emits lifecycle events for instance create / start / stop so multi-instance flows are traceable per requestId. Refs #1141, #1138
This commit is contained in:
@@ -13,6 +13,9 @@ import ProfileContextSyncLock from './profile-context-sync-lock';
|
||||
import { DEFAULT_ACCOUNT_CONTEXT_MODE } from '../auth/account-context';
|
||||
import type { AccountContextPolicy } from '../auth/account-context';
|
||||
import { getCcsDir, getCcsHome } from '../utils/config-manager';
|
||||
import { createLogger } from '../services/logging';
|
||||
|
||||
const logger = createLogger('management:instance-manager');
|
||||
|
||||
const MANAGED_MCP_SERVER_NAMES = new Set(['ccs-websearch', 'ccs-image-analysis', 'ccs-browser']);
|
||||
|
||||
@@ -52,6 +55,10 @@ class InstanceManager {
|
||||
await this.contextSyncLock.withLock(profileName, async () => {
|
||||
// Lazy initialization
|
||||
if (!fs.existsSync(instancePath)) {
|
||||
logger.stage('route', 'instance.init', 'Initializing new profile instance', {
|
||||
profile: profileName,
|
||||
bare: options.bare === true,
|
||||
});
|
||||
this.initializeInstance(profileName, instancePath, options);
|
||||
}
|
||||
|
||||
@@ -170,6 +177,9 @@ class InstanceManager {
|
||||
}
|
||||
|
||||
fs.rmSync(instancePath, { recursive: true, force: true });
|
||||
logger.stage('cleanup', 'instance.deleted', 'Profile instance deleted', {
|
||||
profile: profileName,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ import { getWebSearchHookEnv } from '../utils/websearch-manager';
|
||||
import { appendBrowserToolArgs } from '../utils/browser';
|
||||
import { wireChildProcessSignals } from '../utils/signal-forwarder';
|
||||
import { runCleanup } from '../errors';
|
||||
import { createLogger } from '../services/logging';
|
||||
|
||||
const adapterLogger = createLogger('targets:claude');
|
||||
|
||||
export class ClaudeAdapter implements TargetAdapter {
|
||||
readonly type: TargetType = 'claude';
|
||||
@@ -101,6 +104,13 @@ export class ClaudeAdapter implements TargetAdapter {
|
||||
const isPowerShellScript = isWindows && /\.ps1$/i.test(claudeCli);
|
||||
const needsShell = isWindows && /\.(cmd|bat)$/i.test(claudeCli);
|
||||
|
||||
const spawnStartedAt = Date.now();
|
||||
adapterLogger.stage('dispatch', 'target.spawn', 'Spawning Claude CLI child process', {
|
||||
target: 'claude',
|
||||
claudeCli,
|
||||
argCount: args.length,
|
||||
});
|
||||
|
||||
let child: ChildProcess;
|
||||
if (isPowerShellScript) {
|
||||
child = spawn(
|
||||
@@ -128,6 +138,16 @@ export class ClaudeAdapter implements TargetAdapter {
|
||||
});
|
||||
}
|
||||
|
||||
child.on('exit', (code, signal) => {
|
||||
adapterLogger.stage(
|
||||
'respond',
|
||||
'target.exit',
|
||||
'Claude CLI child process exited',
|
||||
{ target: 'claude', exitCode: code, signal },
|
||||
{ latencyMs: Date.now() - spawnStartedAt }
|
||||
);
|
||||
});
|
||||
|
||||
wireChildProcessSignals(child, async (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'EACCES') {
|
||||
console.error(`[X] Claude CLI is not executable: ${claudeCli}`);
|
||||
|
||||
@@ -23,6 +23,9 @@ import {
|
||||
getCodexBinaryInfo,
|
||||
readCodexVersion,
|
||||
} from './codex-detector';
|
||||
import { createLogger } from '../services/logging';
|
||||
|
||||
const adapterLogger = createLogger('targets:codex');
|
||||
|
||||
const CODEX_RUNTIME_PROVIDER_ID = 'ccs_runtime';
|
||||
const CODEX_RUNTIME_ENV_KEY = 'CCS_CODEX_API_KEY';
|
||||
@@ -310,6 +313,13 @@ export class CodexAdapter implements TargetAdapter {
|
||||
const isPowerShellScript = isWindows && /\.ps1$/i.test(codexPath);
|
||||
const needsShell = isWindows && /\.(cmd|bat)$/i.test(codexPath);
|
||||
|
||||
const spawnStartedAt = Date.now();
|
||||
adapterLogger.stage('dispatch', 'target.spawn', 'Spawning Codex CLI child process', {
|
||||
target: 'codex',
|
||||
codexPath,
|
||||
argCount: args.length,
|
||||
});
|
||||
|
||||
let child: ChildProcess;
|
||||
if (isPowerShellScript) {
|
||||
child = spawn(
|
||||
@@ -329,6 +339,16 @@ export class CodexAdapter implements TargetAdapter {
|
||||
child = spawn(codexPath, args, { stdio: 'inherit', windowsHide: true, env: launchEnv });
|
||||
}
|
||||
|
||||
child.on('exit', (code, signal) => {
|
||||
adapterLogger.stage(
|
||||
'respond',
|
||||
'target.exit',
|
||||
'Codex CLI child process exited',
|
||||
{ target: 'codex', exitCode: code, signal },
|
||||
{ latencyMs: Date.now() - spawnStartedAt }
|
||||
);
|
||||
});
|
||||
|
||||
wireChildProcessSignals(child, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'EACCES') {
|
||||
console.error(`[X] Codex CLI is not executable: ${codexPath}`);
|
||||
|
||||
@@ -19,6 +19,9 @@ import {
|
||||
} from '../utils/shell-executor';
|
||||
import { wireChildProcessSignals } from '../utils/signal-forwarder';
|
||||
import { runCleanup } from '../errors';
|
||||
import { createLogger } from '../services/logging';
|
||||
|
||||
const adapterLogger = createLogger('targets:droid');
|
||||
|
||||
export class DroidAdapter implements TargetAdapter {
|
||||
readonly type: TargetType = 'droid';
|
||||
@@ -122,6 +125,13 @@ export class DroidAdapter implements TargetAdapter {
|
||||
const isPowerShellScript = isWindows && /\.ps1$/i.test(droidPath);
|
||||
const needsShell = isWindows && /\.(cmd|bat)$/i.test(droidPath);
|
||||
|
||||
const spawnStartedAt = Date.now();
|
||||
adapterLogger.stage('dispatch', 'target.spawn', 'Spawning Droid CLI child process', {
|
||||
target: 'droid',
|
||||
droidPath,
|
||||
argCount: args.length,
|
||||
});
|
||||
|
||||
let child: ChildProcess;
|
||||
if (isPowerShellScript) {
|
||||
child = spawn(
|
||||
@@ -149,6 +159,16 @@ export class DroidAdapter implements TargetAdapter {
|
||||
});
|
||||
}
|
||||
|
||||
child.on('exit', (code, signal) => {
|
||||
adapterLogger.stage(
|
||||
'respond',
|
||||
'target.exit',
|
||||
'Droid CLI child process exited',
|
||||
{ target: 'droid', exitCode: code, signal },
|
||||
{ latencyMs: Date.now() - spawnStartedAt }
|
||||
);
|
||||
});
|
||||
|
||||
wireChildProcessSignals(child, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'EACCES') {
|
||||
console.error(`[X] Droid CLI is not executable: ${droidPath}`);
|
||||
|
||||
Reference in New Issue
Block a user