Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bin/coding
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ set -e
AGENT=""
FORCE_AGENT=""
ARGS=()
SERVICE_ARGS=()
VERBOSE=false
CONFIG_FILE=""
PROJECT_DIR=""
Expand DownExpand Up@@ -105,6 +106,10 @@ while [[ $# -gt 0 ]]; do
--lsl-validate)
exec node "$SCRIPT_DIR/../tests/integration/full-system-validation.test.js"
;;
--no-vkb|--no-constraints|--no-transcript|--no-logging|--no-health)
SERVICE_ARGS+=("$1")
shift
;;
--help|-h)
show_help
exit 0
Expand DownExpand Up@@ -177,6 +182,8 @@ export CODING_AGENT="$AGENT"
export CODING_TOOLS_PATH="$SCRIPT_DIR/.."
export CODING_REPO="$SCRIPT_DIR/.."
export CODING_PROJECT_DIR="$PROJECT_DIR"
# Pass service args as a space-separated string
export SERVICE_ARGS_STR="${SERVICE_ARGS[*]}"

# Launch appropriate agent
case "$AGENT" in
Expand Down
6 changes: 5 additions & 1 deletion scripts/launch-claude.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,6 +58,9 @@ verify_monitoring_systems() {
}


# Reconstruct SERVICE_ARGS array from env var
SERVICE_ARGS=($SERVICE_ARGS_STR)

CopilotAINov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array reconstruction SERVICE_ARGS=($SERVICE_ARGS_STR) is missing quotes around the variable expansion. This can cause issues if SERVICE_ARGS_STR is empty or contains special characters. While the current use case (simple flag names) works, this is a bash anti-pattern.

Recommendation: Use read -ra SERVICE_ARGS <<< "$SERVICE_ARGS_STR" for safer array reconstruction, or quote the expansion as SERVICE_ARGS=("$SERVICE_ARGS_STR") if treating as a single element, or use proper array serialization if multiple elements with spaces are expected.

Suggested change
SERVICE_ARGS=($SERVICE_ARGS_STR)
read -ra SERVICE_ARGS<<<"$SERVICE_ARGS_STR"

Copilot uses AI. Check for mistakes.

# Use target project directory if specified, otherwise use coding repo
if [ -n "$CODING_PROJECT_DIR" ]; then
TARGET_PROJECT_DIR="$CODING_PROJECT_DIR"
Expand DownExpand Up@@ -136,7 +139,8 @@ if ! command -v node &> /dev/null; then
fi

# Start services using the simple startup script (from coding repo)
if ! "$CODING_REPO/start-services.sh"; then
# Forward any service-related arguments
if ! "$CODING_REPO/start-services.sh" "${SERVICE_ARGS[@]}"; then
log "Error: Failed to start services"
exit 1
fi
Expand Down
6 changes: 5 additions & 1 deletion scripts/launch-copilot.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,6 +111,9 @@ start_http_adapter() {
fi
}

# Reconstruct SERVICE_ARGS array from env var
SERVICE_ARGS=($SERVICE_ARGS_STR)

CopilotAINov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array reconstruction SERVICE_ARGS=($SERVICE_ARGS_STR) is missing quotes around the variable expansion. This can cause issues if SERVICE_ARGS_STR is empty or contains special characters. While the current use case (simple flag names) works, this is a bash anti-pattern.

Recommendation: Use read -ra SERVICE_ARGS <<< "$SERVICE_ARGS_STR" for safer array reconstruction, or quote the expansion as SERVICE_ARGS=("$SERVICE_ARGS_STR") if treating as a single element, or use proper array serialization if multiple elements with spaces are expected.

Suggested change
SERVICE_ARGS=($SERVICE_ARGS_STR)
read -ra SERVICE_ARGS<<<"$SERVICE_ARGS_STR"

Copilot uses AI. Check for mistakes.

# Use target project directory if specified, otherwise use coding repo
if [ -n "$CODING_PROJECT_DIR" ]; then
TARGET_PROJECT_DIR="$CODING_PROJECT_DIR"
Expand DownExpand Up@@ -148,7 +151,8 @@ if ! command -v node &> /dev/null; then
fi

# Start services using the simple startup script (from coding repo)
if ! "$CODING_REPO/start-services.sh"; then
# Forward any service-related arguments
if ! "$CODING_REPO/start-services.sh" "${SERVICE_ARGS[@]}"; then
log "Error: Failed to start services"
exit 1
fi
Expand Down
234 changes: 137 additions & 97 deletions scripts/start-services-robust.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,26 @@ const CODING_DIR = path.resolve(SCRIPT_DIR, '..');
const execAsync = promisify(exec);
const psm = new ProcessStateManager();

// Parse CLI arguments for service control
const args = process.argv.slice(2);
const SERVICE_FLAGS = {
skipVkb: args.includes('--no-vkb'),
skipConstraints: args.includes('--no-constraints'),
skipTranscript: args.includes('--no-transcript'),
skipLogging: args.includes('--no-logging'),
skipHealth: args.includes('--no-health')
};

if (Object.values(SERVICE_FLAGS).some(Boolean)) {
console.log('🔧 Service Configuration:');
if (SERVICE_FLAGS.skipVkb) console.log(' - VKB Server disabled (--no-vkb)');
if (SERVICE_FLAGS.skipConstraints) console.log(' - Constraint Monitor disabled (--no-constraints)');
if (SERVICE_FLAGS.skipTranscript) console.log(' - Transcript Monitor disabled (--no-transcript)');
if (SERVICE_FLAGS.skipLogging) console.log(' - Live Logging disabled (--no-logging)');
if (SERVICE_FLAGS.skipHealth) console.log(' - Health Monitoring disabled (--no-health)');
console.log('');
}

// Service configurations
const SERVICE_CONFIGS = {
transcriptMonitor: {
Expand DownExpand Up@@ -504,46 +524,54 @@ async function startAllServices() {
console.log('📋 Starting REQUIRED services (Live Logging System)...');
console.log('');

try {
const transcriptResult = await startServiceWithRetry(
SERVICE_CONFIGS.transcriptMonitor.name,
SERVICE_CONFIGS.transcriptMonitor.startFn,
SERVICE_CONFIGS.transcriptMonitor.healthCheckFn,
{
required: SERVICE_CONFIGS.transcriptMonitor.required,
maxRetries: SERVICE_CONFIGS.transcriptMonitor.maxRetries,
timeout: SERVICE_CONFIGS.transcriptMonitor.timeout
}
);
results.successful.push(transcriptResult);
await registerWithPSM(transcriptResult, 'scripts/enhanced-transcript-monitor.js');
} catch (error) {
results.failed.push({
serviceName: SERVICE_CONFIGS.transcriptMonitor.name,
error: error.message,
required: true
});
if (!SERVICE_FLAGS.skipTranscript) {
try {
const transcriptResult = await startServiceWithRetry(
SERVICE_CONFIGS.transcriptMonitor.name,
SERVICE_CONFIGS.transcriptMonitor.startFn,
SERVICE_CONFIGS.transcriptMonitor.healthCheckFn,
{
required: SERVICE_CONFIGS.transcriptMonitor.required,
maxRetries: SERVICE_CONFIGS.transcriptMonitor.maxRetries,
timeout: SERVICE_CONFIGS.transcriptMonitor.timeout
}
);
results.successful.push(transcriptResult);
await registerWithPSM(transcriptResult, 'scripts/enhanced-transcript-monitor.js');
} catch (error) {
results.failed.push({
serviceName: SERVICE_CONFIGS.transcriptMonitor.name,
error: error.message,
required: true
});
}
} else {
console.log('⏭️ Skipping Transcript Monitor (--no-transcript)');
}
Comment on lines +527 to 550

CopilotAINov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --no-transcript and --no-logging flags allow skipping services that are marked as REQUIRED in SERVICE_CONFIGS (lines 67 and 100). When these services fail to start, the code correctly blocks startup (lines 697-706), but when they're skipped via flags, they're not added to results.failed, allowing startup to proceed.

This creates an inconsistency: either these services should not be skippable, or they should not be marked as required. Based on the PR description saying these flags "provide flexibility," it seems the intent is to allow skipping them, but this contradicts their required status.

Recommendation: Either remove --no-transcript and --no-logging from the allowed flags, or change the service configs to mark these as optional when the corresponding skip flags are set.

Copilot uses AI. Check for mistakes.

try {
const coordinatorResult = await startServiceWithRetry(
SERVICE_CONFIGS.liveLoggingCoordinator.name,
SERVICE_CONFIGS.liveLoggingCoordinator.startFn,
SERVICE_CONFIGS.liveLoggingCoordinator.healthCheckFn,
{
required: SERVICE_CONFIGS.liveLoggingCoordinator.required,
maxRetries: SERVICE_CONFIGS.liveLoggingCoordinator.maxRetries,
timeout: SERVICE_CONFIGS.liveLoggingCoordinator.timeout
}
);
results.successful.push(coordinatorResult);
await registerWithPSM(coordinatorResult, 'scripts/live-logging-coordinator.js');
} catch (error) {
results.failed.push({
serviceName: SERVICE_CONFIGS.liveLoggingCoordinator.name,
error: error.message,
required: true
});
if (!SERVICE_FLAGS.skipLogging) {
try {
const coordinatorResult = await startServiceWithRetry(
SERVICE_CONFIGS.liveLoggingCoordinator.name,
SERVICE_CONFIGS.liveLoggingCoordinator.startFn,
SERVICE_CONFIGS.liveLoggingCoordinator.healthCheckFn,
{
required: SERVICE_CONFIGS.liveLoggingCoordinator.required,
maxRetries: SERVICE_CONFIGS.liveLoggingCoordinator.maxRetries,
timeout: SERVICE_CONFIGS.liveLoggingCoordinator.timeout
}
);
results.successful.push(coordinatorResult);
await registerWithPSM(coordinatorResult, 'scripts/live-logging-coordinator.js');
} catch (error) {
results.failed.push({
serviceName: SERVICE_CONFIGS.liveLoggingCoordinator.name,
error: error.message,
required: true
});
}
} else {
console.log('⏭️ Skipping Live Logging Coordinator (--no-logging)');

CopilotAINov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as with --no-transcript: the Live Logging Coordinator is marked as a REQUIRED service (line 100 in SERVICE_CONFIGS) but can be skipped with --no-logging. This bypasses the startup failure logic that would normally block if a required service fails to start.

See comment on lines 527-550 for the full explanation and recommendation.

Suggested change
console.log('⏭️ Skipping Live Logging Coordinator (--no-logging)');
if(SERVICE_CONFIGS.liveLoggingCoordinator.required){
console.error('❌ ERROR: Live Logging Coordinator is a REQUIRED service and cannot be skipped with --no-logging.');
process.exit(1);
}else{
console.log('⏭️ Skipping Live Logging Coordinator (--no-logging)');
}

Copilot uses AI. Check for mistakes.
}

console.log('');
Expand All@@ -552,85 +580,97 @@ async function startAllServices() {
console.log('🔵 Starting OPTIONAL services (graceful degradation enabled)...');
console.log('');

const vkbResult = await startServiceWithRetry(
SERVICE_CONFIGS.vkbServer.name,
SERVICE_CONFIGS.vkbServer.startFn,
SERVICE_CONFIGS.vkbServer.healthCheckFn,
{
required: SERVICE_CONFIGS.vkbServer.required,
maxRetries: SERVICE_CONFIGS.vkbServer.maxRetries,
timeout: SERVICE_CONFIGS.vkbServer.timeout
}
);
if (!SERVICE_FLAGS.skipVkb) {
const vkbResult = await startServiceWithRetry(
SERVICE_CONFIGS.vkbServer.name,
SERVICE_CONFIGS.vkbServer.startFn,
SERVICE_CONFIGS.vkbServer.healthCheckFn,
{
required: SERVICE_CONFIGS.vkbServer.required,
maxRetries: SERVICE_CONFIGS.vkbServer.maxRetries,
timeout: SERVICE_CONFIGS.vkbServer.timeout
}
);

if (vkbResult.status === 'success') {
results.successful.push(vkbResult);
await registerWithPSM(vkbResult, 'lib/vkb-server/cli.js');
if (vkbResult.status === 'success') {
results.successful.push(vkbResult);
await registerWithPSM(vkbResult, 'lib/vkb-server/cli.js');
} else {
results.degraded.push(vkbResult);
}
} else {
results.degraded.push(vkbResult);
console.log('⏭️ Skipping VKB Server (--no-vkb)');
}

console.log('');

// 3. OPTIONAL: Constraint Monitor
const constraintResult = await startServiceWithRetry(
SERVICE_CONFIGS.constraintMonitor.name,
SERVICE_CONFIGS.constraintMonitor.startFn,
SERVICE_CONFIGS.constraintMonitor.healthCheckFn,
{
required: SERVICE_CONFIGS.constraintMonitor.required,
maxRetries: SERVICE_CONFIGS.constraintMonitor.maxRetries,
timeout: SERVICE_CONFIGS.constraintMonitor.timeout
}
);
if (!SERVICE_FLAGS.skipConstraints) {
const constraintResult = await startServiceWithRetry(
SERVICE_CONFIGS.constraintMonitor.name,
SERVICE_CONFIGS.constraintMonitor.startFn,
SERVICE_CONFIGS.constraintMonitor.healthCheckFn,
{
required: SERVICE_CONFIGS.constraintMonitor.required,
maxRetries: SERVICE_CONFIGS.constraintMonitor.maxRetries,
timeout: SERVICE_CONFIGS.constraintMonitor.timeout
}
);

if (constraintResult.status === 'success') {
results.successful.push(constraintResult);
// No PSM registration for Docker-based service
if (constraintResult.status === 'success') {
results.successful.push(constraintResult);
// No PSM registration for Docker-based service
} else {
results.degraded.push(constraintResult);
}
} else {
results.degraded.push(constraintResult);
console.log('⏭️ Skipping Constraint Monitor (--no-constraints)');
}

console.log('');

// 4. OPTIONAL: Health Verifier
const healthVerifierResult = await startServiceWithRetry(
SERVICE_CONFIGS.healthVerifier.name,
SERVICE_CONFIGS.healthVerifier.startFn,
SERVICE_CONFIGS.healthVerifier.healthCheckFn,
{
required: SERVICE_CONFIGS.healthVerifier.required,
maxRetries: SERVICE_CONFIGS.healthVerifier.maxRetries,
timeout: SERVICE_CONFIGS.healthVerifier.timeout
if (!SERVICE_FLAGS.skipHealth) {
const healthVerifierResult = await startServiceWithRetry(
SERVICE_CONFIGS.healthVerifier.name,
SERVICE_CONFIGS.healthVerifier.startFn,
SERVICE_CONFIGS.healthVerifier.healthCheckFn,
{
required: SERVICE_CONFIGS.healthVerifier.required,
maxRetries: SERVICE_CONFIGS.healthVerifier.maxRetries,
timeout: SERVICE_CONFIGS.healthVerifier.timeout
}
);

if (healthVerifierResult.status === 'success') {
results.successful.push(healthVerifierResult);
await registerWithPSM(healthVerifierResult, 'scripts/health-verifier.js');
} else {
results.degraded.push(healthVerifierResult);
}
);

if (healthVerifierResult.status === 'success') {
results.successful.push(healthVerifierResult);
await registerWithPSM(healthVerifierResult, 'scripts/health-verifier.js');
} else {
results.degraded.push(healthVerifierResult);
}
console.log('');

console.log('');
// 5. OPTIONAL: StatusLine Health Monitor
const statuslineHealthResult = await startServiceWithRetry(
SERVICE_CONFIGS.statuslineHealthMonitor.name,
SERVICE_CONFIGS.statuslineHealthMonitor.startFn,
SERVICE_CONFIGS.statuslineHealthMonitor.healthCheckFn,
{
required: SERVICE_CONFIGS.statuslineHealthMonitor.required,
maxRetries: SERVICE_CONFIGS.statuslineHealthMonitor.maxRetries,
timeout: SERVICE_CONFIGS.statuslineHealthMonitor.timeout
}
);

// 5. OPTIONAL: StatusLine Health Monitor
const statuslineHealthResult = await startServiceWithRetry(
SERVICE_CONFIGS.statuslineHealthMonitor.name,
SERVICE_CONFIGS.statuslineHealthMonitor.startFn,
SERVICE_CONFIGS.statuslineHealthMonitor.healthCheckFn,
{
required: SERVICE_CONFIGS.statuslineHealthMonitor.required,
maxRetries: SERVICE_CONFIGS.statuslineHealthMonitor.maxRetries,
timeout: SERVICE_CONFIGS.statuslineHealthMonitor.timeout
if (statuslineHealthResult.status === 'success') {
results.successful.push(statuslineHealthResult);
await registerWithPSM(statuslineHealthResult, 'scripts/statusline-health-monitor.js');
} else {
results.degraded.push(statuslineHealthResult);
}
);

if (statuslineHealthResult.status === 'success') {
results.successful.push(statuslineHealthResult);
await registerWithPSM(statuslineHealthResult, 'scripts/statusline-health-monitor.js');
} else {
results.degraded.push(statuslineHealthResult);
console.log('⏭️ Skipping Health Monitoring (--no-health)');
}

console.log('');
Expand Down
2 changes: 1 addition & 1 deletion start-services.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,7 @@ if [ "$ROBUST_MODE" = "true" ]; then
echo ""

# Use the Node.js-based robust service starter
exec node "$SCRIPT_DIR/scripts/start-services-robust.js"
exec node "$SCRIPT_DIR/scripts/start-services-robust.js" "$@"
fi

# LEGACY MODE (kept for backward compatibility, disable with ROBUST_MODE=false)
Expand Down