@resolveio/server-lib 22.3.58 → 22.3.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -193,6 +193,26 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
193
193
|
' done',
|
|
194
194
|
' sleep 1',
|
|
195
195
|
'}',
|
|
196
|
+
'kill_env_marked_processes() {',
|
|
197
|
+
' [ -d /proc ] || return 0',
|
|
198
|
+
' local job_id="${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}"',
|
|
199
|
+
' local owner_id="${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}"',
|
|
200
|
+
' local token="${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}"',
|
|
201
|
+
' [ -n "$job_id$owner_id$token" ] || return 0',
|
|
202
|
+
' for env_file in /proc/[0-9]*/environ; do',
|
|
203
|
+
' pid="${env_file#/proc/}"',
|
|
204
|
+
' pid="${pid%/environ}"',
|
|
205
|
+
' skip_cleanup_pid "$pid" && continue',
|
|
206
|
+
' [ "$pid" = "$$" ] && continue',
|
|
207
|
+
' env_text="$(tr "\\0" "\\n" < "$env_file" 2>/dev/null || true)"',
|
|
208
|
+
' [ -n "$env_text" ] || continue',
|
|
209
|
+
' matched=0',
|
|
210
|
+
' [ -n "$job_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$" && matched=1',
|
|
211
|
+
' [ "$matched" = "0" ] && [ -n "$owner_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$" && matched=1',
|
|
212
|
+
' [ "$matched" = "0" ] && [ -n "$token" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$" && matched=1',
|
|
213
|
+
' [ "$matched" = "1" ] && kill_tree "$pid"',
|
|
214
|
+
' done',
|
|
215
|
+
'}',
|
|
196
216
|
'RUNNER_ANCESTOR_PIDS=" $$ ${PPID:-} "',
|
|
197
217
|
'ancestor_pid="${PPID:-}"',
|
|
198
218
|
'while [ -n "$ancestor_pid" ] && [ "$ancestor_pid" != "0" ] && [ "$ancestor_pid" != "1" ]; do',
|
|
@@ -212,6 +232,7 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
212
232
|
' done',
|
|
213
233
|
' for pass in 1 2 3; do',
|
|
214
234
|
' for port in "$CLIENT_PORT" "$SERVER_PORT" "$MONGO_PORT" "$INSPECT_PORT"; do kill_port_listeners "$port"; done',
|
|
235
|
+
' kill_env_marked_processes',
|
|
215
236
|
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng|esbuild|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
216
237
|
' skip_cleanup_pid "$pid" && continue',
|
|
217
238
|
' kill_tree "$pid"',
|
|
@@ -235,6 +256,20 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
235
256
|
' for port in "$CLIENT_PORT" "$SERVER_PORT" "$MONGO_PORT" "$INSPECT_PORT"; do',
|
|
236
257
|
' if command -v lsof >/dev/null 2>&1 && [ -n "$(janitor_bounded 3 lsof -ti tcp:"$port")" ]; then found=1; fi',
|
|
237
258
|
' done',
|
|
259
|
+
' if [ -d /proc ]; then',
|
|
260
|
+
' job_id="${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}"',
|
|
261
|
+
' owner_id="${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}"',
|
|
262
|
+
' token="${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}"',
|
|
263
|
+
' for env_file in /proc/[0-9]*/environ; do',
|
|
264
|
+
' pid="${env_file#/proc/}"',
|
|
265
|
+
' pid="${pid%/environ}"',
|
|
266
|
+
' skip_cleanup_pid "$pid" && continue',
|
|
267
|
+
' env_text="$(tr "\\0" "\\n" < "$env_file" 2>/dev/null || true)"',
|
|
268
|
+
' [ -n "$job_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$" && found=1',
|
|
269
|
+
' [ -n "$owner_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$" && found=1',
|
|
270
|
+
' [ -n "$token" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$" && found=1',
|
|
271
|
+
' done',
|
|
272
|
+
' fi',
|
|
238
273
|
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng|esbuild|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
239
274
|
' skip_cleanup_pid "$pid" && continue',
|
|
240
275
|
' found=1',
|
|
@@ -253,13 +288,13 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
253
288
|
' cleanup_project_processes',
|
|
254
289
|
' janitor_update_manifest_status cleanup_complete',
|
|
255
290
|
' janitor_write_cleanup_status complete 0',
|
|
256
|
-
'
|
|
291
|
+
' rm -rf "$LOCK_DIR" >/dev/null 2>&1 || true',
|
|
257
292
|
'}',
|
|
258
293
|
'trap cleanup EXIT',
|
|
259
294
|
'detach_keepalive() {',
|
|
260
295
|
' janitor_update_manifest_status ready_keepalive',
|
|
261
296
|
' janitor_write_cleanup_status ready_keepalive 0',
|
|
262
|
-
'
|
|
297
|
+
' rm -rf "$LOCK_DIR" >/dev/null 2>&1 || true',
|
|
263
298
|
' trap - EXIT',
|
|
264
299
|
' disown >/dev/null 2>&1 || true',
|
|
265
300
|
' echo "ResolveIO AI runner QA keepalive detached at $CLIENT_URL; stop with $TOOLS_DIR/stop-local-qa.sh $PROJECT_ROOT."',
|
|
@@ -359,7 +394,7 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
359
394
|
' fi',
|
|
360
395
|
' echo "ResolveIO AI runner QA lock is stale for $PROJECT_ROOT; cleaning scoped local QA processes before retrying." | tee "$ARTIFACT_DIR/runner.log"',
|
|
361
396
|
' cleanup_project_processes',
|
|
362
|
-
'
|
|
397
|
+
' rm -rf "$LOCK_DIR" >/dev/null 2>&1 || true',
|
|
363
398
|
' if ! mkdir "$LOCK_DIR" 2>/dev/null; then',
|
|
364
399
|
' echo "ResolveIO AI runner QA lock is still held for $PROJECT_ROOT after cleanup. Stop the existing QA runner before starting another." | tee -a "$ARTIFACT_DIR/runner.log"',
|
|
365
400
|
' exit 6',
|
|
@@ -469,6 +504,26 @@ function buildResolveIORunnerLocalQaStopperScript() {
|
|
|
469
504
|
' done',
|
|
470
505
|
' sleep 1',
|
|
471
506
|
'}',
|
|
507
|
+
'kill_env_marked_processes() {',
|
|
508
|
+
' [ -d /proc ] || return 0',
|
|
509
|
+
' local job_id="${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}"',
|
|
510
|
+
' local owner_id="${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}"',
|
|
511
|
+
' local token="${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}"',
|
|
512
|
+
' [ -n "$job_id$owner_id$token" ] || return 0',
|
|
513
|
+
' for env_file in /proc/[0-9]*/environ; do',
|
|
514
|
+
' pid="${env_file#/proc/}"',
|
|
515
|
+
' pid="${pid%/environ}"',
|
|
516
|
+
' skip_cleanup_pid "$pid" && continue',
|
|
517
|
+
' [ "$pid" = "$$" ] && continue',
|
|
518
|
+
' env_text="$(tr "\\0" "\\n" < "$env_file" 2>/dev/null || true)"',
|
|
519
|
+
' [ -n "$env_text" ] || continue',
|
|
520
|
+
' matched=0',
|
|
521
|
+
' [ -n "$job_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$" && matched=1',
|
|
522
|
+
' [ "$matched" = "0" ] && [ -n "$owner_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$" && matched=1',
|
|
523
|
+
' [ "$matched" = "0" ] && [ -n "$token" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$" && matched=1',
|
|
524
|
+
' [ "$matched" = "1" ] && kill_tree "$pid" && killed_count=$((killed_count + 1))',
|
|
525
|
+
' done',
|
|
526
|
+
'}',
|
|
472
527
|
'RUNNER_ANCESTOR_PIDS=" $$ ${PPID:-} "',
|
|
473
528
|
'ancestor_pid="${PPID:-}"',
|
|
474
529
|
'while [ -n "$ancestor_pid" ] && [ "$ancestor_pid" != "0" ] && [ "$ancestor_pid" != "1" ]; do',
|
|
@@ -490,6 +545,7 @@ function buildResolveIORunnerLocalQaStopperScript() {
|
|
|
490
545
|
'done',
|
|
491
546
|
'for pass in 1 2 3; do',
|
|
492
547
|
' for port in "$CLIENT_PORT" "$SERVER_PORT" "$MONGO_PORT" "$INSPECT_PORT"; do kill_port_listeners "$port"; done',
|
|
548
|
+
' kill_env_marked_processes',
|
|
493
549
|
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng|esbuild|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
494
550
|
' skip_cleanup_pid "$pid" && continue',
|
|
495
551
|
' kill_tree "$pid"',
|
|
@@ -514,6 +570,20 @@ function buildResolveIORunnerLocalQaStopperScript() {
|
|
|
514
570
|
' for port in "$CLIENT_PORT" "$SERVER_PORT" "$MONGO_PORT" "$INSPECT_PORT"; do',
|
|
515
571
|
' if command -v lsof >/dev/null 2>&1 && [ -n "$(janitor_bounded 3 lsof -ti tcp:"$port")" ]; then found=1; fi',
|
|
516
572
|
' done',
|
|
573
|
+
' if [ -d /proc ]; then',
|
|
574
|
+
' job_id="${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}"',
|
|
575
|
+
' owner_id="${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}"',
|
|
576
|
+
' token="${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}"',
|
|
577
|
+
' for env_file in /proc/[0-9]*/environ; do',
|
|
578
|
+
' pid="${env_file#/proc/}"',
|
|
579
|
+
' pid="${pid%/environ}"',
|
|
580
|
+
' skip_cleanup_pid "$pid" && continue',
|
|
581
|
+
' env_text="$(tr "\\0" "\\n" < "$env_file" 2>/dev/null || true)"',
|
|
582
|
+
' [ -n "$job_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$" && found=1',
|
|
583
|
+
' [ -n "$owner_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$" && found=1',
|
|
584
|
+
' [ -n "$token" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$" && found=1',
|
|
585
|
+
' done',
|
|
586
|
+
' fi',
|
|
517
587
|
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng|esbuild|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
518
588
|
' skip_cleanup_pid "$pid" && continue',
|
|
519
589
|
' found=1',
|
|
@@ -521,7 +591,7 @@ function buildResolveIORunnerLocalQaStopperScript() {
|
|
|
521
591
|
' [ "$found" = "0" ] && break',
|
|
522
592
|
' sleep 2',
|
|
523
593
|
'done',
|
|
524
|
-
'
|
|
594
|
+
'rm -rf "$ARTIFACT_DIR/.qa.lock" >/dev/null 2>&1 || true',
|
|
525
595
|
'janitor_update_manifest_status stopped',
|
|
526
596
|
'janitor_write_cleanup_status stopped "$killed_count"',
|
|
527
597
|
'echo "ResolveIO AI runner QA local app stopped for $PROJECT_ROOT"',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/util/ai-runner-qa-tools.ts"],"names":[],"mappings":"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","file":"ai-runner-qa-tools.js","sourcesContent":["import { buildRunnerProcessJanitorShellLibrary } from './runner-process-janitor';\n\nexport interface ResolveIORunnerQaToolBundleOptions {\n\tmode?: 'support' | 'runner';\n\tqaClientPort?: number | string;\n\tdefaultUsername?: string;\n\tdefaultPassword?: string;\n\tjobId?: string;\n\townerId?: string;\n\trunnerToken?: string;\n\ttoolsBinPath?: string;\n\tbrowserslistPath?: string;\n\tmongodbBinaryCachePath?: string;\n\ttmpRoot?: string;\n\thomeRoot?: string;\n}\n\nfunction shellDoubleQuote(value: string): string {\n\treturn String(value || '').replace(/[\"\\\\$`]/g, '\\\\$&');\n}\n\nfunction normalizePort(value: number | string | undefined, fallback: string): string {\n\tconst parsed = Number.parseInt(String(value || ''), 10);\n\treturn Number.isFinite(parsed) && parsed > 0 ? String(parsed) : fallback;\n}\n\nfunction envVar(mode: 'support' | 'runner', suffix: string): string {\n\treturn mode === 'support' ? `RESOLVEIO_SUPPORT_QA_${suffix}` : `RESOLVEIO_RUNNER_QA_${suffix}`;\n}\n\nexport function buildResolveIORunnerQaEnvScript(options: ResolveIORunnerQaToolBundleOptions = {}): string {\n\tconst mode = options.mode || 'runner';\n\tconst altMode = mode === 'support' ? 'runner' : 'support';\n\tconst tmpRoot = options.tmpRoot || (mode === 'support' ? '/tmp/resolveio-support-qa' : '/tmp/resolveio-ai-runner-qa');\n\tconst homeRoot = options.homeRoot || `${tmpRoot}/home`;\n\tconst defaultPort = normalizePort(options.qaClientPort, '4200');\n\tconst username = shellDoubleQuote(options.defaultUsername || 'admin');\n\tconst password = shellDoubleQuote(options.defaultPassword || '');\n\tconst jobId = shellDoubleQuote(options.jobId || '');\n\tconst ownerId = shellDoubleQuote(options.ownerId || '');\n\tconst runnerToken = shellDoubleQuote(options.runnerToken || '');\n\tconst toolsBinPath = options.toolsBinPath || '';\n\tconst browserslistPath = options.browserslistPath || '';\n\tconst mongodbBinaryCachePath = options.mongodbBinaryCachePath || '';\n\tconst clientPortVar = envVar(mode, 'CLIENT_PORT');\n\tconst altClientPortVar = envVar(altMode, 'CLIENT_PORT');\n\tconst clientUrlVar = envVar(mode, 'CLIENT_URL');\n\tconst altClientUrlVar = envVar(altMode, 'CLIENT_URL');\n\tconst serverUrlVar = envVar(mode, 'SERVER_URL');\n\tconst altServerUrlVar = envVar(altMode, 'SERVER_URL');\n\tconst usernameVar = envVar(mode, 'USERNAME');\n\tconst altUsernameVar = envVar(altMode, 'USERNAME');\n\tconst passwordVar = envVar(mode, 'PASSWORD');\n\tconst altPasswordVar = envVar(altMode, 'PASSWORD');\n\tconst viewportWidthVar = envVar(mode, 'VIEWPORT_WIDTH');\n\tconst altViewportWidthVar = envVar(altMode, 'VIEWPORT_WIDTH');\n\tconst viewportHeightVar = envVar(mode, 'VIEWPORT_HEIGHT');\n\tconst altViewportHeightVar = envVar(altMode, 'VIEWPORT_HEIGHT');\n\tconst timeoutVar = envVar(mode, 'ANGULAR_STARTUP_TIMEOUT_SECONDS');\n\tconst altTimeoutVar = envVar(altMode, 'ANGULAR_STARTUP_TIMEOUT_SECONDS');\n\tconst prebundleVar = envVar(mode, 'ANGULAR_PREBUNDLE');\n\tconst altPrebundleVar = envVar(altMode, 'ANGULAR_PREBUNDLE');\n\tconst reuseVar = envVar(mode, 'REUSE_RUNNING');\n\tconst altReuseVar = envVar(altMode, 'REUSE_RUNNING');\n\tconst keepaliveVar = envVar(mode, 'KEEPALIVE');\n\tconst altKeepaliveVar = envVar(altMode, 'KEEPALIVE');\n\tconst browserLoopVar = mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_BROWSER' : 'RESOLVEIO_RUNNER_QA_BROWSER';\n\treturn [\n\t\t`export ${mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_TMP' : 'RESOLVEIO_RUNNER_QA_TMP'}=\"${'${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_TMP' : 'RESOLVEIO_RUNNER_QA_TMP') + ':-' + tmpRoot + '}'}\"`,\n\t\t`export ${mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME'}=\"${'${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME') + ':-' + homeRoot + '}'}\"`,\n\t\t`RESOLVEIO_QA_TMP=\"${'${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_TMP' : 'RESOLVEIO_RUNNER_QA_TMP') + '}'}\"`,\n\t\t`RESOLVEIO_QA_HOME=\"${'${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME') + '}'}\"`,\n\t\t'mkdir -p \"$RESOLVEIO_QA_HOME/.nvm\" \"$RESOLVEIO_QA_TMP/npm-cache\" \"$RESOLVEIO_QA_TMP/mongodb-binaries\" \"$RESOLVEIO_QA_TMP/mongodb-memory-server-core\"',\n\t\t'if [ ! -f \"$RESOLVEIO_QA_HOME/.nvm/nvm.sh\" ]; then',\n\t\t' cat > \"$RESOLVEIO_QA_HOME/.nvm/nvm.sh\" <<\\'RESOLVEIO_NVM_SHIM\\'',\n\t\t'nvm() {',\n\t\t' case \"$1\" in',\n\t\t' use|install|alias) return 0 ;;',\n\t\t' current) node -v 2>/dev/null || true; return 0 ;;',\n\t\t' *) return 0 ;;',\n\t\t' esac',\n\t\t'}',\n\t\t'RESOLVEIO_NVM_SHIM',\n\t\t'fi',\n\t\t'export HOME=\"$RESOLVEIO_QA_HOME\"',\n\t\t'export NVM_DIR=\"$RESOLVEIO_QA_HOME/.nvm\"',\n\t\ttoolsBinPath ? `export PATH=\"${toolsBinPath}:$PATH\"` : '',\n\t\t'export NODE_ENV=development',\n\t\t'# Local QA must start the application HTTP server even when launched from a dedicated support-manager worker.',\n\t\t'export IS_WORKERS_ENABLED=false',\n\t\t'export IS_WORKER_INSTANCE=false',\n\t\t'export DISABLE_WORKER_SERVER_CONNECTION=false',\n\t\t'export SUPPORT_CODEX_MANAGER_PROCESS_ONLY=false',\n\t\t'export SUPPORT_AUTO_MANAGER_PROCESS_ONLY=false',\n\t\t'export AI_ASSISTANT_CODEX_MANAGER_PROCESS_ONLY=false',\n\t\tbrowserslistPath ? `export BROWSERSLIST_CONFIG=\"${browserslistPath}\"` : '',\n\t\t`export ${clientPortVar}=\"${'${' + clientPortVar + ':-${' + altClientPortVar + ':-' + defaultPort + '}}'}\"`,\n\t\t`export ${altClientPortVar}=\"${'${' + altClientPortVar + ':-${' + clientPortVar + '}}'}\"`,\n\t\t`export ${clientUrlVar}=\"${'${' + clientUrlVar + ':-${' + altClientUrlVar + ':-http://localhost:${' + clientPortVar + '}}}'}\"`,\n\t\t`export ${altClientUrlVar}=\"${'${' + altClientUrlVar + ':-${' + clientUrlVar + '}}'}\"`,\n\t\t`export ${serverUrlVar}=\"${'${' + serverUrlVar + ':-${' + altServerUrlVar + ':-http://localhost:8080}}'}\"`,\n\t\t`export ${altServerUrlVar}=\"${'${' + altServerUrlVar + ':-${' + serverUrlVar + '}}'}\"`,\n\t\t'export ADDITIONAL_ALLOWED_ORIGINS=\"${ADDITIONAL_ALLOWED_ORIGINS:-$' + clientUrlVar + '}\"',\n\t\t`export ${viewportWidthVar}=\"${'${' + viewportWidthVar + ':-${' + altViewportWidthVar + ':-1920}}'}\"`,\n\t\t`export ${altViewportWidthVar}=\"${'${' + altViewportWidthVar + ':-${' + viewportWidthVar + '}}'}\"`,\n\t\t`export ${viewportHeightVar}=\"${'${' + viewportHeightVar + ':-${' + altViewportHeightVar + ':-1080}}'}\"`,\n\t\t`export ${altViewportHeightVar}=\"${'${' + altViewportHeightVar + ':-${' + viewportHeightVar + '}}'}\"`,\n\t\t`export ${timeoutVar}=\"${'${' + timeoutVar + ':-${' + altTimeoutVar + ':-900}}'}\"`,\n\t\t`export ${altTimeoutVar}=\"${'${' + altTimeoutVar + ':-${' + timeoutVar + '}}'}\"`,\n\t\t`export ${prebundleVar}=\"${'${' + prebundleVar + ':-${' + altPrebundleVar + ':-false}}'}\"`,\n\t\t`export ${altPrebundleVar}=\"${'${' + altPrebundleVar + ':-${' + prebundleVar + '}}'}\"`,\n\t\t`export ${reuseVar}=\"${'${' + reuseVar + ':-${' + altReuseVar + ':-true}}'}\"`,\n\t\t`export ${altReuseVar}=\"${'${' + altReuseVar + ':-${' + reuseVar + '}}'}\"`,\n\t\t`export ${keepaliveVar}=\"${'${' + keepaliveVar + ':-${' + altKeepaliveVar + ':-false}}'}\"`,\n\t\t`export ${altKeepaliveVar}=\"${'${' + altKeepaliveVar + ':-${' + keepaliveVar + '}}'}\"`,\n\t\t`export ${usernameVar}=\"${'${' + usernameVar + ':-${' + altUsernameVar + ':-' + username + '}}'}\"`,\n\t\t`export ${altUsernameVar}=\"${'${' + altUsernameVar + ':-${' + usernameVar + '}}'}\"`,\n\t\t`export ${passwordVar}=\"${'${' + passwordVar + ':-${' + altPasswordVar + ':-' + password + '}}'}\"`,\n\t\t`export ${altPasswordVar}=\"${'${' + altPasswordVar + ':-${' + passwordVar + '}}'}\"`,\n\t\t`export ${envVar(mode, 'JOB_ID')}=\"${'${' + envVar(mode, 'JOB_ID') + ':-${' + envVar(altMode, 'JOB_ID') + ':-' + jobId + '}}'}\"`,\n\t\t`export ${envVar(altMode, 'JOB_ID')}=\"${'${' + envVar(altMode, 'JOB_ID') + ':-${' + envVar(mode, 'JOB_ID') + '}}'}\"`,\n\t\t`export ${envVar(mode, 'OWNER_ID')}=\"${'${' + envVar(mode, 'OWNER_ID') + ':-${' + envVar(altMode, 'OWNER_ID') + ':-' + ownerId + '}}'}\"`,\n\t\t`export ${envVar(altMode, 'OWNER_ID')}=\"${'${' + envVar(altMode, 'OWNER_ID') + ':-${' + envVar(mode, 'OWNER_ID') + '}}'}\"`,\n\t\t`export ${envVar(mode, 'RUNNER_TOKEN')}=\"${'${' + envVar(mode, 'RUNNER_TOKEN') + ':-${' + envVar(altMode, 'RUNNER_TOKEN') + ':-' + runnerToken + '}}'}\"`,\n\t\t`export ${envVar(altMode, 'RUNNER_TOKEN')}=\"${'${' + envVar(altMode, 'RUNNER_TOKEN') + ':-${' + envVar(mode, 'RUNNER_TOKEN') + '}}'}\"`,\n\t\t'export PUPPETEER_CACHE_DIR=\"${PUPPETEER_CACHE_DIR:-/var/lib/resolveio/puppeteer}\"',\n\t\t'if [ ! -d \"$PUPPETEER_CACHE_DIR\" ] || [ ! -w \"$PUPPETEER_CACHE_DIR\" ]; then',\n\t\t' export PUPPETEER_CACHE_DIR=\"$RESOLVEIO_QA_TMP/puppeteer\"',\n\t\t' mkdir -p \"$PUPPETEER_CACHE_DIR\"',\n\t\t'fi',\n\t\t`for ${browserLoopVar} in \"$PUPPETEER_CACHE_DIR\"/chrome-headless-shell/linux-*/chrome-headless-shell-linux64/chrome-headless-shell \"$PUPPETEER_CACHE_DIR\"/chrome/linux-*/chrome-linux64/chrome; do`,\n\t\t` if [ -x \"$${browserLoopVar}\" ]; then`,\n\t\t` export PUPPETEER_EXECUTABLE_PATH=\"$${browserLoopVar}\"`,\n\t\t` export CHROME_BIN=\"$${browserLoopVar}\"`,\n\t\t' break',\n\t\t' fi',\n\t\t'done',\n\t\t`unset ${browserLoopVar}`,\n\t\t'export NPM_CONFIG_CACHE=\"$RESOLVEIO_QA_TMP/npm-cache\"',\n\t\t'export NPM_CONFIG_PREFER_OFFLINE=true',\n\t\t'export NPM_CONFIG_PRODUCTION=false',\n\t\t'export npm_config_production=false',\n\t\t'export RESOLVEIO_SUPPORT_MONGOMS_PACKAGE_ROOT=\"$RESOLVEIO_QA_TMP/mongodb-memory-server-core\"',\n\t\t'export MONGOMS_DOWNLOAD_DIR=\"$RESOLVEIO_QA_TMP/mongodb-binaries\"',\n\t\tmongodbBinaryCachePath ? `export RESOLVEIO_SUPPORT_SHARED_MONGOMS_DOWNLOAD_DIR=\"${mongodbBinaryCachePath}\"` : '',\n\t\t'export MONGOMS_VERSION=\"${MONGOMS_VERSION:-7.0.14}\"',\n\t\t''\n\t].filter((line) => line !== '').join('\\n');\n}\n\nexport function buildResolveIORunnerLocalQaScript(): string {\n\treturn [\n\t\t'#!/usr/bin/env bash',\n\t\t'set -u',\n\t\t'TOOLS_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"',\n\t\t'source \"$TOOLS_DIR/env.sh\"',\n\t\t'PROJECT_ROOT=\"${1:-$(pwd)}\"',\n\t\t'PROJECT_ROOT=\"$(cd \"$PROJECT_ROOT\" && pwd)\"',\n\t\t'ARTIFACT_DIR=\"$PROJECT_ROOT/qa-artifacts\"',\n\t\t'mkdir -p \"$ARTIFACT_DIR\"',\n\t\t'CLIENT_URL=\"${RESOLVEIO_RUNNER_QA_CLIENT_URL:-${RESOLVEIO_SUPPORT_QA_CLIENT_URL:-http://localhost:${RESOLVEIO_RUNNER_QA_CLIENT_PORT:-${RESOLVEIO_SUPPORT_QA_CLIENT_PORT:-4200}}}}\"',\n\t\t'SERVER_URL=\"${RESOLVEIO_RUNNER_QA_SERVER_URL:-${RESOLVEIO_SUPPORT_QA_SERVER_URL:-http://localhost:8080}}\"',\n\t\t'CLIENT_PORT=\"${RESOLVEIO_RUNNER_QA_CLIENT_PORT:-${RESOLVEIO_SUPPORT_QA_CLIENT_PORT:-4200}}\"',\n\t\t'SERVER_PORT=\"${RESOLVEIO_RUNNER_QA_SERVER_PORT:-${RESOLVEIO_SUPPORT_QA_SERVER_PORT:-8080}}\"',\n\t\t'MONGO_PORT=\"${RESOLVEIO_RUNNER_QA_MONGO_PORT:-${RESOLVEIO_SUPPORT_QA_MONGO_PORT:-3001}}\"',\n\t\t'INSPECT_PORT=\"${RESOLVEIO_RUNNER_QA_INSPECT_PORT:-${RESOLVEIO_SUPPORT_QA_INSPECT_PORT:-9229}}\"',\n\t\t'STARTUP_TIMEOUT=\"${RESOLVEIO_RUNNER_QA_ANGULAR_STARTUP_TIMEOUT_SECONDS:-${RESOLVEIO_SUPPORT_QA_ANGULAR_STARTUP_TIMEOUT_SECONDS:-900}}\"',\n\t\t'ANGULAR_PREBUNDLE=\"${RESOLVEIO_RUNNER_QA_ANGULAR_PREBUNDLE:-${RESOLVEIO_SUPPORT_QA_ANGULAR_PREBUNDLE:-false}}\"',\n\t\t'REUSE_RUNNING=\"${RESOLVEIO_RUNNER_QA_REUSE_RUNNING:-${RESOLVEIO_SUPPORT_QA_REUSE_RUNNING:-true}}\"',\n\t\t'if [[ \"$(basename \"$TOOLS_DIR\")\" == *support* ]]; then',\n\t\t' KEEPALIVE=\"${RESOLVEIO_SUPPORT_QA_KEEPALIVE:-${RESOLVEIO_RUNNER_QA_KEEPALIVE:-false}}\"',\n\t\t'else',\n\t\t' KEEPALIVE=\"${RESOLVEIO_RUNNER_QA_KEEPALIVE:-${RESOLVEIO_SUPPORT_QA_KEEPALIVE:-false}}\"',\n\t\t'fi',\n\t\t'SERVER_STABLE_SECONDS=\"${RESOLVEIO_RUNNER_QA_SERVER_STABLE_SECONDS:-${RESOLVEIO_SUPPORT_QA_SERVER_STABLE_SECONDS:-20}}\"',\n\t\t'LOCK_DIR=\"$ARTIFACT_DIR/.qa.lock\"',\n\t\t'SERVER_PID=\"\"',\n\t\t'CLIENT_PID=\"\"',\n\t\t'SERVER_REQUIRED=0',\n\t\t'RUNNER_REUSED_READY=0',\n\t\t'ANGULAR_PREBUNDLE_ARGS=()',\n\t\t'REPO_ROOT=\"$(git -C \"$PROJECT_ROOT\" rev-parse --show-toplevel 2>/dev/null || echo \"$PROJECT_ROOT\")\"',\n\t\tbuildRunnerProcessJanitorShellLibrary({ mode: 'support' }),\n\t\t'case \"${ANGULAR_PREBUNDLE,,}\" in',\n\t\t' false|0|no|off) ANGULAR_PREBUNDLE_ARGS=(--prebundle=false) ;;',\n\t\t'esac',\n\t\t'kill_tree() {',\n\t\t' local pid=\"$1\"',\n\t\t' [ -n \"$pid\" ] || return 0',\n\t\t' for child in $(pgrep -P \"$pid\" 2>/dev/null || true); do kill_tree \"$child\"; done',\n\t\t' kill \"$pid\" >/dev/null 2>&1 || true',\n\t\t' sleep 1',\n\t\t' kill -0 \"$pid\" >/dev/null 2>&1 && kill -9 \"$pid\" >/dev/null 2>&1 || true',\n\t\t'}',\n\t\t'kill_port_listeners() {',\n\t\t' local port=\"$1\"',\n\t\t' [ -n \"$port\" ] || return 0',\n\t\t' local pids=\"\"',\n\t\t' if command -v lsof >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 lsof -ti tcp:\"$port\")\"; fi',\n\t\t' if command -v fuser >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 fuser -n tcp \"$port\")\"; fi',\n\t\t' if command -v ss >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 ss -ltnp \"sport = :$port\" | sed -n \\'s/.*pid=\\\\([0-9][0-9]*\\\\).*/\\\\1/p\\' || true)\"; fi',\n\t\t' for pid in $pids; do',\n\t\t' [ \"$pid\" = \"$$\" ] && continue',\n\t\t' kill_tree \"$pid\"',\n\t\t' done',\n\t\t' sleep 1',\n\t\t'}',\n\t\t'RUNNER_ANCESTOR_PIDS=\" $$ ${PPID:-} \"',\n\t\t'ancestor_pid=\"${PPID:-}\"',\n\t\t'while [ -n \"$ancestor_pid\" ] && [ \"$ancestor_pid\" != \"0\" ] && [ \"$ancestor_pid\" != \"1\" ]; do',\n\t\t' ancestor_pid=\"$(ps -o ppid= -p \"$ancestor_pid\" 2>/dev/null | tr -d \" \" || true)\"',\n\t\t' [ -n \"$ancestor_pid\" ] && RUNNER_ANCESTOR_PIDS=\"$RUNNER_ANCESTOR_PIDS $ancestor_pid \"',\n\t\t'done',\n\t\t'skip_cleanup_pid() {',\n\t\t' case \"$RUNNER_ANCESTOR_PIDS\" in *\" $1 \"*) return 0 ;; esac',\n\t\t' return 1',\n\t\t'}',\n\t\t'cleanup_project_processes() {',\n\t\t' for pid_file in \"$ARTIFACT_DIR/server.pid\" \"$ARTIFACT_DIR/client.pid\"; do',\n\t\t' [ -f \"$pid_file\" ] || continue',\n\t\t' pid=\"$(cat \"$pid_file\" 2>/dev/null || true)\"',\n\t\t' kill_tree \"$pid\"',\n\t\t' rm -f \"$pid_file\"',\n\t\t' done',\n\t\t' for pass in 1 2 3; do',\n\t\t' for port in \"$CLIENT_PORT\" \"$SERVER_PORT\" \"$MONGO_PORT\" \"$INSPECT_PORT\"; do kill_port_listeners \"$port\"; done',\n\t\t' for pid in $(ps -eo pid=,args= | awk -v root=\"$PROJECT_ROOT\" \\'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\\\.sh|stop-local-qa\\\\.sh|bugfix-comparison-qa\\\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\\\/\\\\.bin\\\\/ng|esbuild|npm run client|start_client\\\\.sh|npm run server|start_server\\\\.sh|nodemon|node .*tmp\\\\/index\\\\.js|mongod|mongodb-binaries\\\\/mongod)/ {print $1}\\' 2>/dev/null || true); do',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' kill_tree \"$pid\"',\n\t\t' done',\n\t\t' if [ -d /proc ]; then',\n\t\t' for proc_cwd in /proc/[0-9]*/cwd; do',\n\t\t' pid=\"${proc_cwd#/proc/}\"',\n\t\t' pid=\"${pid%/cwd}\"',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' cwd=\"$(readlink -f \"$proc_cwd\" 2>/dev/null || true)\"',\n\t\t' case \"$cwd\" in',\n\t\t' \"$PROJECT_ROOT\"|\"$PROJECT_ROOT\"/*) kill_tree \"$pid\" ;;',\n\t\t' esac',\n\t\t' done',\n\t\t' fi',\n\t\t' sleep 1',\n\t\t' done',\n\t\t' local wait_until=$((SECONDS + 60))',\n\t\t' while [ \"$SECONDS\" -lt \"$wait_until\" ]; do',\n\t\t' local found=0',\n\t\t' for port in \"$CLIENT_PORT\" \"$SERVER_PORT\" \"$MONGO_PORT\" \"$INSPECT_PORT\"; do',\n\t\t' if command -v lsof >/dev/null 2>&1 && [ -n \"$(janitor_bounded 3 lsof -ti tcp:\"$port\")\" ]; then found=1; fi',\n\t\t' done',\n\t\t' for pid in $(ps -eo pid=,args= | awk -v root=\"$PROJECT_ROOT\" \\'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\\\.sh|stop-local-qa\\\\.sh|bugfix-comparison-qa\\\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\\\/\\\\.bin\\\\/ng|esbuild|npm run client|start_client\\\\.sh|npm run server|start_server\\\\.sh|nodemon|node .*tmp\\\\/index\\\\.js|mongod|mongodb-binaries\\\\/mongod)/ {print $1}\\' 2>/dev/null || true); do',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' found=1',\n\t\t' done',\n\t\t' [ \"$found\" = \"0\" ] && break',\n\t\t' sleep 2',\n\t\t' done',\n\t\t'}',\n\t\t'cleanup() {',\n\t\t' [ \"${RUNNER_REUSED_READY:-0}\" = \"1\" ] && return 0',\n\t\t' janitor_stop_heartbeat',\n\t\t' rm -f \"$ARTIFACT_DIR/heartbeat.pid\"',\n\t\t' janitor_update_manifest_status cleanup_started',\n\t\t' kill_tree \"$SERVER_PID\"',\n\t\t' kill_tree \"$CLIENT_PID\"',\n\t\t' cleanup_project_processes',\n\t\t' janitor_update_manifest_status cleanup_complete',\n\t\t' janitor_write_cleanup_status complete 0',\n\t\t' rmdir \"$LOCK_DIR\" >/dev/null 2>&1 || true',\n\t\t'}',\n\t\t'trap cleanup EXIT',\n\t\t'detach_keepalive() {',\n\t\t' janitor_update_manifest_status ready_keepalive',\n\t\t' janitor_write_cleanup_status ready_keepalive 0',\n\t\t' rmdir \"$LOCK_DIR\" >/dev/null 2>&1 || true',\n\t\t' trap - EXIT',\n\t\t' disown >/dev/null 2>&1 || true',\n\t\t' echo \"ResolveIO AI runner QA keepalive detached at $CLIENT_URL; stop with $TOOLS_DIR/stop-local-qa.sh $PROJECT_ROOT.\"',\n\t\t' exit 0',\n\t\t'}',\n\t\t'probe_url() { node - \"$1\" <<\\'RESOLVEIO_PROBE\\'',\n\t\t'const http = require(\"http\");',\n\t\t'const https = require(\"https\");',\n\t\t'const url = process.argv[2];',\n\t\t'const mod = /^https:/i.test(url) ? https : http;',\n\t\t'const req = mod.get(url, { timeout: 2500 }, (res) => { res.resume(); process.exit(res.statusCode && res.statusCode < 500 ? 0 : 1); });',\n\t\t'req.on(\"timeout\", () => req.destroy(new Error(\"timeout\")));',\n\t\t'req.on(\"error\", () => process.exit(1));',\n\t\t'RESOLVEIO_PROBE',\n\t\t'}',\n\t\t'truthy() { case \"${1,,}\" in true|1|yes|on) return 0 ;; *) return 1 ;; esac; }',\n\t\t'reuse_running_app_if_ready() {',\n\t\t' truthy \"$REUSE_RUNNING\" || return 1',\n\t\t' [ -d \"$PROJECT_ROOT/server\" ] && SERVER_REQUIRED=1 || SERVER_REQUIRED=0',\n\t\t' probe_url \"$CLIENT_URL\" || return 1',\n\t\t' if [ \"$SERVER_REQUIRED\" = \"1\" ] && ! probe_url \"$SERVER_URL\"; then return 1; fi',\n\t\t' echo \"ResolveIO AI runner QA reusing already-ready local app at $CLIENT_URL\"',\n\t\t' return 0',\n\t\t'}',\n\t\t'log_has_fatal() {',\n\t\t' local file=\"$1\"',\n\t\t' [ -f \"$file\" ] || return 1',\n\t\t' grep -Eiq \"Unhandled Rejection|Cannot read properties of undefined|TypeError|ReferenceError|EADDRINUSE|app crashed|Failed to compile|Error: Cannot find module|NG[0-9]{4}|TS[0-9]{4}\" \"$file\"',\n\t\t'}',\n\t\t'prepare_angular_cache_dirs() {',\n\t\t' [ -d \"$PROJECT_ROOT\" ] || return 0',\n\t\t' mkdir -p \"$PROJECT_ROOT/.angular/cache\" 2>/dev/null || true',\n\t\t' local version=\"\"',\n\t\t' for pkg in \"$PROJECT_ROOT/node_modules/@angular/build/package.json\" \"$PROJECT_ROOT/node_modules/@angular/cli/package.json\" \"$PROJECT_ROOT/node_modules/@angular-devkit/build-angular/package.json\"; do',\n\t\t' if [ -f \"$pkg\" ]; then',\n\t\t' version=\"$(node -e \"try{console.log(require(process.argv[1]).version||\\\\\\\"\\\\\\\")}catch(e){}\" \"$pkg\" 2>/dev/null | head -1)\"',\n\t\t' [ -n \"$version\" ] && break',\n\t\t' fi',\n\t\t' done',\n\t\t' [ -n \"$version\" ] || return 0',\n\t\t' node - \"$PROJECT_ROOT/angular.json\" \"$(basename \"$PROJECT_ROOT\")\" <<\\'RESOLVEIO_ANGULAR_CACHE_PROJECTS\\' | while IFS= read -r project; do',\n\t\t'const fs = require(\"fs\");',\n\t\t'const path = process.argv[2];',\n\t\t'const fallback = process.argv[3];',\n\t\t'const names = new Set([fallback].filter(Boolean));',\n\t\t'try {',\n\t\t' const parsed = JSON.parse(fs.readFileSync(path, \"utf8\"));',\n\t\t' if (parsed.defaultProject) names.add(String(parsed.defaultProject));',\n\t\t' if (parsed.projects && typeof parsed.projects === \"object\") Object.keys(parsed.projects).forEach((name) => names.add(String(name)));',\n\t\t'} catch (error) {}',\n\t\t'for (const name of names) console.log(name.replace(/[^A-Za-z0-9._-]/g, \"_\"));',\n\t\t'RESOLVEIO_ANGULAR_CACHE_PROJECTS',\n\t\t' [ -n \"$project\" ] || continue',\n\t\t' mkdir -p \"$PROJECT_ROOT/.angular/cache/$version/$project/vite\" 2>/dev/null || true',\n\t\t' done',\n\t\t'}',\n\t\t'server_has_started() {',\n\t\t' local file=\"$ARTIFACT_DIR/server.log\"',\n\t\t' [ -f \"$file\" ] || return 1',\n\t\t' grep -Eiq \"nodemon.*starting|node .*tmp/index\\\\.js|Running as Worker|Standalone Node Reaper|listening on|server listening|Server listening|app listening|App listening|Finished .default.\" \"$file\"',\n\t\t'}',\n\t\t'wait_for_server_ready() {',\n\t\t' [ \"$SERVER_REQUIRED\" = \"1\" ] || return 0',\n\t\t' local end=$((SECONDS + STARTUP_TIMEOUT))',\n\t\t' while [ \"$SECONDS\" -lt \"$end\" ]; do',\n\t\t' if log_has_fatal \"$ARTIFACT_DIR/server.log\"; then return 4; fi',\n\t\t' if server_has_started; then',\n\t\t' local stable_until=$((SECONDS + SERVER_STABLE_SECONDS))',\n\t\t' while [ \"$SECONDS\" -lt \"$stable_until\" ]; do if log_has_fatal \"$ARTIFACT_DIR/server.log\"; then return 4; fi; sleep 2; done',\n\t\t' return 0',\n\t\t' fi',\n\t\t' if probe_url \"$SERVER_URL\"; then',\n\t\t' local stable_until=$((SECONDS + SERVER_STABLE_SECONDS))',\n\t\t' while [ \"$SECONDS\" -lt \"$stable_until\" ]; do if log_has_fatal \"$ARTIFACT_DIR/server.log\"; then return 4; fi; sleep 2; done',\n\t\t' return 0',\n\t\t' fi',\n\t\t' sleep 5',\n\t\t' done',\n\t\t' return 4',\n\t\t'}',\n\t\t'wait_for_client() {',\n\t\t' local end=$((SECONDS + STARTUP_TIMEOUT))',\n\t\t' while [ \"$SECONDS\" -lt \"$end\" ]; do',\n\t\t' if [ -n \"$CLIENT_PID\" ] && ! kill -0 \"$CLIENT_PID\" >/dev/null 2>&1; then return 2; fi',\n\t\t' if log_has_fatal \"$ARTIFACT_DIR/client.log\"; then return 3; fi',\n\t\t' if log_has_fatal \"$ARTIFACT_DIR/server.log\"; then return 4; fi',\n\t\t' if probe_url \"$CLIENT_URL\"; then wait_for_server_ready || return $?; return 0; fi',\n\t\t' sleep 5',\n\t\t' done',\n\t\t' return 1',\n\t\t'}',\n\t\t'if reuse_running_app_if_ready; then RUNNER_REUSED_READY=1; exit 0; fi',\n\t\t'if ! mkdir \"$LOCK_DIR\" 2>/dev/null; then',\n\t\t' if janitor_lock_is_live; then',\n\t\t' echo \"ResolveIO AI runner QA lock is already held by a live runner for $PROJECT_ROOT; refusing duplicate startup.\" | tee \"$ARTIFACT_DIR/runner.log\"',\n\t\t' exit 6',\n\t\t' fi',\n\t\t' echo \"ResolveIO AI runner QA lock is stale for $PROJECT_ROOT; cleaning scoped local QA processes before retrying.\" | tee \"$ARTIFACT_DIR/runner.log\"',\n\t\t' cleanup_project_processes',\n\t\t' rmdir \"$LOCK_DIR\" >/dev/null 2>&1 || true',\n\t\t' if ! mkdir \"$LOCK_DIR\" 2>/dev/null; then',\n\t\t' echo \"ResolveIO AI runner QA lock is still held for $PROJECT_ROOT after cleanup. Stop the existing QA runner before starting another.\" | tee -a \"$ARTIFACT_DIR/runner.log\"',\n\t\t' exit 6',\n\t\t' fi',\n\t\t'fi',\n\t\t'janitor_write_lock',\n\t\t'cleanup_project_processes',\n\t\t': > \"$ARTIFACT_DIR/server.log\"',\n\t\t': > \"$ARTIFACT_DIR/client.log\"',\n\t\t': > \"$ARTIFACT_DIR/runner.log\"',\n\t\t'janitor_write_manifest',\n\t\t'janitor_start_heartbeat',\n\t\t'echo \"$RUNNER_JANITOR_HEARTBEAT_PID\" > \"$ARTIFACT_DIR/heartbeat.pid\"',\n\t\t'janitor_check_resources || exit $?',\n\t\t'if [ -d \"$PROJECT_ROOT/server\" ]; then',\n\t\t' SERVER_REQUIRED=1',\n\t\t' if node -e \"const p=require(process.argv[1]); process.exit(p.scripts&&p.scripts.server?0:1)\" \"$PROJECT_ROOT/server/package.json\" >/dev/null 2>&1; then',\n\t\t' (cd \"$PROJECT_ROOT/server\" && source \"$TOOLS_DIR/env.sh\" && npm run server 2>&1 | tee \"$ARTIFACT_DIR/server.log\") &',\n\t\t' elif [ -x \"$PROJECT_ROOT/server/start_server.sh\" ]; then',\n\t\t' (cd \"$PROJECT_ROOT/server\" && source \"$TOOLS_DIR/env.sh\" && ./start_server.sh 2>&1 | tee \"$ARTIFACT_DIR/server.log\") &',\n\t\t' else',\n\t\t' echo \"ResolveIO AI runner QA cannot find npm server script or start_server.sh for $PROJECT_ROOT/server\" | tee \"$ARTIFACT_DIR/server.log\"',\n\t\t' exit 5',\n\t\t' fi',\n\t\t' SERVER_PID=$!',\n\t\t' wait_for_server_ready',\n\t\t' SERVER_RESULT=$?',\n\t\t' if [ \"$SERVER_RESULT\" != \"0\" ]; then echo \"ResolveIO AI runner QA server startup fatal error. See $ARTIFACT_DIR/server.log\"; exit \"$SERVER_RESULT\"; fi',\n\t\t'fi',\n\t\t'CLIENT_HOST=\"${RESOLVEIO_RUNNER_QA_CLIENT_HOST:-${RESOLVEIO_SUPPORT_QA_CLIENT_HOST:-0.0.0.0}}\"',\n\t\t'if [ -x \"$PROJECT_ROOT/node_modules/.bin/ng\" ]; then',\n\t\t' prepare_angular_cache_dirs',\n\t\t' (cd \"$PROJECT_ROOT\" && source \"$TOOLS_DIR/env.sh\" && node --max_old_space_size=8048 ./node_modules/.bin/ng serve --watch --configuration local --host \"$CLIENT_HOST\" --port \"$CLIENT_PORT\" \"${ANGULAR_PREBUNDLE_ARGS[@]}\" 2>&1 | tee \"$ARTIFACT_DIR/client.log\") &',\n\t\t'elif [ -x \"$PROJECT_ROOT/start_client.sh\" ]; then',\n\t\t' (cd \"$PROJECT_ROOT\" && source \"$TOOLS_DIR/env.sh\" && ./start_client.sh 2>&1 | tee \"$ARTIFACT_DIR/client.log\") &',\n\t\t'elif node -e \"const p=require(process.argv[1]); process.exit(p.scripts&&p.scripts.client?0:1)\" \"$PROJECT_ROOT/package.json\" >/dev/null 2>&1; then',\n\t\t' (cd \"$PROJECT_ROOT\" && source \"$TOOLS_DIR/env.sh\" && npm run client 2>&1 | tee \"$ARTIFACT_DIR/client.log\") &',\n\t\t'else',\n\t\t' echo \"ResolveIO AI runner QA cannot find Angular CLI, start_client.sh, or npm client script for $PROJECT_ROOT\" | tee \"$ARTIFACT_DIR/client.log\"',\n\t\t' exit 5',\n\t\t'fi',\n\t\t'CLIENT_PID=$!',\n\t\t'wait_for_client',\n\t\t'RESULT=$?',\n\t\t'case \"$RESULT\" in',\n\t\t' 0)',\n\t\t' echo \"ResolveIO AI runner QA local app ready at $CLIENT_URL\";',\n\t\t' echo \"$SERVER_PID\" > \"$ARTIFACT_DIR/server.pid\";',\n\t\t' echo \"$CLIENT_PID\" > \"$ARTIFACT_DIR/client.pid\";',\n\t\t' if truthy \"$KEEPALIVE\"; then detach_keepalive; fi;',\n\t\t' exit 0',\n\t\t' ;;',\n\t\t' 2) echo \"ResolveIO AI runner QA client process exited before $CLIENT_URL became ready. See $ARTIFACT_DIR/client.log\"; exit 2 ;;',\n\t\t' 3) echo \"ResolveIO AI runner QA client startup fatal error. See $ARTIFACT_DIR/client.log\"; exit 3 ;;',\n\t\t' 4) echo \"ResolveIO AI runner QA server startup fatal error. See $ARTIFACT_DIR/server.log\"; exit 4 ;;',\n\t\t' *) echo \"ResolveIO AI runner QA local app did not become ready at $CLIENT_URL within ${STARTUP_TIMEOUT}s. See $ARTIFACT_DIR/client.log and $ARTIFACT_DIR/server.log\"; exit 1 ;;',\n\t\t'esac',\n\t\t''\n\t].join('\\n');\n}\n\nexport function buildResolveIORunnerLocalQaStopperScript(): string {\n\treturn [\n\t\t'#!/usr/bin/env bash',\n\t\t'set -u',\n\t\t'TOOLS_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"',\n\t\t'source \"$TOOLS_DIR/env.sh\"',\n\t\t'PROJECT_ROOT=\"${1:-$(pwd)}\"',\n\t\t'PROJECT_ROOT=\"$(cd \"$PROJECT_ROOT\" && pwd)\"',\n\t\t'ARTIFACT_DIR=\"$PROJECT_ROOT/qa-artifacts\"',\n\t\t'CLIENT_PORT=\"${RESOLVEIO_RUNNER_QA_CLIENT_PORT:-${RESOLVEIO_SUPPORT_QA_CLIENT_PORT:-4200}}\"',\n\t\t'SERVER_PORT=\"${RESOLVEIO_RUNNER_QA_SERVER_PORT:-${RESOLVEIO_SUPPORT_QA_SERVER_PORT:-8080}}\"',\n\t\t'MONGO_PORT=\"${RESOLVEIO_RUNNER_QA_MONGO_PORT:-${RESOLVEIO_SUPPORT_QA_MONGO_PORT:-3001}}\"',\n\t\t'INSPECT_PORT=\"${RESOLVEIO_RUNNER_QA_INSPECT_PORT:-${RESOLVEIO_SUPPORT_QA_INSPECT_PORT:-9229}}\"',\n\t\t'REPO_ROOT=\"$(git -C \"$PROJECT_ROOT\" rev-parse --show-toplevel 2>/dev/null || echo \"$PROJECT_ROOT\")\"',\n\t\tbuildRunnerProcessJanitorShellLibrary({ mode: 'support' }),\n\t\t'STOP_LOCK_DIR=\"$ARTIFACT_DIR/.qa.stop.lock\"',\n\t\t'if ! mkdir \"$STOP_LOCK_DIR\" 2>/dev/null; then',\n\t\t' lock_pid=\"$(cat \"$STOP_LOCK_DIR/pid\" 2>/dev/null || true)\"',\n\t\t' if [ -n \"$lock_pid\" ] && kill -0 \"$lock_pid\" >/dev/null 2>&1; then',\n\t\t' echo \"ResolveIO AI runner QA cleanup already active for $PROJECT_ROOT; refusing duplicate stopper.\"',\n\t\t' exit 0',\n\t\t' fi',\n\t\t' rm -rf \"$STOP_LOCK_DIR\" >/dev/null 2>&1 || true',\n\t\t' mkdir \"$STOP_LOCK_DIR\" 2>/dev/null || { echo \"ResolveIO AI runner QA cleanup lock still active for $PROJECT_ROOT.\"; exit 0; }',\n\t\t'fi',\n\t\t'echo \"$$\" > \"$STOP_LOCK_DIR/pid\" 2>/dev/null || true',\n\t\t'trap \\'rm -rf \"$STOP_LOCK_DIR\" >/dev/null 2>&1 || true\\' EXIT',\n\t\t'kill_tree() {',\n\t\t' local pid=\"$1\"',\n\t\t' [ -n \"$pid\" ] || return 0',\n\t\t' kill -0 \"$pid\" >/dev/null 2>&1 || return 0',\n\t\t' for child in $(pgrep -P \"$pid\" 2>/dev/null || true); do kill_tree \"$child\"; done',\n\t\t' kill \"$pid\" >/dev/null 2>&1 || true',\n\t\t' sleep 1',\n\t\t' kill -0 \"$pid\" >/dev/null 2>&1 && kill -9 \"$pid\" >/dev/null 2>&1 || true',\n\t\t'}',\n\t\t'kill_port_listeners() {',\n\t\t' local port=\"$1\"',\n\t\t' [ -n \"$port\" ] || return 0',\n\t\t' local pids=\"\"',\n\t\t' if command -v lsof >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 lsof -ti tcp:\"$port\")\"; fi',\n\t\t' if command -v fuser >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 fuser -n tcp \"$port\")\"; fi',\n\t\t' if command -v ss >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 ss -ltnp \"sport = :$port\" | sed -n \\'s/.*pid=\\\\([0-9][0-9]*\\\\).*/\\\\1/p\\' || true)\"; fi',\n\t\t' for pid in $pids; do',\n\t\t' [ \"$pid\" = \"$$\" ] && continue',\n\t\t' kill_tree \"$pid\"',\n\t\t' done',\n\t\t' sleep 1',\n\t\t'}',\n\t\t'RUNNER_ANCESTOR_PIDS=\" $$ ${PPID:-} \"',\n\t\t'ancestor_pid=\"${PPID:-}\"',\n\t\t'while [ -n \"$ancestor_pid\" ] && [ \"$ancestor_pid\" != \"0\" ] && [ \"$ancestor_pid\" != \"1\" ]; do',\n\t\t' ancestor_pid=\"$(ps -o ppid= -p \"$ancestor_pid\" 2>/dev/null | tr -d \" \" || true)\"',\n\t\t' [ -n \"$ancestor_pid\" ] && RUNNER_ANCESTOR_PIDS=\"$RUNNER_ANCESTOR_PIDS $ancestor_pid \"',\n\t\t'done',\n\t\t'skip_cleanup_pid() {',\n\t\t' case \"$RUNNER_ANCESTOR_PIDS\" in *\" $1 \"*) return 0 ;; esac',\n\t\t' return 1',\n\t\t'}',\n\t\t'janitor_update_manifest_status cleanup_started',\n\t\t'killed_count=0',\n'for pid_file in \"$ARTIFACT_DIR/heartbeat.pid\" \"$ARTIFACT_DIR/server.pid\" \"$ARTIFACT_DIR/client.pid\"; do',\n\t\t' [ -f \"$pid_file\" ] || continue',\n\t\t' pid=\"$(cat \"$pid_file\" 2>/dev/null || true)\"',\n\t\t' kill_tree \"$pid\"',\n\t\t' killed_count=$((killed_count + 1))',\n\t\t' rm -f \"$pid_file\"',\n\t\t'done',\n\t\t'for pass in 1 2 3; do',\n\t\t' for port in \"$CLIENT_PORT\" \"$SERVER_PORT\" \"$MONGO_PORT\" \"$INSPECT_PORT\"; do kill_port_listeners \"$port\"; done',\n\t\t' for pid in $(ps -eo pid=,args= | awk -v root=\"$PROJECT_ROOT\" \\'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\\\.sh|stop-local-qa\\\\.sh|bugfix-comparison-qa\\\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\\\/\\\\.bin\\\\/ng|esbuild|npm run client|start_client\\\\.sh|npm run server|start_server\\\\.sh|nodemon|node .*tmp\\\\/index\\\\.js|mongod|mongodb-binaries\\\\/mongod)/ {print $1}\\' 2>/dev/null || true); do',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' kill_tree \"$pid\"',\n\t\t' killed_count=$((killed_count + 1))',\n\t\t' done',\n\t\t' if [ -d /proc ]; then',\n\t\t' for proc_cwd in /proc/[0-9]*/cwd; do',\n\t\t' pid=\"${proc_cwd#/proc/}\"',\n\t\t' pid=\"${pid%/cwd}\"',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' cwd=\"$(readlink -f \"$proc_cwd\" 2>/dev/null || true)\"',\n\t\t' case \"$cwd\" in',\n\t\t' \"$PROJECT_ROOT\"|\"$PROJECT_ROOT\"/*) kill_tree \"$pid\" ;;',\n\t\t' esac',\n\t\t' done',\n\t\t' fi',\n\t\t' sleep 1',\n\t\t'done',\n\t\t'wait_until=$((SECONDS + 60))',\n\t'while [ \"$SECONDS\" -lt \"$wait_until\" ]; do',\n\t\t' found=0',\n\t\t' for port in \"$CLIENT_PORT\" \"$SERVER_PORT\" \"$MONGO_PORT\" \"$INSPECT_PORT\"; do',\n\t\t' if command -v lsof >/dev/null 2>&1 && [ -n \"$(janitor_bounded 3 lsof -ti tcp:\"$port\")\" ]; then found=1; fi',\n\t\t' done',\n\t\t' for pid in $(ps -eo pid=,args= | awk -v root=\"$PROJECT_ROOT\" \\'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\\\.sh|stop-local-qa\\\\.sh|bugfix-comparison-qa\\\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\\\/\\\\.bin\\\\/ng|esbuild|npm run client|start_client\\\\.sh|npm run server|start_server\\\\.sh|nodemon|node .*tmp\\\\/index\\\\.js|mongod|mongodb-binaries\\\\/mongod)/ {print $1}\\' 2>/dev/null || true); do',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' found=1',\n\t\t' done',\n\t\t' [ \"$found\" = \"0\" ] && break',\n\t\t' sleep 2',\n\t\t'done',\n\t\t'rmdir \"$ARTIFACT_DIR/.qa.lock\" >/dev/null 2>&1 || true',\n\t\t'janitor_update_manifest_status stopped',\n\t\t'janitor_write_cleanup_status stopped \"$killed_count\"',\n\t\t'echo \"ResolveIO AI runner QA local app stopped for $PROJECT_ROOT\"',\n\t\t''\n\t].join('\\n');\n}\n\nexport function buildResolveIORunnerBugfixComparisonQaScript(): string {\n\treturn [\n\t\t'#!/usr/bin/env bash',\n\t\t'set -u',\n\t\t'TOOLS_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"',\n\t\t'source \"$TOOLS_DIR/env.sh\"',\n\t\t'PROJECT_ROOT=\"${1:-}\"',\n\t\t'if [ -z \"$PROJECT_ROOT\" ]; then',\n\t\t' echo \"Usage: $0 <project-root> [baseline-ref] -- <qa-command...>\" >&2',\n\t\t' exit 2',\n\t\t'fi',\n\t\t'shift || true',\n\t\t'DEFAULT_BASELINE_REF=\"${RESOLVEIO_RUNNER_QA_BASELINE_REF:-${RESOLVEIO_SUPPORT_QA_BASELINE_REF:-origin/master}}\"',\n\t\t'if [ \"${1:-}\" = \"--\" ]; then',\n\t\t' BASELINE_REF=\"$DEFAULT_BASELINE_REF\"',\n\t\t'else',\n\t\t' BASELINE_REF=\"${1:-$DEFAULT_BASELINE_REF}\"',\n\t\t' if [ \"$#\" -gt 0 ]; then shift || true; fi',\n\t\t'fi',\n\t\t'if [ \"${1:-}\" = \"--\" ]; then shift; fi',\n\t\t'if [ \"$#\" -eq 0 ]; then',\n\t\t' echo \"Usage: $0 <project-root> [baseline-ref] -- <qa-command...>\" >&2',\n\t\t' exit 2',\n\t\t'fi',\n\t\t'PROJECT_ROOT=\"$(cd \"$PROJECT_ROOT\" && pwd)\"',\n\t\t'REPO_ROOT=\"$(git -C \"$PROJECT_ROOT\" rev-parse --show-toplevel)\"',\n\t\t'PROJECT_REL=\"$(git -C \"$REPO_ROOT\" ls-files --full-name \"$PROJECT_ROOT\" 2>/dev/null | head -1 | xargs dirname 2>/dev/null || true)\"',\n\t\t'if [ -z \"$PROJECT_REL\" ] || [ \"$PROJECT_REL\" = \".\" ]; then',\n\t\t' PROJECT_REL=\"$(node - \"$REPO_ROOT\" \"$PROJECT_ROOT\" <<\\'RESOLVEIO_REL\\'',\n\t\t'const path = require(\"path\");',\n\t\t'console.log(path.relative(process.argv[2], process.argv[3]) || \".\");',\n\t\t'RESOLVEIO_REL',\n\t\t' )\"',\n\t\t'fi',\n\t\t'ARTIFACT_DIR=\"$PROJECT_ROOT/qa-artifacts\"',\n\t\t'mkdir -p \"$ARTIFACT_DIR/baseline\" \"$ARTIFACT_DIR/candidate\"',\n\t\t'RESULT_JSON=\"$ARTIFACT_DIR/bugfix-comparison-result.json\"',\n\t\t'CANDIDATE_PATCH=\"$ARTIFACT_DIR/bugfix-candidate.patch\"',\n\t\t'ORIGINAL_HEAD=\"$(git -C \"$REPO_ROOT\" rev-parse HEAD)\"',\n\t\t'ORIGINAL_BRANCH=\"$(git -C \"$REPO_ROOT\" symbolic-ref --short HEAD 2>/dev/null || true)\"',\n\t\t'QA_COMMAND=(\"$@\")',\n\t\t'STOPPER=\"$TOOLS_DIR/stop-local-qa.sh\"',\n\t\t'CURRENT_PHASE=\"\"',\n\t\t'stop_local_qa() {',\n\t\t' \"$STOPPER\" \"$PROJECT_ROOT\" >/dev/null 2>&1 || true',\n\t\t'}',\n\t\t'write_json() {',\n\t\t' node - \"$RESULT_JSON\" \"$@\" <<\\'RESOLVEIO_WRITE_JSON\\'',\n\t\t'const fs = require(\"fs\");',\n\t\t'const [path, status, baselineExit, candidateExit, baselineRef, originalHead, projectRel, note] = process.argv.slice(2);',\n\t\t'const payload = {',\n\t\t' status,',\n\t\t' baseline_ref: baselineRef,',\n\t\t' original_head: originalHead,',\n\t\t' project: projectRel,',\n\t\t' baseline_exit_code: Number(baselineExit),',\n\t\t' candidate_exit_code: Number(candidateExit),',\n\t\t' code_switch_proven: Number(baselineExit) !== 0 && Number(candidateExit) === 0,',\n\t\t' candidate_passed: Number(candidateExit) === 0,',\n\t\t' baseline_failed: Number(baselineExit) !== 0,',\n\t\t' note,',\n\t\t' artifact_dirs: { baseline: \"qa-artifacts/baseline\", candidate: \"qa-artifacts/candidate\" },',\n\t\t' created_at: new Date().toISOString()',\n\t\t'};',\n\t\t'fs.writeFileSync(path, JSON.stringify(payload, null, 2));',\n\t\t'console.log(JSON.stringify(payload, null, 2));',\n\t\t'RESOLVEIO_WRITE_JSON',\n\t\t'}',\n\t\t'snapshot_phase_artifacts() {',\n\t\t' local phase=\"$1\"',\n\t\t' mkdir -p \"$ARTIFACT_DIR/$phase\"',\n\t\t' find \"$ARTIFACT_DIR\" -maxdepth 1 -type f \\\\( -name \"*.png\" -o -name \"*.jpg\" -o -name \"*.jpeg\" -o -name \"*.webp\" -o -name \"*.json\" -o -name \"*.txt\" -o -name \"*.log\" -o -name \"*.zip\" \\\\) -print0 2>/dev/null | while IFS= read -r -d \"\" file; do',\n\t\t' cp \"$file\" \"$ARTIFACT_DIR/$phase/$(basename \"$file\")\" 2>/dev/null || true',\n\t\t' done',\n\t\t'}',\n\t\t'capture_candidate_patch() {',\n\t\t' : > \"$CANDIDATE_PATCH\"',\n\t\t' git -C \"$REPO_ROOT\" diff --binary -- \"$PROJECT_REL\" >> \"$CANDIDATE_PATCH\" || true',\n\t\t' git -C \"$REPO_ROOT\" diff --binary --cached -- \"$PROJECT_REL\" >> \"$CANDIDATE_PATCH\" || true',\n\t\t'}',\n\t\t'checkout_project_from_ref() {',\n\t\t' local ref=\"$1\"',\n\t\t' git -C \"$REPO_ROOT\" checkout \"$ref\" -- \"$PROJECT_REL\"',\n\t\t'}',\n\t\t'restore_candidate() {',\n\t\t' stop_local_qa',\n\t\t' checkout_project_from_ref \"$ORIGINAL_HEAD\" || true',\n\t\t' if [ -s \"$CANDIDATE_PATCH\" ]; then',\n\t\t' git -C \"$REPO_ROOT\" apply --whitespace=nowarn \"$CANDIDATE_PATCH\" || true',\n\t\t' fi',\n\t\t'}',\n\t\t'trap restore_candidate EXIT',\n\t\t'run_phase() {',\n\t\t' local phase=\"$1\"',\n\t\t' CURRENT_PHASE=\"$phase\"',\n\t\t' stop_local_qa',\n\t\t' export RESOLVEIO_RUNNER_QA_PHASE=\"$phase\"',\n\t\t' export RESOLVEIO_SUPPORT_QA_PHASE=\"$phase\"',\n\t\t' export RESOLVEIO_RUNNER_QA_ARTIFACT_DIR=\"$ARTIFACT_DIR/$phase\"',\n\t\t' export RESOLVEIO_SUPPORT_QA_ARTIFACT_DIR=\"$ARTIFACT_DIR/$phase\"',\n\t\t' mkdir -p \"$ARTIFACT_DIR/$phase\"',\n\t\t' find \"$ARTIFACT_DIR/$phase\" -maxdepth 1 -type f \\\\( -name \"*.png\" -o -name \"*.jpg\" -o -name \"*.jpeg\" -o -name \"*.webp\" -o -name \"*.json\" -o -name \"*.txt\" -o -name \"*.log\" -o -name \"*.zip\" \\\\) -delete 2>/dev/null || true',\n\t\t' find \"$ARTIFACT_DIR\" -maxdepth 1 -type f \\\\( -name \"*.png\" -o -name \"*.jpg\" -o -name \"*.jpeg\" -o -name \"*.webp\" -o -name \"*proof.json\" -o -name \"auth-bootstrap-result.json\" \\\\) -delete 2>/dev/null || true',\n\t\t' set +e',\n\t\t' (cd \"$REPO_ROOT\" && \"${QA_COMMAND[@]}\") 2>&1 | tee \"$ARTIFACT_DIR/$phase/qa-command.log\"',\n\t\t' local rc=\"${PIPESTATUS[0]}\"',\n\t\t' set +e',\n\t\t' snapshot_phase_artifacts \"$phase\"',\n\t\t' stop_local_qa',\n\t\t' return \"$rc\"',\n\t\t'}',\n\t\t'echo \"ResolveIO bugfix comparison QA preserving candidate diff for $PROJECT_REL\"',\n\t\t'capture_candidate_patch',\n\t\t'stop_local_qa',\n\t\t'echo \"ResolveIO bugfix comparison QA baseline phase: $BASELINE_REF\"',\n\t\t'checkout_project_from_ref \"$BASELINE_REF\"',\n\t\t'run_phase baseline',\n\t\t'BASELINE_EXIT=\"$?\"',\n\t\t'echo \"ResolveIO bugfix comparison QA candidate phase: restored workspace candidate\"',\n\t\t'restore_candidate',\n\t\t'run_phase candidate',\n\t\t'CANDIDATE_EXIT=\"$?\"',\n\t\t'if [ \"$CANDIDATE_EXIT\" = \"0\" ] && [ \"$BASELINE_EXIT\" != \"0\" ]; then',\n\t\t' STATUS=\"pass\"',\n\t\t' NOTE=\"Baseline failed and candidate passed for the same QA command.\"',\n\t\t'elif [ \"$CANDIDATE_EXIT\" = \"0\" ]; then',\n\t\t' STATUS=\"inconclusive_baseline_also_passed\"',\n\t\t' NOTE=\"Candidate passed, but baseline also passed; the code switch did not prove the bug fix.\"',\n\t\t'else',\n\t\t' STATUS=\"fail_candidate_failed\"',\n\t\t' NOTE=\"Candidate failed the QA command.\"',\n\t\t'fi',\n\t\t'write_json \"$STATUS\" \"$BASELINE_EXIT\" \"$CANDIDATE_EXIT\" \"$BASELINE_REF\" \"$ORIGINAL_HEAD\" \"$PROJECT_REL\" \"$NOTE\"',\n\t\t'[ \"$CANDIDATE_EXIT\" = \"0\" ] || exit \"$CANDIDATE_EXIT\"',\n\t\t'[ \"$STATUS\" = \"pass\" ] || exit 8',\n\t\t''\n\t].join('\\n');\n}\n\nexport function buildResolveIORunnerQaToolsReadme(options: ResolveIORunnerQaToolBundleOptions = {}): string {\n\tconst mode = options.mode || 'runner';\n\tconst toolsDir = mode === 'support' ? '.resolveio-support-tools' : '.resolveio-ai-runner-tools';\n\tconst port = normalizePort(options.qaClientPort, '4200');\n\tconst clientUrlVar = envVar(mode, 'CLIENT_URL');\n\tconst keepaliveVar = envVar(mode, 'KEEPALIVE');\n\tconst usernameVar = envVar(mode, 'USERNAME');\n\tconst passwordVar = envVar(mode, 'PASSWORD');\n\treturn [\n\t\t`# ResolveIO ${mode === 'support' ? 'Support' : 'AI Runner'} Local QA Tools`,\n\t\t'',\n\t\t'These scripts are generated by `@resolveio/server-lib` and are shared by support tickets, AICoder app-builder runs, and AI-terminal runs.',\n\t\t'',\n\t\t'```bash',\n\t\t`source ${toolsDir}/env.sh`,\n\t\t`${toolsDir}/run-local-qa.sh <project-root>`,\n\t\t`node ${toolsDir}/qa-auth-bootstrap.js <project-root> /target-route`,\n\t\t`${toolsDir}/bugfix-comparison-qa.sh <project-root> origin/master -- bash -lc '<same QA command>'`,\n\t\t'```',\n\t\t'',\n\t\t`This workspace reserves Angular QA client port ${port}; use \\`$${clientUrlVar}\\` instead of assuming 4200 is free.`,\n\t\t'The local QA runner starts server/client, polls the reserved client URL, writes `qa-artifacts/server.log` and `qa-artifacts/client.log`, and fails fast on fatal startup/runtime errors.',\n\t\t'The shared auth bootstrap first opens the exact localhost client origin, logs out any visible stale session, clears service workers/cache/IndexedDB/local/session storage, then calls `/login` and `/accessToken`, seeds `refreshToken`, `accessToken`, `user`, and `lastURL`, and writes `qa-artifacts/auth-bootstrap-result.json` plus a ready/failure screenshot.',\n\t\t`For browser clickthrough work, start the runner once with \\`${keepaliveVar}=true ${toolsDir}/run-local-qa.sh <project-root>\\`; it detaches after the app is ready, and later calls should reuse \\`$${clientUrlVar}\\` for all login/upload/screenshot retries. Do not restart Angular for auth failures.`,\n\t\t'Do not run `npm run build-dev`, `ng build`, or another Angular compile while keepalive `ng serve` is running. If a full Angular build is required after browser QA, first run the staged `stop-local-qa.sh`, then build, then restart `run-local-qa.sh` for final browser proof.',\n\t\t'Use desktop screenshots at 1920x1080 by default unless the task is explicitly mobile/responsive. Every screenshot must have a customer-facing caption.',\n\t\t'For import/export/form-submit/data workflows, prove before/action/after with representative data and a concrete row/count/value assertion.',\n\t\t'For bug fixes, use `bugfix-comparison-qa.sh` so baseline/master runs the exact same repro before the candidate/PR run. A passing candidate is not enough unless the comparison result shows baseline failed or the report explicitly explains why the baseline failure could not be reproduced.',\n\t\t`Use \\`$${usernameVar}\\` and \\`$${passwordVar}\\` for the local fixture admin account unless ticket/app-specific credentials are provided.`,\n\t\t'The env file reuses `/var/lib/resolveio/puppeteer`, npm cache, worker-safe Browserslist settings, and Angular cache prep so QA should not download a browser or rebuild cold caches unnecessarily.',\n\t\tmode === 'support'\n\t\t\t? 'Support workspaces also stage local `mongod` and `mongosh` wrappers so app server scripts can start MongoDB when the worker image does not have MongoDB installed.'\n\t\t\t: '',\n\t\t''\n\t].filter((line) => line !== '').join('\\n');\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/util/ai-runner-qa-tools.ts"],"names":[],"mappings":"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","file":"ai-runner-qa-tools.js","sourcesContent":["import { buildRunnerProcessJanitorShellLibrary } from './runner-process-janitor';\n\nexport interface ResolveIORunnerQaToolBundleOptions {\n\tmode?: 'support' | 'runner';\n\tqaClientPort?: number | string;\n\tdefaultUsername?: string;\n\tdefaultPassword?: string;\n\tjobId?: string;\n\townerId?: string;\n\trunnerToken?: string;\n\ttoolsBinPath?: string;\n\tbrowserslistPath?: string;\n\tmongodbBinaryCachePath?: string;\n\ttmpRoot?: string;\n\thomeRoot?: string;\n}\n\nfunction shellDoubleQuote(value: string): string {\n\treturn String(value || '').replace(/[\"\\\\$`]/g, '\\\\$&');\n}\n\nfunction normalizePort(value: number | string | undefined, fallback: string): string {\n\tconst parsed = Number.parseInt(String(value || ''), 10);\n\treturn Number.isFinite(parsed) && parsed > 0 ? String(parsed) : fallback;\n}\n\nfunction envVar(mode: 'support' | 'runner', suffix: string): string {\n\treturn mode === 'support' ? `RESOLVEIO_SUPPORT_QA_${suffix}` : `RESOLVEIO_RUNNER_QA_${suffix}`;\n}\n\nexport function buildResolveIORunnerQaEnvScript(options: ResolveIORunnerQaToolBundleOptions = {}): string {\n\tconst mode = options.mode || 'runner';\n\tconst altMode = mode === 'support' ? 'runner' : 'support';\n\tconst tmpRoot = options.tmpRoot || (mode === 'support' ? '/tmp/resolveio-support-qa' : '/tmp/resolveio-ai-runner-qa');\n\tconst homeRoot = options.homeRoot || `${tmpRoot}/home`;\n\tconst defaultPort = normalizePort(options.qaClientPort, '4200');\n\tconst username = shellDoubleQuote(options.defaultUsername || 'admin');\n\tconst password = shellDoubleQuote(options.defaultPassword || '');\n\tconst jobId = shellDoubleQuote(options.jobId || '');\n\tconst ownerId = shellDoubleQuote(options.ownerId || '');\n\tconst runnerToken = shellDoubleQuote(options.runnerToken || '');\n\tconst toolsBinPath = options.toolsBinPath || '';\n\tconst browserslistPath = options.browserslistPath || '';\n\tconst mongodbBinaryCachePath = options.mongodbBinaryCachePath || '';\n\tconst clientPortVar = envVar(mode, 'CLIENT_PORT');\n\tconst altClientPortVar = envVar(altMode, 'CLIENT_PORT');\n\tconst clientUrlVar = envVar(mode, 'CLIENT_URL');\n\tconst altClientUrlVar = envVar(altMode, 'CLIENT_URL');\n\tconst serverUrlVar = envVar(mode, 'SERVER_URL');\n\tconst altServerUrlVar = envVar(altMode, 'SERVER_URL');\n\tconst usernameVar = envVar(mode, 'USERNAME');\n\tconst altUsernameVar = envVar(altMode, 'USERNAME');\n\tconst passwordVar = envVar(mode, 'PASSWORD');\n\tconst altPasswordVar = envVar(altMode, 'PASSWORD');\n\tconst viewportWidthVar = envVar(mode, 'VIEWPORT_WIDTH');\n\tconst altViewportWidthVar = envVar(altMode, 'VIEWPORT_WIDTH');\n\tconst viewportHeightVar = envVar(mode, 'VIEWPORT_HEIGHT');\n\tconst altViewportHeightVar = envVar(altMode, 'VIEWPORT_HEIGHT');\n\tconst timeoutVar = envVar(mode, 'ANGULAR_STARTUP_TIMEOUT_SECONDS');\n\tconst altTimeoutVar = envVar(altMode, 'ANGULAR_STARTUP_TIMEOUT_SECONDS');\n\tconst prebundleVar = envVar(mode, 'ANGULAR_PREBUNDLE');\n\tconst altPrebundleVar = envVar(altMode, 'ANGULAR_PREBUNDLE');\n\tconst reuseVar = envVar(mode, 'REUSE_RUNNING');\n\tconst altReuseVar = envVar(altMode, 'REUSE_RUNNING');\n\tconst keepaliveVar = envVar(mode, 'KEEPALIVE');\n\tconst altKeepaliveVar = envVar(altMode, 'KEEPALIVE');\n\tconst browserLoopVar = mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_BROWSER' : 'RESOLVEIO_RUNNER_QA_BROWSER';\n\treturn [\n\t\t`export ${mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_TMP' : 'RESOLVEIO_RUNNER_QA_TMP'}=\"${'${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_TMP' : 'RESOLVEIO_RUNNER_QA_TMP') + ':-' + tmpRoot + '}'}\"`,\n\t\t`export ${mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME'}=\"${'${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME') + ':-' + homeRoot + '}'}\"`,\n\t\t`RESOLVEIO_QA_TMP=\"${'${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_TMP' : 'RESOLVEIO_RUNNER_QA_TMP') + '}'}\"`,\n\t\t`RESOLVEIO_QA_HOME=\"${'${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME') + '}'}\"`,\n\t\t'mkdir -p \"$RESOLVEIO_QA_HOME/.nvm\" \"$RESOLVEIO_QA_TMP/npm-cache\" \"$RESOLVEIO_QA_TMP/mongodb-binaries\" \"$RESOLVEIO_QA_TMP/mongodb-memory-server-core\"',\n\t\t'if [ ! -f \"$RESOLVEIO_QA_HOME/.nvm/nvm.sh\" ]; then',\n\t\t' cat > \"$RESOLVEIO_QA_HOME/.nvm/nvm.sh\" <<\\'RESOLVEIO_NVM_SHIM\\'',\n\t\t'nvm() {',\n\t\t' case \"$1\" in',\n\t\t' use|install|alias) return 0 ;;',\n\t\t' current) node -v 2>/dev/null || true; return 0 ;;',\n\t\t' *) return 0 ;;',\n\t\t' esac',\n\t\t'}',\n\t\t'RESOLVEIO_NVM_SHIM',\n\t\t'fi',\n\t\t'export HOME=\"$RESOLVEIO_QA_HOME\"',\n\t\t'export NVM_DIR=\"$RESOLVEIO_QA_HOME/.nvm\"',\n\t\ttoolsBinPath ? `export PATH=\"${toolsBinPath}:$PATH\"` : '',\n\t\t'export NODE_ENV=development',\n\t\t'# Local QA must start the application HTTP server even when launched from a dedicated support-manager worker.',\n\t\t'export IS_WORKERS_ENABLED=false',\n\t\t'export IS_WORKER_INSTANCE=false',\n\t\t'export DISABLE_WORKER_SERVER_CONNECTION=false',\n\t\t'export SUPPORT_CODEX_MANAGER_PROCESS_ONLY=false',\n\t\t'export SUPPORT_AUTO_MANAGER_PROCESS_ONLY=false',\n\t\t'export AI_ASSISTANT_CODEX_MANAGER_PROCESS_ONLY=false',\n\t\tbrowserslistPath ? `export BROWSERSLIST_CONFIG=\"${browserslistPath}\"` : '',\n\t\t`export ${clientPortVar}=\"${'${' + clientPortVar + ':-${' + altClientPortVar + ':-' + defaultPort + '}}'}\"`,\n\t\t`export ${altClientPortVar}=\"${'${' + altClientPortVar + ':-${' + clientPortVar + '}}'}\"`,\n\t\t`export ${clientUrlVar}=\"${'${' + clientUrlVar + ':-${' + altClientUrlVar + ':-http://localhost:${' + clientPortVar + '}}}'}\"`,\n\t\t`export ${altClientUrlVar}=\"${'${' + altClientUrlVar + ':-${' + clientUrlVar + '}}'}\"`,\n\t\t`export ${serverUrlVar}=\"${'${' + serverUrlVar + ':-${' + altServerUrlVar + ':-http://localhost:8080}}'}\"`,\n\t\t`export ${altServerUrlVar}=\"${'${' + altServerUrlVar + ':-${' + serverUrlVar + '}}'}\"`,\n\t\t'export ADDITIONAL_ALLOWED_ORIGINS=\"${ADDITIONAL_ALLOWED_ORIGINS:-$' + clientUrlVar + '}\"',\n\t\t`export ${viewportWidthVar}=\"${'${' + viewportWidthVar + ':-${' + altViewportWidthVar + ':-1920}}'}\"`,\n\t\t`export ${altViewportWidthVar}=\"${'${' + altViewportWidthVar + ':-${' + viewportWidthVar + '}}'}\"`,\n\t\t`export ${viewportHeightVar}=\"${'${' + viewportHeightVar + ':-${' + altViewportHeightVar + ':-1080}}'}\"`,\n\t\t`export ${altViewportHeightVar}=\"${'${' + altViewportHeightVar + ':-${' + viewportHeightVar + '}}'}\"`,\n\t\t`export ${timeoutVar}=\"${'${' + timeoutVar + ':-${' + altTimeoutVar + ':-900}}'}\"`,\n\t\t`export ${altTimeoutVar}=\"${'${' + altTimeoutVar + ':-${' + timeoutVar + '}}'}\"`,\n\t\t`export ${prebundleVar}=\"${'${' + prebundleVar + ':-${' + altPrebundleVar + ':-false}}'}\"`,\n\t\t`export ${altPrebundleVar}=\"${'${' + altPrebundleVar + ':-${' + prebundleVar + '}}'}\"`,\n\t\t`export ${reuseVar}=\"${'${' + reuseVar + ':-${' + altReuseVar + ':-true}}'}\"`,\n\t\t`export ${altReuseVar}=\"${'${' + altReuseVar + ':-${' + reuseVar + '}}'}\"`,\n\t\t`export ${keepaliveVar}=\"${'${' + keepaliveVar + ':-${' + altKeepaliveVar + ':-false}}'}\"`,\n\t\t`export ${altKeepaliveVar}=\"${'${' + altKeepaliveVar + ':-${' + keepaliveVar + '}}'}\"`,\n\t\t`export ${usernameVar}=\"${'${' + usernameVar + ':-${' + altUsernameVar + ':-' + username + '}}'}\"`,\n\t\t`export ${altUsernameVar}=\"${'${' + altUsernameVar + ':-${' + usernameVar + '}}'}\"`,\n\t\t`export ${passwordVar}=\"${'${' + passwordVar + ':-${' + altPasswordVar + ':-' + password + '}}'}\"`,\n\t\t`export ${altPasswordVar}=\"${'${' + altPasswordVar + ':-${' + passwordVar + '}}'}\"`,\n\t\t`export ${envVar(mode, 'JOB_ID')}=\"${'${' + envVar(mode, 'JOB_ID') + ':-${' + envVar(altMode, 'JOB_ID') + ':-' + jobId + '}}'}\"`,\n\t\t`export ${envVar(altMode, 'JOB_ID')}=\"${'${' + envVar(altMode, 'JOB_ID') + ':-${' + envVar(mode, 'JOB_ID') + '}}'}\"`,\n\t\t`export ${envVar(mode, 'OWNER_ID')}=\"${'${' + envVar(mode, 'OWNER_ID') + ':-${' + envVar(altMode, 'OWNER_ID') + ':-' + ownerId + '}}'}\"`,\n\t\t`export ${envVar(altMode, 'OWNER_ID')}=\"${'${' + envVar(altMode, 'OWNER_ID') + ':-${' + envVar(mode, 'OWNER_ID') + '}}'}\"`,\n\t\t`export ${envVar(mode, 'RUNNER_TOKEN')}=\"${'${' + envVar(mode, 'RUNNER_TOKEN') + ':-${' + envVar(altMode, 'RUNNER_TOKEN') + ':-' + runnerToken + '}}'}\"`,\n\t\t`export ${envVar(altMode, 'RUNNER_TOKEN')}=\"${'${' + envVar(altMode, 'RUNNER_TOKEN') + ':-${' + envVar(mode, 'RUNNER_TOKEN') + '}}'}\"`,\n\t\t'export PUPPETEER_CACHE_DIR=\"${PUPPETEER_CACHE_DIR:-/var/lib/resolveio/puppeteer}\"',\n\t\t'if [ ! -d \"$PUPPETEER_CACHE_DIR\" ] || [ ! -w \"$PUPPETEER_CACHE_DIR\" ]; then',\n\t\t' export PUPPETEER_CACHE_DIR=\"$RESOLVEIO_QA_TMP/puppeteer\"',\n\t\t' mkdir -p \"$PUPPETEER_CACHE_DIR\"',\n\t\t'fi',\n\t\t`for ${browserLoopVar} in \"$PUPPETEER_CACHE_DIR\"/chrome-headless-shell/linux-*/chrome-headless-shell-linux64/chrome-headless-shell \"$PUPPETEER_CACHE_DIR\"/chrome/linux-*/chrome-linux64/chrome; do`,\n\t\t` if [ -x \"$${browserLoopVar}\" ]; then`,\n\t\t` export PUPPETEER_EXECUTABLE_PATH=\"$${browserLoopVar}\"`,\n\t\t` export CHROME_BIN=\"$${browserLoopVar}\"`,\n\t\t' break',\n\t\t' fi',\n\t\t'done',\n\t\t`unset ${browserLoopVar}`,\n\t\t'export NPM_CONFIG_CACHE=\"$RESOLVEIO_QA_TMP/npm-cache\"',\n\t\t'export NPM_CONFIG_PREFER_OFFLINE=true',\n\t\t'export NPM_CONFIG_PRODUCTION=false',\n\t\t'export npm_config_production=false',\n\t\t'export RESOLVEIO_SUPPORT_MONGOMS_PACKAGE_ROOT=\"$RESOLVEIO_QA_TMP/mongodb-memory-server-core\"',\n\t\t'export MONGOMS_DOWNLOAD_DIR=\"$RESOLVEIO_QA_TMP/mongodb-binaries\"',\n\t\tmongodbBinaryCachePath ? `export RESOLVEIO_SUPPORT_SHARED_MONGOMS_DOWNLOAD_DIR=\"${mongodbBinaryCachePath}\"` : '',\n\t\t'export MONGOMS_VERSION=\"${MONGOMS_VERSION:-7.0.14}\"',\n\t\t''\n\t].filter((line) => line !== '').join('\\n');\n}\n\nexport function buildResolveIORunnerLocalQaScript(): string {\n\treturn [\n\t\t'#!/usr/bin/env bash',\n\t\t'set -u',\n\t\t'TOOLS_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"',\n\t\t'source \"$TOOLS_DIR/env.sh\"',\n\t\t'PROJECT_ROOT=\"${1:-$(pwd)}\"',\n\t\t'PROJECT_ROOT=\"$(cd \"$PROJECT_ROOT\" && pwd)\"',\n\t\t'ARTIFACT_DIR=\"$PROJECT_ROOT/qa-artifacts\"',\n\t\t'mkdir -p \"$ARTIFACT_DIR\"',\n\t\t'CLIENT_URL=\"${RESOLVEIO_RUNNER_QA_CLIENT_URL:-${RESOLVEIO_SUPPORT_QA_CLIENT_URL:-http://localhost:${RESOLVEIO_RUNNER_QA_CLIENT_PORT:-${RESOLVEIO_SUPPORT_QA_CLIENT_PORT:-4200}}}}\"',\n\t\t'SERVER_URL=\"${RESOLVEIO_RUNNER_QA_SERVER_URL:-${RESOLVEIO_SUPPORT_QA_SERVER_URL:-http://localhost:8080}}\"',\n\t\t'CLIENT_PORT=\"${RESOLVEIO_RUNNER_QA_CLIENT_PORT:-${RESOLVEIO_SUPPORT_QA_CLIENT_PORT:-4200}}\"',\n\t\t'SERVER_PORT=\"${RESOLVEIO_RUNNER_QA_SERVER_PORT:-${RESOLVEIO_SUPPORT_QA_SERVER_PORT:-8080}}\"',\n\t\t'MONGO_PORT=\"${RESOLVEIO_RUNNER_QA_MONGO_PORT:-${RESOLVEIO_SUPPORT_QA_MONGO_PORT:-3001}}\"',\n\t\t'INSPECT_PORT=\"${RESOLVEIO_RUNNER_QA_INSPECT_PORT:-${RESOLVEIO_SUPPORT_QA_INSPECT_PORT:-9229}}\"',\n\t\t'STARTUP_TIMEOUT=\"${RESOLVEIO_RUNNER_QA_ANGULAR_STARTUP_TIMEOUT_SECONDS:-${RESOLVEIO_SUPPORT_QA_ANGULAR_STARTUP_TIMEOUT_SECONDS:-900}}\"',\n\t\t'ANGULAR_PREBUNDLE=\"${RESOLVEIO_RUNNER_QA_ANGULAR_PREBUNDLE:-${RESOLVEIO_SUPPORT_QA_ANGULAR_PREBUNDLE:-false}}\"',\n\t\t'REUSE_RUNNING=\"${RESOLVEIO_RUNNER_QA_REUSE_RUNNING:-${RESOLVEIO_SUPPORT_QA_REUSE_RUNNING:-true}}\"',\n\t\t'if [[ \"$(basename \"$TOOLS_DIR\")\" == *support* ]]; then',\n\t\t' KEEPALIVE=\"${RESOLVEIO_SUPPORT_QA_KEEPALIVE:-${RESOLVEIO_RUNNER_QA_KEEPALIVE:-false}}\"',\n\t\t'else',\n\t\t' KEEPALIVE=\"${RESOLVEIO_RUNNER_QA_KEEPALIVE:-${RESOLVEIO_SUPPORT_QA_KEEPALIVE:-false}}\"',\n\t\t'fi',\n\t\t'SERVER_STABLE_SECONDS=\"${RESOLVEIO_RUNNER_QA_SERVER_STABLE_SECONDS:-${RESOLVEIO_SUPPORT_QA_SERVER_STABLE_SECONDS:-20}}\"',\n\t\t'LOCK_DIR=\"$ARTIFACT_DIR/.qa.lock\"',\n\t\t'SERVER_PID=\"\"',\n\t\t'CLIENT_PID=\"\"',\n\t\t'SERVER_REQUIRED=0',\n\t\t'RUNNER_REUSED_READY=0',\n\t\t'ANGULAR_PREBUNDLE_ARGS=()',\n\t\t'REPO_ROOT=\"$(git -C \"$PROJECT_ROOT\" rev-parse --show-toplevel 2>/dev/null || echo \"$PROJECT_ROOT\")\"',\n\t\tbuildRunnerProcessJanitorShellLibrary({ mode: 'support' }),\n\t\t'case \"${ANGULAR_PREBUNDLE,,}\" in',\n\t\t' false|0|no|off) ANGULAR_PREBUNDLE_ARGS=(--prebundle=false) ;;',\n\t\t'esac',\n\t\t'kill_tree() {',\n\t\t' local pid=\"$1\"',\n\t\t' [ -n \"$pid\" ] || return 0',\n\t\t' for child in $(pgrep -P \"$pid\" 2>/dev/null || true); do kill_tree \"$child\"; done',\n\t\t' kill \"$pid\" >/dev/null 2>&1 || true',\n\t\t' sleep 1',\n\t\t' kill -0 \"$pid\" >/dev/null 2>&1 && kill -9 \"$pid\" >/dev/null 2>&1 || true',\n\t\t'}',\n\t\t'kill_port_listeners() {',\n\t\t' local port=\"$1\"',\n\t\t' [ -n \"$port\" ] || return 0',\n\t\t' local pids=\"\"',\n\t\t' if command -v lsof >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 lsof -ti tcp:\"$port\")\"; fi',\n\t\t' if command -v fuser >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 fuser -n tcp \"$port\")\"; fi',\n\t\t' if command -v ss >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 ss -ltnp \"sport = :$port\" | sed -n \\'s/.*pid=\\\\([0-9][0-9]*\\\\).*/\\\\1/p\\' || true)\"; fi',\n\t\t' for pid in $pids; do',\n\t\t' [ \"$pid\" = \"$$\" ] && continue',\n\t\t' kill_tree \"$pid\"',\n\t\t' done',\n\t\t' sleep 1',\n\t\t'}',\n\t\t'kill_env_marked_processes() {',\n\t\t' [ -d /proc ] || return 0',\n\t\t' local job_id=\"${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}\"',\n\t\t' local owner_id=\"${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}\"',\n\t\t' local token=\"${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}\"',\n\t\t' [ -n \"$job_id$owner_id$token\" ] || return 0',\n\t\t' for env_file in /proc/[0-9]*/environ; do',\n\t\t' pid=\"${env_file#/proc/}\"',\n\t\t' pid=\"${pid%/environ}\"',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' [ \"$pid\" = \"$$\" ] && continue',\n\t\t' env_text=\"$(tr \"\\\\0\" \"\\\\n\" < \"$env_file\" 2>/dev/null || true)\"',\n\t\t' [ -n \"$env_text\" ] || continue',\n\t\t' matched=0',\n\t\t' [ -n \"$job_id\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$\" && matched=1',\n\t\t' [ \"$matched\" = \"0\" ] && [ -n \"$owner_id\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$\" && matched=1',\n\t\t' [ \"$matched\" = \"0\" ] && [ -n \"$token\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$\" && matched=1',\n\t\t' [ \"$matched\" = \"1\" ] && kill_tree \"$pid\"',\n\t\t' done',\n\t\t'}',\n\t\t'RUNNER_ANCESTOR_PIDS=\" $$ ${PPID:-} \"',\n\t\t'ancestor_pid=\"${PPID:-}\"',\n\t\t'while [ -n \"$ancestor_pid\" ] && [ \"$ancestor_pid\" != \"0\" ] && [ \"$ancestor_pid\" != \"1\" ]; do',\n\t\t' ancestor_pid=\"$(ps -o ppid= -p \"$ancestor_pid\" 2>/dev/null | tr -d \" \" || true)\"',\n\t\t' [ -n \"$ancestor_pid\" ] && RUNNER_ANCESTOR_PIDS=\"$RUNNER_ANCESTOR_PIDS $ancestor_pid \"',\n\t\t'done',\n\t\t'skip_cleanup_pid() {',\n\t\t' case \"$RUNNER_ANCESTOR_PIDS\" in *\" $1 \"*) return 0 ;; esac',\n\t\t' return 1',\n\t\t'}',\n\t\t'cleanup_project_processes() {',\n\t\t' for pid_file in \"$ARTIFACT_DIR/server.pid\" \"$ARTIFACT_DIR/client.pid\"; do',\n\t\t' [ -f \"$pid_file\" ] || continue',\n\t\t' pid=\"$(cat \"$pid_file\" 2>/dev/null || true)\"',\n\t\t' kill_tree \"$pid\"',\n\t\t' rm -f \"$pid_file\"',\n\t\t' done',\n\t\t' for pass in 1 2 3; do',\n\t\t' for port in \"$CLIENT_PORT\" \"$SERVER_PORT\" \"$MONGO_PORT\" \"$INSPECT_PORT\"; do kill_port_listeners \"$port\"; done',\n\t\t' kill_env_marked_processes',\n\t\t' for pid in $(ps -eo pid=,args= | awk -v root=\"$PROJECT_ROOT\" \\'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\\\.sh|stop-local-qa\\\\.sh|bugfix-comparison-qa\\\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\\\/\\\\.bin\\\\/ng|esbuild|npm run client|start_client\\\\.sh|npm run server|start_server\\\\.sh|nodemon|node .*tmp\\\\/index\\\\.js|mongod|mongodb-binaries\\\\/mongod)/ {print $1}\\' 2>/dev/null || true); do',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' kill_tree \"$pid\"',\n\t\t' done',\n\t\t' if [ -d /proc ]; then',\n\t\t' for proc_cwd in /proc/[0-9]*/cwd; do',\n\t\t' pid=\"${proc_cwd#/proc/}\"',\n\t\t' pid=\"${pid%/cwd}\"',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' cwd=\"$(readlink -f \"$proc_cwd\" 2>/dev/null || true)\"',\n\t\t' case \"$cwd\" in',\n\t\t' \"$PROJECT_ROOT\"|\"$PROJECT_ROOT\"/*) kill_tree \"$pid\" ;;',\n\t\t' esac',\n\t\t' done',\n\t\t' fi',\n\t\t' sleep 1',\n\t\t' done',\n\t\t' local wait_until=$((SECONDS + 60))',\n\t\t' while [ \"$SECONDS\" -lt \"$wait_until\" ]; do',\n\t\t' local found=0',\n\t\t' for port in \"$CLIENT_PORT\" \"$SERVER_PORT\" \"$MONGO_PORT\" \"$INSPECT_PORT\"; do',\n\t\t' if command -v lsof >/dev/null 2>&1 && [ -n \"$(janitor_bounded 3 lsof -ti tcp:\"$port\")\" ]; then found=1; fi',\n\t\t' done',\n\t\t' if [ -d /proc ]; then',\n\t\t' job_id=\"${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}\"',\n\t\t' owner_id=\"${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}\"',\n\t\t' token=\"${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}\"',\n\t\t' for env_file in /proc/[0-9]*/environ; do',\n\t\t' pid=\"${env_file#/proc/}\"',\n\t\t' pid=\"${pid%/environ}\"',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' env_text=\"$(tr \"\\\\0\" \"\\\\n\" < \"$env_file\" 2>/dev/null || true)\"',\n\t\t' [ -n \"$job_id\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$\" && found=1',\n\t\t' [ -n \"$owner_id\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$\" && found=1',\n\t\t' [ -n \"$token\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$\" && found=1',\n\t\t' done',\n\t\t' fi',\n\t\t' for pid in $(ps -eo pid=,args= | awk -v root=\"$PROJECT_ROOT\" \\'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\\\.sh|stop-local-qa\\\\.sh|bugfix-comparison-qa\\\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\\\/\\\\.bin\\\\/ng|esbuild|npm run client|start_client\\\\.sh|npm run server|start_server\\\\.sh|nodemon|node .*tmp\\\\/index\\\\.js|mongod|mongodb-binaries\\\\/mongod)/ {print $1}\\' 2>/dev/null || true); do',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' found=1',\n\t\t' done',\n\t\t' [ \"$found\" = \"0\" ] && break',\n\t\t' sleep 2',\n\t\t' done',\n\t\t'}',\n\t\t'cleanup() {',\n\t\t' [ \"${RUNNER_REUSED_READY:-0}\" = \"1\" ] && return 0',\n\t\t' janitor_stop_heartbeat',\n\t\t' rm -f \"$ARTIFACT_DIR/heartbeat.pid\"',\n\t\t' janitor_update_manifest_status cleanup_started',\n\t\t' kill_tree \"$SERVER_PID\"',\n\t\t' kill_tree \"$CLIENT_PID\"',\n\t\t' cleanup_project_processes',\n\t\t' janitor_update_manifest_status cleanup_complete',\n\t\t' janitor_write_cleanup_status complete 0',\n\t\t' rm -rf \"$LOCK_DIR\" >/dev/null 2>&1 || true',\n\t\t'}',\n\t\t'trap cleanup EXIT',\n\t\t'detach_keepalive() {',\n\t\t' janitor_update_manifest_status ready_keepalive',\n\t\t' janitor_write_cleanup_status ready_keepalive 0',\n\t\t' rm -rf \"$LOCK_DIR\" >/dev/null 2>&1 || true',\n\t\t' trap - EXIT',\n\t\t' disown >/dev/null 2>&1 || true',\n\t\t' echo \"ResolveIO AI runner QA keepalive detached at $CLIENT_URL; stop with $TOOLS_DIR/stop-local-qa.sh $PROJECT_ROOT.\"',\n\t\t' exit 0',\n\t\t'}',\n\t\t'probe_url() { node - \"$1\" <<\\'RESOLVEIO_PROBE\\'',\n\t\t'const http = require(\"http\");',\n\t\t'const https = require(\"https\");',\n\t\t'const url = process.argv[2];',\n\t\t'const mod = /^https:/i.test(url) ? https : http;',\n\t\t'const req = mod.get(url, { timeout: 2500 }, (res) => { res.resume(); process.exit(res.statusCode && res.statusCode < 500 ? 0 : 1); });',\n\t\t'req.on(\"timeout\", () => req.destroy(new Error(\"timeout\")));',\n\t\t'req.on(\"error\", () => process.exit(1));',\n\t\t'RESOLVEIO_PROBE',\n\t\t'}',\n\t\t'truthy() { case \"${1,,}\" in true|1|yes|on) return 0 ;; *) return 1 ;; esac; }',\n\t\t'reuse_running_app_if_ready() {',\n\t\t' truthy \"$REUSE_RUNNING\" || return 1',\n\t\t' [ -d \"$PROJECT_ROOT/server\" ] && SERVER_REQUIRED=1 || SERVER_REQUIRED=0',\n\t\t' probe_url \"$CLIENT_URL\" || return 1',\n\t\t' if [ \"$SERVER_REQUIRED\" = \"1\" ] && ! probe_url \"$SERVER_URL\"; then return 1; fi',\n\t\t' echo \"ResolveIO AI runner QA reusing already-ready local app at $CLIENT_URL\"',\n\t\t' return 0',\n\t\t'}',\n\t\t'log_has_fatal() {',\n\t\t' local file=\"$1\"',\n\t\t' [ -f \"$file\" ] || return 1',\n\t\t' grep -Eiq \"Unhandled Rejection|Cannot read properties of undefined|TypeError|ReferenceError|EADDRINUSE|app crashed|Failed to compile|Error: Cannot find module|NG[0-9]{4}|TS[0-9]{4}\" \"$file\"',\n\t\t'}',\n\t\t'prepare_angular_cache_dirs() {',\n\t\t' [ -d \"$PROJECT_ROOT\" ] || return 0',\n\t\t' mkdir -p \"$PROJECT_ROOT/.angular/cache\" 2>/dev/null || true',\n\t\t' local version=\"\"',\n\t\t' for pkg in \"$PROJECT_ROOT/node_modules/@angular/build/package.json\" \"$PROJECT_ROOT/node_modules/@angular/cli/package.json\" \"$PROJECT_ROOT/node_modules/@angular-devkit/build-angular/package.json\"; do',\n\t\t' if [ -f \"$pkg\" ]; then',\n\t\t' version=\"$(node -e \"try{console.log(require(process.argv[1]).version||\\\\\\\"\\\\\\\")}catch(e){}\" \"$pkg\" 2>/dev/null | head -1)\"',\n\t\t' [ -n \"$version\" ] && break',\n\t\t' fi',\n\t\t' done',\n\t\t' [ -n \"$version\" ] || return 0',\n\t\t' node - \"$PROJECT_ROOT/angular.json\" \"$(basename \"$PROJECT_ROOT\")\" <<\\'RESOLVEIO_ANGULAR_CACHE_PROJECTS\\' | while IFS= read -r project; do',\n\t\t'const fs = require(\"fs\");',\n\t\t'const path = process.argv[2];',\n\t\t'const fallback = process.argv[3];',\n\t\t'const names = new Set([fallback].filter(Boolean));',\n\t\t'try {',\n\t\t' const parsed = JSON.parse(fs.readFileSync(path, \"utf8\"));',\n\t\t' if (parsed.defaultProject) names.add(String(parsed.defaultProject));',\n\t\t' if (parsed.projects && typeof parsed.projects === \"object\") Object.keys(parsed.projects).forEach((name) => names.add(String(name)));',\n\t\t'} catch (error) {}',\n\t\t'for (const name of names) console.log(name.replace(/[^A-Za-z0-9._-]/g, \"_\"));',\n\t\t'RESOLVEIO_ANGULAR_CACHE_PROJECTS',\n\t\t' [ -n \"$project\" ] || continue',\n\t\t' mkdir -p \"$PROJECT_ROOT/.angular/cache/$version/$project/vite\" 2>/dev/null || true',\n\t\t' done',\n\t\t'}',\n\t\t'server_has_started() {',\n\t\t' local file=\"$ARTIFACT_DIR/server.log\"',\n\t\t' [ -f \"$file\" ] || return 1',\n\t\t' grep -Eiq \"nodemon.*starting|node .*tmp/index\\\\.js|Running as Worker|Standalone Node Reaper|listening on|server listening|Server listening|app listening|App listening|Finished .default.\" \"$file\"',\n\t\t'}',\n\t\t'wait_for_server_ready() {',\n\t\t' [ \"$SERVER_REQUIRED\" = \"1\" ] || return 0',\n\t\t' local end=$((SECONDS + STARTUP_TIMEOUT))',\n\t\t' while [ \"$SECONDS\" -lt \"$end\" ]; do',\n\t\t' if log_has_fatal \"$ARTIFACT_DIR/server.log\"; then return 4; fi',\n\t\t' if server_has_started; then',\n\t\t' local stable_until=$((SECONDS + SERVER_STABLE_SECONDS))',\n\t\t' while [ \"$SECONDS\" -lt \"$stable_until\" ]; do if log_has_fatal \"$ARTIFACT_DIR/server.log\"; then return 4; fi; sleep 2; done',\n\t\t' return 0',\n\t\t' fi',\n\t\t' if probe_url \"$SERVER_URL\"; then',\n\t\t' local stable_until=$((SECONDS + SERVER_STABLE_SECONDS))',\n\t\t' while [ \"$SECONDS\" -lt \"$stable_until\" ]; do if log_has_fatal \"$ARTIFACT_DIR/server.log\"; then return 4; fi; sleep 2; done',\n\t\t' return 0',\n\t\t' fi',\n\t\t' sleep 5',\n\t\t' done',\n\t\t' return 4',\n\t\t'}',\n\t\t'wait_for_client() {',\n\t\t' local end=$((SECONDS + STARTUP_TIMEOUT))',\n\t\t' while [ \"$SECONDS\" -lt \"$end\" ]; do',\n\t\t' if [ -n \"$CLIENT_PID\" ] && ! kill -0 \"$CLIENT_PID\" >/dev/null 2>&1; then return 2; fi',\n\t\t' if log_has_fatal \"$ARTIFACT_DIR/client.log\"; then return 3; fi',\n\t\t' if log_has_fatal \"$ARTIFACT_DIR/server.log\"; then return 4; fi',\n\t\t' if probe_url \"$CLIENT_URL\"; then wait_for_server_ready || return $?; return 0; fi',\n\t\t' sleep 5',\n\t\t' done',\n\t\t' return 1',\n\t\t'}',\n\t\t'if reuse_running_app_if_ready; then RUNNER_REUSED_READY=1; exit 0; fi',\n\t\t'if ! mkdir \"$LOCK_DIR\" 2>/dev/null; then',\n\t\t' if janitor_lock_is_live; then',\n\t\t' echo \"ResolveIO AI runner QA lock is already held by a live runner for $PROJECT_ROOT; refusing duplicate startup.\" | tee \"$ARTIFACT_DIR/runner.log\"',\n\t\t' exit 6',\n\t\t' fi',\n\t\t' echo \"ResolveIO AI runner QA lock is stale for $PROJECT_ROOT; cleaning scoped local QA processes before retrying.\" | tee \"$ARTIFACT_DIR/runner.log\"',\n\t\t' cleanup_project_processes',\n\t\t' rm -rf \"$LOCK_DIR\" >/dev/null 2>&1 || true',\n\t\t' if ! mkdir \"$LOCK_DIR\" 2>/dev/null; then',\n\t\t' echo \"ResolveIO AI runner QA lock is still held for $PROJECT_ROOT after cleanup. Stop the existing QA runner before starting another.\" | tee -a \"$ARTIFACT_DIR/runner.log\"',\n\t\t' exit 6',\n\t\t' fi',\n\t\t'fi',\n\t\t'janitor_write_lock',\n\t\t'cleanup_project_processes',\n\t\t': > \"$ARTIFACT_DIR/server.log\"',\n\t\t': > \"$ARTIFACT_DIR/client.log\"',\n\t\t': > \"$ARTIFACT_DIR/runner.log\"',\n\t\t'janitor_write_manifest',\n\t\t'janitor_start_heartbeat',\n\t\t'echo \"$RUNNER_JANITOR_HEARTBEAT_PID\" > \"$ARTIFACT_DIR/heartbeat.pid\"',\n\t\t'janitor_check_resources || exit $?',\n\t\t'if [ -d \"$PROJECT_ROOT/server\" ]; then',\n\t\t' SERVER_REQUIRED=1',\n\t\t' if node -e \"const p=require(process.argv[1]); process.exit(p.scripts&&p.scripts.server?0:1)\" \"$PROJECT_ROOT/server/package.json\" >/dev/null 2>&1; then',\n\t\t' (cd \"$PROJECT_ROOT/server\" && source \"$TOOLS_DIR/env.sh\" && npm run server 2>&1 | tee \"$ARTIFACT_DIR/server.log\") &',\n\t\t' elif [ -x \"$PROJECT_ROOT/server/start_server.sh\" ]; then',\n\t\t' (cd \"$PROJECT_ROOT/server\" && source \"$TOOLS_DIR/env.sh\" && ./start_server.sh 2>&1 | tee \"$ARTIFACT_DIR/server.log\") &',\n\t\t' else',\n\t\t' echo \"ResolveIO AI runner QA cannot find npm server script or start_server.sh for $PROJECT_ROOT/server\" | tee \"$ARTIFACT_DIR/server.log\"',\n\t\t' exit 5',\n\t\t' fi',\n\t\t' SERVER_PID=$!',\n\t\t' wait_for_server_ready',\n\t\t' SERVER_RESULT=$?',\n\t\t' if [ \"$SERVER_RESULT\" != \"0\" ]; then echo \"ResolveIO AI runner QA server startup fatal error. See $ARTIFACT_DIR/server.log\"; exit \"$SERVER_RESULT\"; fi',\n\t\t'fi',\n\t\t'CLIENT_HOST=\"${RESOLVEIO_RUNNER_QA_CLIENT_HOST:-${RESOLVEIO_SUPPORT_QA_CLIENT_HOST:-0.0.0.0}}\"',\n\t\t'if [ -x \"$PROJECT_ROOT/node_modules/.bin/ng\" ]; then',\n\t\t' prepare_angular_cache_dirs',\n\t\t' (cd \"$PROJECT_ROOT\" && source \"$TOOLS_DIR/env.sh\" && node --max_old_space_size=8048 ./node_modules/.bin/ng serve --watch --configuration local --host \"$CLIENT_HOST\" --port \"$CLIENT_PORT\" \"${ANGULAR_PREBUNDLE_ARGS[@]}\" 2>&1 | tee \"$ARTIFACT_DIR/client.log\") &',\n\t\t'elif [ -x \"$PROJECT_ROOT/start_client.sh\" ]; then',\n\t\t' (cd \"$PROJECT_ROOT\" && source \"$TOOLS_DIR/env.sh\" && ./start_client.sh 2>&1 | tee \"$ARTIFACT_DIR/client.log\") &',\n\t\t'elif node -e \"const p=require(process.argv[1]); process.exit(p.scripts&&p.scripts.client?0:1)\" \"$PROJECT_ROOT/package.json\" >/dev/null 2>&1; then',\n\t\t' (cd \"$PROJECT_ROOT\" && source \"$TOOLS_DIR/env.sh\" && npm run client 2>&1 | tee \"$ARTIFACT_DIR/client.log\") &',\n\t\t'else',\n\t\t' echo \"ResolveIO AI runner QA cannot find Angular CLI, start_client.sh, or npm client script for $PROJECT_ROOT\" | tee \"$ARTIFACT_DIR/client.log\"',\n\t\t' exit 5',\n\t\t'fi',\n\t\t'CLIENT_PID=$!',\n\t\t'wait_for_client',\n\t\t'RESULT=$?',\n\t\t'case \"$RESULT\" in',\n\t\t' 0)',\n\t\t' echo \"ResolveIO AI runner QA local app ready at $CLIENT_URL\";',\n\t\t' echo \"$SERVER_PID\" > \"$ARTIFACT_DIR/server.pid\";',\n\t\t' echo \"$CLIENT_PID\" > \"$ARTIFACT_DIR/client.pid\";',\n\t\t' if truthy \"$KEEPALIVE\"; then detach_keepalive; fi;',\n\t\t' exit 0',\n\t\t' ;;',\n\t\t' 2) echo \"ResolveIO AI runner QA client process exited before $CLIENT_URL became ready. See $ARTIFACT_DIR/client.log\"; exit 2 ;;',\n\t\t' 3) echo \"ResolveIO AI runner QA client startup fatal error. See $ARTIFACT_DIR/client.log\"; exit 3 ;;',\n\t\t' 4) echo \"ResolveIO AI runner QA server startup fatal error. See $ARTIFACT_DIR/server.log\"; exit 4 ;;',\n\t\t' *) echo \"ResolveIO AI runner QA local app did not become ready at $CLIENT_URL within ${STARTUP_TIMEOUT}s. See $ARTIFACT_DIR/client.log and $ARTIFACT_DIR/server.log\"; exit 1 ;;',\n\t\t'esac',\n\t\t''\n\t].join('\\n');\n}\n\nexport function buildResolveIORunnerLocalQaStopperScript(): string {\n\treturn [\n\t\t'#!/usr/bin/env bash',\n\t\t'set -u',\n\t\t'TOOLS_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"',\n\t\t'source \"$TOOLS_DIR/env.sh\"',\n\t\t'PROJECT_ROOT=\"${1:-$(pwd)}\"',\n\t\t'PROJECT_ROOT=\"$(cd \"$PROJECT_ROOT\" && pwd)\"',\n\t\t'ARTIFACT_DIR=\"$PROJECT_ROOT/qa-artifacts\"',\n\t\t'CLIENT_PORT=\"${RESOLVEIO_RUNNER_QA_CLIENT_PORT:-${RESOLVEIO_SUPPORT_QA_CLIENT_PORT:-4200}}\"',\n\t\t'SERVER_PORT=\"${RESOLVEIO_RUNNER_QA_SERVER_PORT:-${RESOLVEIO_SUPPORT_QA_SERVER_PORT:-8080}}\"',\n\t\t'MONGO_PORT=\"${RESOLVEIO_RUNNER_QA_MONGO_PORT:-${RESOLVEIO_SUPPORT_QA_MONGO_PORT:-3001}}\"',\n\t\t'INSPECT_PORT=\"${RESOLVEIO_RUNNER_QA_INSPECT_PORT:-${RESOLVEIO_SUPPORT_QA_INSPECT_PORT:-9229}}\"',\n\t\t'REPO_ROOT=\"$(git -C \"$PROJECT_ROOT\" rev-parse --show-toplevel 2>/dev/null || echo \"$PROJECT_ROOT\")\"',\n\t\tbuildRunnerProcessJanitorShellLibrary({ mode: 'support' }),\n\t\t'STOP_LOCK_DIR=\"$ARTIFACT_DIR/.qa.stop.lock\"',\n\t\t'if ! mkdir \"$STOP_LOCK_DIR\" 2>/dev/null; then',\n\t\t' lock_pid=\"$(cat \"$STOP_LOCK_DIR/pid\" 2>/dev/null || true)\"',\n\t\t' if [ -n \"$lock_pid\" ] && kill -0 \"$lock_pid\" >/dev/null 2>&1; then',\n\t\t' echo \"ResolveIO AI runner QA cleanup already active for $PROJECT_ROOT; refusing duplicate stopper.\"',\n\t\t' exit 0',\n\t\t' fi',\n\t\t' rm -rf \"$STOP_LOCK_DIR\" >/dev/null 2>&1 || true',\n\t\t' mkdir \"$STOP_LOCK_DIR\" 2>/dev/null || { echo \"ResolveIO AI runner QA cleanup lock still active for $PROJECT_ROOT.\"; exit 0; }',\n\t\t'fi',\n\t\t'echo \"$$\" > \"$STOP_LOCK_DIR/pid\" 2>/dev/null || true',\n\t\t'trap \\'rm -rf \"$STOP_LOCK_DIR\" >/dev/null 2>&1 || true\\' EXIT',\n\t\t'kill_tree() {',\n\t\t' local pid=\"$1\"',\n\t\t' [ -n \"$pid\" ] || return 0',\n\t\t' kill -0 \"$pid\" >/dev/null 2>&1 || return 0',\n\t\t' for child in $(pgrep -P \"$pid\" 2>/dev/null || true); do kill_tree \"$child\"; done',\n\t\t' kill \"$pid\" >/dev/null 2>&1 || true',\n\t\t' sleep 1',\n\t\t' kill -0 \"$pid\" >/dev/null 2>&1 && kill -9 \"$pid\" >/dev/null 2>&1 || true',\n\t\t'}',\n\t\t'kill_port_listeners() {',\n\t\t' local port=\"$1\"',\n\t\t' [ -n \"$port\" ] || return 0',\n\t\t' local pids=\"\"',\n\t\t' if command -v lsof >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 lsof -ti tcp:\"$port\")\"; fi',\n\t\t' if command -v fuser >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 fuser -n tcp \"$port\")\"; fi',\n\t\t' if command -v ss >/dev/null 2>&1; then pids=\"$pids $(janitor_bounded 3 ss -ltnp \"sport = :$port\" | sed -n \\'s/.*pid=\\\\([0-9][0-9]*\\\\).*/\\\\1/p\\' || true)\"; fi',\n\t\t' for pid in $pids; do',\n\t\t' [ \"$pid\" = \"$$\" ] && continue',\n\t\t' kill_tree \"$pid\"',\n\t\t' done',\n\t\t' sleep 1',\n\t\t'}',\n\t\t'kill_env_marked_processes() {',\n\t\t' [ -d /proc ] || return 0',\n\t\t' local job_id=\"${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}\"',\n\t\t' local owner_id=\"${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}\"',\n\t\t' local token=\"${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}\"',\n\t\t' [ -n \"$job_id$owner_id$token\" ] || return 0',\n\t\t' for env_file in /proc/[0-9]*/environ; do',\n\t\t' pid=\"${env_file#/proc/}\"',\n\t\t' pid=\"${pid%/environ}\"',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' [ \"$pid\" = \"$$\" ] && continue',\n\t\t' env_text=\"$(tr \"\\\\0\" \"\\\\n\" < \"$env_file\" 2>/dev/null || true)\"',\n\t\t' [ -n \"$env_text\" ] || continue',\n\t\t' matched=0',\n\t\t' [ -n \"$job_id\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$\" && matched=1',\n\t\t' [ \"$matched\" = \"0\" ] && [ -n \"$owner_id\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$\" && matched=1',\n\t\t' [ \"$matched\" = \"0\" ] && [ -n \"$token\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$\" && matched=1',\n\t\t' [ \"$matched\" = \"1\" ] && kill_tree \"$pid\" && killed_count=$((killed_count + 1))',\n\t\t' done',\n\t\t'}',\n\t\t'RUNNER_ANCESTOR_PIDS=\" $$ ${PPID:-} \"',\n\t\t'ancestor_pid=\"${PPID:-}\"',\n\t\t'while [ -n \"$ancestor_pid\" ] && [ \"$ancestor_pid\" != \"0\" ] && [ \"$ancestor_pid\" != \"1\" ]; do',\n\t\t' ancestor_pid=\"$(ps -o ppid= -p \"$ancestor_pid\" 2>/dev/null | tr -d \" \" || true)\"',\n\t\t' [ -n \"$ancestor_pid\" ] && RUNNER_ANCESTOR_PIDS=\"$RUNNER_ANCESTOR_PIDS $ancestor_pid \"',\n\t\t'done',\n\t\t'skip_cleanup_pid() {',\n\t\t' case \"$RUNNER_ANCESTOR_PIDS\" in *\" $1 \"*) return 0 ;; esac',\n\t\t' return 1',\n\t\t'}',\n\t\t'janitor_update_manifest_status cleanup_started',\n\t\t'killed_count=0',\n'for pid_file in \"$ARTIFACT_DIR/heartbeat.pid\" \"$ARTIFACT_DIR/server.pid\" \"$ARTIFACT_DIR/client.pid\"; do',\n\t\t' [ -f \"$pid_file\" ] || continue',\n\t\t' pid=\"$(cat \"$pid_file\" 2>/dev/null || true)\"',\n\t\t' kill_tree \"$pid\"',\n\t\t' killed_count=$((killed_count + 1))',\n\t\t' rm -f \"$pid_file\"',\n\t\t'done',\n\t\t'for pass in 1 2 3; do',\n\t\t' for port in \"$CLIENT_PORT\" \"$SERVER_PORT\" \"$MONGO_PORT\" \"$INSPECT_PORT\"; do kill_port_listeners \"$port\"; done',\n\t\t' kill_env_marked_processes',\n\t\t' for pid in $(ps -eo pid=,args= | awk -v root=\"$PROJECT_ROOT\" \\'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\\\.sh|stop-local-qa\\\\.sh|bugfix-comparison-qa\\\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\\\/\\\\.bin\\\\/ng|esbuild|npm run client|start_client\\\\.sh|npm run server|start_server\\\\.sh|nodemon|node .*tmp\\\\/index\\\\.js|mongod|mongodb-binaries\\\\/mongod)/ {print $1}\\' 2>/dev/null || true); do',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' kill_tree \"$pid\"',\n\t\t' killed_count=$((killed_count + 1))',\n\t\t' done',\n\t\t' if [ -d /proc ]; then',\n\t\t' for proc_cwd in /proc/[0-9]*/cwd; do',\n\t\t' pid=\"${proc_cwd#/proc/}\"',\n\t\t' pid=\"${pid%/cwd}\"',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' cwd=\"$(readlink -f \"$proc_cwd\" 2>/dev/null || true)\"',\n\t\t' case \"$cwd\" in',\n\t\t' \"$PROJECT_ROOT\"|\"$PROJECT_ROOT\"/*) kill_tree \"$pid\" ;;',\n\t\t' esac',\n\t\t' done',\n\t\t' fi',\n\t\t' sleep 1',\n\t\t'done',\n\t\t'wait_until=$((SECONDS + 60))',\n\t'while [ \"$SECONDS\" -lt \"$wait_until\" ]; do',\n\t\t' found=0',\n\t\t' for port in \"$CLIENT_PORT\" \"$SERVER_PORT\" \"$MONGO_PORT\" \"$INSPECT_PORT\"; do',\n\t\t' if command -v lsof >/dev/null 2>&1 && [ -n \"$(janitor_bounded 3 lsof -ti tcp:\"$port\")\" ]; then found=1; fi',\n\t\t' done',\n\t\t' if [ -d /proc ]; then',\n\t\t' job_id=\"${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}\"',\n\t\t' owner_id=\"${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}\"',\n\t\t' token=\"${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}\"',\n\t\t' for env_file in /proc/[0-9]*/environ; do',\n\t\t' pid=\"${env_file#/proc/}\"',\n\t\t' pid=\"${pid%/environ}\"',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' env_text=\"$(tr \"\\\\0\" \"\\\\n\" < \"$env_file\" 2>/dev/null || true)\"',\n\t\t' [ -n \"$job_id\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$\" && found=1',\n\t\t' [ -n \"$owner_id\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$\" && found=1',\n\t\t' [ -n \"$token\" ] && echo \"$env_text\" | grep -Eq \"^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$\" && found=1',\n\t\t' done',\n\t\t' fi',\n\t\t' for pid in $(ps -eo pid=,args= | awk -v root=\"$PROJECT_ROOT\" \\'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\\\.sh|stop-local-qa\\\\.sh|bugfix-comparison-qa\\\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\\\/\\\\.bin\\\\/ng|esbuild|npm run client|start_client\\\\.sh|npm run server|start_server\\\\.sh|nodemon|node .*tmp\\\\/index\\\\.js|mongod|mongodb-binaries\\\\/mongod)/ {print $1}\\' 2>/dev/null || true); do',\n\t\t' skip_cleanup_pid \"$pid\" && continue',\n\t\t' found=1',\n\t\t' done',\n\t\t' [ \"$found\" = \"0\" ] && break',\n\t\t' sleep 2',\n\t\t'done',\n\t\t'rm -rf \"$ARTIFACT_DIR/.qa.lock\" >/dev/null 2>&1 || true',\n\t\t'janitor_update_manifest_status stopped',\n\t\t'janitor_write_cleanup_status stopped \"$killed_count\"',\n\t\t'echo \"ResolveIO AI runner QA local app stopped for $PROJECT_ROOT\"',\n\t\t''\n\t].join('\\n');\n}\n\nexport function buildResolveIORunnerBugfixComparisonQaScript(): string {\n\treturn [\n\t\t'#!/usr/bin/env bash',\n\t\t'set -u',\n\t\t'TOOLS_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"',\n\t\t'source \"$TOOLS_DIR/env.sh\"',\n\t\t'PROJECT_ROOT=\"${1:-}\"',\n\t\t'if [ -z \"$PROJECT_ROOT\" ]; then',\n\t\t' echo \"Usage: $0 <project-root> [baseline-ref] -- <qa-command...>\" >&2',\n\t\t' exit 2',\n\t\t'fi',\n\t\t'shift || true',\n\t\t'DEFAULT_BASELINE_REF=\"${RESOLVEIO_RUNNER_QA_BASELINE_REF:-${RESOLVEIO_SUPPORT_QA_BASELINE_REF:-origin/master}}\"',\n\t\t'if [ \"${1:-}\" = \"--\" ]; then',\n\t\t' BASELINE_REF=\"$DEFAULT_BASELINE_REF\"',\n\t\t'else',\n\t\t' BASELINE_REF=\"${1:-$DEFAULT_BASELINE_REF}\"',\n\t\t' if [ \"$#\" -gt 0 ]; then shift || true; fi',\n\t\t'fi',\n\t\t'if [ \"${1:-}\" = \"--\" ]; then shift; fi',\n\t\t'if [ \"$#\" -eq 0 ]; then',\n\t\t' echo \"Usage: $0 <project-root> [baseline-ref] -- <qa-command...>\" >&2',\n\t\t' exit 2',\n\t\t'fi',\n\t\t'PROJECT_ROOT=\"$(cd \"$PROJECT_ROOT\" && pwd)\"',\n\t\t'REPO_ROOT=\"$(git -C \"$PROJECT_ROOT\" rev-parse --show-toplevel)\"',\n\t\t'PROJECT_REL=\"$(git -C \"$REPO_ROOT\" ls-files --full-name \"$PROJECT_ROOT\" 2>/dev/null | head -1 | xargs dirname 2>/dev/null || true)\"',\n\t\t'if [ -z \"$PROJECT_REL\" ] || [ \"$PROJECT_REL\" = \".\" ]; then',\n\t\t' PROJECT_REL=\"$(node - \"$REPO_ROOT\" \"$PROJECT_ROOT\" <<\\'RESOLVEIO_REL\\'',\n\t\t'const path = require(\"path\");',\n\t\t'console.log(path.relative(process.argv[2], process.argv[3]) || \".\");',\n\t\t'RESOLVEIO_REL',\n\t\t' )\"',\n\t\t'fi',\n\t\t'ARTIFACT_DIR=\"$PROJECT_ROOT/qa-artifacts\"',\n\t\t'mkdir -p \"$ARTIFACT_DIR/baseline\" \"$ARTIFACT_DIR/candidate\"',\n\t\t'RESULT_JSON=\"$ARTIFACT_DIR/bugfix-comparison-result.json\"',\n\t\t'CANDIDATE_PATCH=\"$ARTIFACT_DIR/bugfix-candidate.patch\"',\n\t\t'ORIGINAL_HEAD=\"$(git -C \"$REPO_ROOT\" rev-parse HEAD)\"',\n\t\t'ORIGINAL_BRANCH=\"$(git -C \"$REPO_ROOT\" symbolic-ref --short HEAD 2>/dev/null || true)\"',\n\t\t'QA_COMMAND=(\"$@\")',\n\t\t'STOPPER=\"$TOOLS_DIR/stop-local-qa.sh\"',\n\t\t'CURRENT_PHASE=\"\"',\n\t\t'stop_local_qa() {',\n\t\t' \"$STOPPER\" \"$PROJECT_ROOT\" >/dev/null 2>&1 || true',\n\t\t'}',\n\t\t'write_json() {',\n\t\t' node - \"$RESULT_JSON\" \"$@\" <<\\'RESOLVEIO_WRITE_JSON\\'',\n\t\t'const fs = require(\"fs\");',\n\t\t'const [path, status, baselineExit, candidateExit, baselineRef, originalHead, projectRel, note] = process.argv.slice(2);',\n\t\t'const payload = {',\n\t\t' status,',\n\t\t' baseline_ref: baselineRef,',\n\t\t' original_head: originalHead,',\n\t\t' project: projectRel,',\n\t\t' baseline_exit_code: Number(baselineExit),',\n\t\t' candidate_exit_code: Number(candidateExit),',\n\t\t' code_switch_proven: Number(baselineExit) !== 0 && Number(candidateExit) === 0,',\n\t\t' candidate_passed: Number(candidateExit) === 0,',\n\t\t' baseline_failed: Number(baselineExit) !== 0,',\n\t\t' note,',\n\t\t' artifact_dirs: { baseline: \"qa-artifacts/baseline\", candidate: \"qa-artifacts/candidate\" },',\n\t\t' created_at: new Date().toISOString()',\n\t\t'};',\n\t\t'fs.writeFileSync(path, JSON.stringify(payload, null, 2));',\n\t\t'console.log(JSON.stringify(payload, null, 2));',\n\t\t'RESOLVEIO_WRITE_JSON',\n\t\t'}',\n\t\t'snapshot_phase_artifacts() {',\n\t\t' local phase=\"$1\"',\n\t\t' mkdir -p \"$ARTIFACT_DIR/$phase\"',\n\t\t' find \"$ARTIFACT_DIR\" -maxdepth 1 -type f \\\\( -name \"*.png\" -o -name \"*.jpg\" -o -name \"*.jpeg\" -o -name \"*.webp\" -o -name \"*.json\" -o -name \"*.txt\" -o -name \"*.log\" -o -name \"*.zip\" \\\\) -print0 2>/dev/null | while IFS= read -r -d \"\" file; do',\n\t\t' cp \"$file\" \"$ARTIFACT_DIR/$phase/$(basename \"$file\")\" 2>/dev/null || true',\n\t\t' done',\n\t\t'}',\n\t\t'capture_candidate_patch() {',\n\t\t' : > \"$CANDIDATE_PATCH\"',\n\t\t' git -C \"$REPO_ROOT\" diff --binary -- \"$PROJECT_REL\" >> \"$CANDIDATE_PATCH\" || true',\n\t\t' git -C \"$REPO_ROOT\" diff --binary --cached -- \"$PROJECT_REL\" >> \"$CANDIDATE_PATCH\" || true',\n\t\t'}',\n\t\t'checkout_project_from_ref() {',\n\t\t' local ref=\"$1\"',\n\t\t' git -C \"$REPO_ROOT\" checkout \"$ref\" -- \"$PROJECT_REL\"',\n\t\t'}',\n\t\t'restore_candidate() {',\n\t\t' stop_local_qa',\n\t\t' checkout_project_from_ref \"$ORIGINAL_HEAD\" || true',\n\t\t' if [ -s \"$CANDIDATE_PATCH\" ]; then',\n\t\t' git -C \"$REPO_ROOT\" apply --whitespace=nowarn \"$CANDIDATE_PATCH\" || true',\n\t\t' fi',\n\t\t'}',\n\t\t'trap restore_candidate EXIT',\n\t\t'run_phase() {',\n\t\t' local phase=\"$1\"',\n\t\t' CURRENT_PHASE=\"$phase\"',\n\t\t' stop_local_qa',\n\t\t' export RESOLVEIO_RUNNER_QA_PHASE=\"$phase\"',\n\t\t' export RESOLVEIO_SUPPORT_QA_PHASE=\"$phase\"',\n\t\t' export RESOLVEIO_RUNNER_QA_ARTIFACT_DIR=\"$ARTIFACT_DIR/$phase\"',\n\t\t' export RESOLVEIO_SUPPORT_QA_ARTIFACT_DIR=\"$ARTIFACT_DIR/$phase\"',\n\t\t' mkdir -p \"$ARTIFACT_DIR/$phase\"',\n\t\t' find \"$ARTIFACT_DIR/$phase\" -maxdepth 1 -type f \\\\( -name \"*.png\" -o -name \"*.jpg\" -o -name \"*.jpeg\" -o -name \"*.webp\" -o -name \"*.json\" -o -name \"*.txt\" -o -name \"*.log\" -o -name \"*.zip\" \\\\) -delete 2>/dev/null || true',\n\t\t' find \"$ARTIFACT_DIR\" -maxdepth 1 -type f \\\\( -name \"*.png\" -o -name \"*.jpg\" -o -name \"*.jpeg\" -o -name \"*.webp\" -o -name \"*proof.json\" -o -name \"auth-bootstrap-result.json\" \\\\) -delete 2>/dev/null || true',\n\t\t' set +e',\n\t\t' (cd \"$REPO_ROOT\" && \"${QA_COMMAND[@]}\") 2>&1 | tee \"$ARTIFACT_DIR/$phase/qa-command.log\"',\n\t\t' local rc=\"${PIPESTATUS[0]}\"',\n\t\t' set +e',\n\t\t' snapshot_phase_artifacts \"$phase\"',\n\t\t' stop_local_qa',\n\t\t' return \"$rc\"',\n\t\t'}',\n\t\t'echo \"ResolveIO bugfix comparison QA preserving candidate diff for $PROJECT_REL\"',\n\t\t'capture_candidate_patch',\n\t\t'stop_local_qa',\n\t\t'echo \"ResolveIO bugfix comparison QA baseline phase: $BASELINE_REF\"',\n\t\t'checkout_project_from_ref \"$BASELINE_REF\"',\n\t\t'run_phase baseline',\n\t\t'BASELINE_EXIT=\"$?\"',\n\t\t'echo \"ResolveIO bugfix comparison QA candidate phase: restored workspace candidate\"',\n\t\t'restore_candidate',\n\t\t'run_phase candidate',\n\t\t'CANDIDATE_EXIT=\"$?\"',\n\t\t'if [ \"$CANDIDATE_EXIT\" = \"0\" ] && [ \"$BASELINE_EXIT\" != \"0\" ]; then',\n\t\t' STATUS=\"pass\"',\n\t\t' NOTE=\"Baseline failed and candidate passed for the same QA command.\"',\n\t\t'elif [ \"$CANDIDATE_EXIT\" = \"0\" ]; then',\n\t\t' STATUS=\"inconclusive_baseline_also_passed\"',\n\t\t' NOTE=\"Candidate passed, but baseline also passed; the code switch did not prove the bug fix.\"',\n\t\t'else',\n\t\t' STATUS=\"fail_candidate_failed\"',\n\t\t' NOTE=\"Candidate failed the QA command.\"',\n\t\t'fi',\n\t\t'write_json \"$STATUS\" \"$BASELINE_EXIT\" \"$CANDIDATE_EXIT\" \"$BASELINE_REF\" \"$ORIGINAL_HEAD\" \"$PROJECT_REL\" \"$NOTE\"',\n\t\t'[ \"$CANDIDATE_EXIT\" = \"0\" ] || exit \"$CANDIDATE_EXIT\"',\n\t\t'[ \"$STATUS\" = \"pass\" ] || exit 8',\n\t\t''\n\t].join('\\n');\n}\n\nexport function buildResolveIORunnerQaToolsReadme(options: ResolveIORunnerQaToolBundleOptions = {}): string {\n\tconst mode = options.mode || 'runner';\n\tconst toolsDir = mode === 'support' ? '.resolveio-support-tools' : '.resolveio-ai-runner-tools';\n\tconst port = normalizePort(options.qaClientPort, '4200');\n\tconst clientUrlVar = envVar(mode, 'CLIENT_URL');\n\tconst keepaliveVar = envVar(mode, 'KEEPALIVE');\n\tconst usernameVar = envVar(mode, 'USERNAME');\n\tconst passwordVar = envVar(mode, 'PASSWORD');\n\treturn [\n\t\t`# ResolveIO ${mode === 'support' ? 'Support' : 'AI Runner'} Local QA Tools`,\n\t\t'',\n\t\t'These scripts are generated by `@resolveio/server-lib` and are shared by support tickets, AICoder app-builder runs, and AI-terminal runs.',\n\t\t'',\n\t\t'```bash',\n\t\t`source ${toolsDir}/env.sh`,\n\t\t`${toolsDir}/run-local-qa.sh <project-root>`,\n\t\t`node ${toolsDir}/qa-auth-bootstrap.js <project-root> /target-route`,\n\t\t`${toolsDir}/bugfix-comparison-qa.sh <project-root> origin/master -- bash -lc '<same QA command>'`,\n\t\t'```',\n\t\t'',\n\t\t`This workspace reserves Angular QA client port ${port}; use \\`$${clientUrlVar}\\` instead of assuming 4200 is free.`,\n\t\t'The local QA runner starts server/client, polls the reserved client URL, writes `qa-artifacts/server.log` and `qa-artifacts/client.log`, and fails fast on fatal startup/runtime errors.',\n\t\t'The shared auth bootstrap first opens the exact localhost client origin, logs out any visible stale session, clears service workers/cache/IndexedDB/local/session storage, then calls `/login` and `/accessToken`, seeds `refreshToken`, `accessToken`, `user`, and `lastURL`, and writes `qa-artifacts/auth-bootstrap-result.json` plus a ready/failure screenshot.',\n\t\t`For browser clickthrough work, start the runner once with \\`${keepaliveVar}=true ${toolsDir}/run-local-qa.sh <project-root>\\`; it detaches after the app is ready, and later calls should reuse \\`$${clientUrlVar}\\` for all login/upload/screenshot retries. Do not restart Angular for auth failures.`,\n\t\t'Do not run `npm run build-dev`, `ng build`, or another Angular compile while keepalive `ng serve` is running. If a full Angular build is required after browser QA, first run the staged `stop-local-qa.sh`, then build, then restart `run-local-qa.sh` for final browser proof.',\n\t\t'Use desktop screenshots at 1920x1080 by default unless the task is explicitly mobile/responsive. Every screenshot must have a customer-facing caption.',\n\t\t'For import/export/form-submit/data workflows, prove before/action/after with representative data and a concrete row/count/value assertion.',\n\t\t'For bug fixes, use `bugfix-comparison-qa.sh` so baseline/master runs the exact same repro before the candidate/PR run. A passing candidate is not enough unless the comparison result shows baseline failed or the report explicitly explains why the baseline failure could not be reproduced.',\n\t\t`Use \\`$${usernameVar}\\` and \\`$${passwordVar}\\` for the local fixture admin account unless ticket/app-specific credentials are provided.`,\n\t\t'The env file reuses `/var/lib/resolveio/puppeteer`, npm cache, worker-safe Browserslist settings, and Angular cache prep so QA should not download a browser or rebuild cold caches unnecessarily.',\n\t\tmode === 'support'\n\t\t\t? 'Support workspaces also stage local `mongod` and `mongosh` wrappers so app server scripts can start MongoDB when the worker image does not have MongoDB installed.'\n\t\t\t: '',\n\t\t''\n\t].filter((line) => line !== '').join('\\n');\n}\n"]}
|
|
@@ -104,10 +104,23 @@ var RunnerProcessJanitor = /** @class */ (function () {
|
|
|
104
104
|
'} catch (error) {}',
|
|
105
105
|
'JANITOR_UPDATE_STATUS',
|
|
106
106
|
'}',
|
|
107
|
+
'janitor_update_lock_heartbeat() {',
|
|
108
|
+
' local lock_json="${LOCK_DIR:-}/lock.json"',
|
|
109
|
+
' [ -f "$lock_json" ] || return 0',
|
|
110
|
+
' node - "$lock_json" <<\'JANITOR_UPDATE_LOCK\'',
|
|
111
|
+
'const fs = require("fs");',
|
|
112
|
+
'const [file] = process.argv.slice(2);',
|
|
113
|
+
'try {',
|
|
114
|
+
' const data = JSON.parse(fs.readFileSync(file, "utf8"));',
|
|
115
|
+
' data.heartbeat_at = new Date().toISOString();',
|
|
116
|
+
' fs.writeFileSync(file, JSON.stringify(data, null, 2));',
|
|
117
|
+
'} catch (error) {}',
|
|
118
|
+
'JANITOR_UPDATE_LOCK',
|
|
119
|
+
'}',
|
|
107
120
|
'janitor_start_heartbeat() {',
|
|
108
121
|
' local manifest',
|
|
109
122
|
' manifest="$(janitor_manifest_path)"',
|
|
110
|
-
' ( while true; do janitor_update_manifest_status running; sleep 15; done ) >/dev/null 2>&1 &',
|
|
123
|
+
' ( while true; do janitor_update_manifest_status running; janitor_update_lock_heartbeat; sleep 15; done ) >/dev/null 2>&1 &',
|
|
111
124
|
' RUNNER_JANITOR_HEARTBEAT_PID="$!"',
|
|
112
125
|
'}',
|
|
113
126
|
'janitor_stop_heartbeat() {',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/util/runner-process-janitor.ts"],"names":[],"mappings":"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","file":"runner-process-janitor.js","sourcesContent":["export interface RunnerProcessJanitorManifestInput {\n\tjobId?: string;\n\townerId?: string;\n\trunnerToken?: string;\n\tworkspaceRoot?: string;\n\tprojectRoot?: string;\n\tclientPort?: string | number;\n\tserverPort?: string | number;\n\tmongoPort?: string | number;\n\tinspectPort?: string | number;\n}\n\nexport interface RunnerProcessJanitorScriptOptions {\n\tmode?: 'support' | 'runner';\n\tstaleTtlSeconds?: number;\n\tminAvailableMemoryMb?: number;\n\tminAvailableDiskMb?: number;\n}\n\nexport class RunnerProcessJanitor {\n\tpublic static readonly defaultStaleTtlSeconds = 600;\n\tpublic static readonly defaultMinAvailableMemoryMb = 512;\n\tpublic static readonly defaultMinAvailableDiskMb = 1536;\n\n\tpublic static buildManifest(input: RunnerProcessJanitorManifestInput = {}): Record<string, unknown> {\n\t\treturn {\n\t\t\tjob_id: String(input.jobId || '').trim(),\n\t\t\towner_id: String(input.ownerId || '').trim(),\n\t\t\trunner_token: String(input.runnerToken || '').trim(),\n\t\t\tworkspace_root: String(input.workspaceRoot || '').trim(),\n\t\t\tproject_root: String(input.projectRoot || '').trim(),\n\t\t\tports: {\n\t\t\t\tclient: String(input.clientPort || '').trim(),\n\t\t\t\tserver: String(input.serverPort || '').trim(),\n\t\t\t\tmongo: String(input.mongoPort || '').trim(),\n\t\t\t\tinspect: String(input.inspectPort || '').trim()\n\t\t\t},\n\t\t\tstarted_at: new Date().toISOString(),\n\t\t\theartbeat_at: new Date().toISOString(),\n\t\t\tcleanup_status: 'running'\n\t\t};\n\t}\n\n\tpublic static buildShellLibrary(options: RunnerProcessJanitorScriptOptions = {}): string {\n\t\tconst mode = options.mode || 'runner';\n\t\tconst staleTtlSeconds = Number.isFinite(Number(options.staleTtlSeconds)) && Number(options.staleTtlSeconds) > 0\n\t\t\t? Number(options.staleTtlSeconds)\n\t\t\t: RunnerProcessJanitor.defaultStaleTtlSeconds;\n\t\tconst minAvailableMemoryMb = Number.isFinite(Number(options.minAvailableMemoryMb)) && Number(options.minAvailableMemoryMb) > 0\n\t\t\t? Number(options.minAvailableMemoryMb)\n\t\t\t: RunnerProcessJanitor.defaultMinAvailableMemoryMb;\n\t\tconst minAvailableDiskMb = Number.isFinite(Number(options.minAvailableDiskMb)) && Number(options.minAvailableDiskMb) > 0\n\t\t\t? Number(options.minAvailableDiskMb)\n\t\t\t: RunnerProcessJanitor.defaultMinAvailableDiskMb;\n\t\tconst prefix = mode === 'support' ? 'RESOLVEIO_SUPPORT_QA' : 'RESOLVEIO_RUNNER_QA';\n\t\tconst altPrefix = mode === 'support' ? 'RESOLVEIO_RUNNER_QA' : 'RESOLVEIO_SUPPORT_QA';\n\t\treturn [\n\t\t\t`RUNNER_JANITOR_STALE_TTL_SECONDS=\"\\${${prefix}_STALE_TTL_SECONDS:-\\${${altPrefix}_STALE_TTL_SECONDS:-${staleTtlSeconds}}}\"`,\n\t\t\t`RUNNER_JANITOR_MIN_AVAILABLE_MEMORY_MB=\"\\${${prefix}_MIN_AVAILABLE_MEMORY_MB:-\\${${altPrefix}_MIN_AVAILABLE_MEMORY_MB:-${minAvailableMemoryMb}}}\"`,\n\t\t\t`RUNNER_JANITOR_MIN_AVAILABLE_DISK_MB=\"\\${${prefix}_MIN_AVAILABLE_DISK_MB:-\\${${altPrefix}_MIN_AVAILABLE_DISK_MB:-${minAvailableDiskMb}}}\"`,\n\t\t\t'RUNNER_JANITOR_HEARTBEAT_PID=\"\"',\n\t\t\t'janitor_json_escape() { node -e \"process.stdout.write(JSON.stringify(process.argv[1] || \\'\\'))\" \"$1\"; }',\n\t\t\t'janitor_now_ms() { node -e \"console.log(Date.now())\"; }',\n\t\t\t'janitor_file_mtime_ms() { node -e \"const fs=require(\\'fs\\'); try{console.log(Math.floor(fs.statSync(process.argv[1]).mtimeMs||0))}catch(e){console.log(0)}\" \"$1\"; }',\n\t\t\t'janitor_bounded() {',\n\t\t\t' local seconds=\"$1\"',\n\t\t\t' shift || return 0',\n\t\t\t' if command -v timeout >/dev/null 2>&1; then',\n\t\t\t' timeout -k 2 \"$seconds\" \"$@\" 2>/dev/null || true',\n\t\t\t' else',\n\t\t\t' \"$@\" 2>/dev/null || true',\n\t\t\t' fi',\n\t\t\t'}',\n\t\t\t'janitor_manifest_path() { echo \"$ARTIFACT_DIR/runner-process-manifest.json\"; }',\n\t\t\t'janitor_status_path() { echo \"$ARTIFACT_DIR/runner-process-cleanup.json\"; }',\n\t\t\t'janitor_runner_token() {',\n\t\t\t` local token=\"\\${${prefix}_RUNNER_TOKEN:-\\${${altPrefix}_RUNNER_TOKEN:-}}\"`,\n\t\t\t' if [ -z \"$token\" ]; then token=\"$$-$(date +%s)-$RANDOM\"; fi',\n\t\t\t' echo \"$token\"',\n\t\t\t'}',\n\t\t\t'janitor_write_manifest() {',\n\t\t\t' local manifest token job owner',\n\t\t\t' manifest=\"$(janitor_manifest_path)\"',\n\t\t\t' token=\"$(janitor_runner_token)\"',\n\t\t\t` job=\"\\${${prefix}_JOB_ID:-\\${${altPrefix}_JOB_ID:-}}\"`,\n\t\t\t` owner=\"\\${${prefix}_OWNER_ID:-\\${${altPrefix}_OWNER_ID:-}}\"`,\n\t\t\t' cat > \"$manifest\" <<EOF',\n\t\t\t'{',\n\t\t\t' \"job_id\": $(janitor_json_escape \"$job\"),',\n\t\t\t' \"owner_id\": $(janitor_json_escape \"$owner\"),',\n\t\t\t' \"runner_token\": $(janitor_json_escape \"$token\"),',\n\t\t\t' \"pid\": $$,',\n\t\t\t' \"workspace_root\": $(janitor_json_escape \"${REPO_ROOT:-}\"),',\n\t\t\t' \"project_root\": $(janitor_json_escape \"$PROJECT_ROOT\"),',\n\t\t\t' \"ports\": {',\n\t\t\t' \"client\": $(janitor_json_escape \"$CLIENT_PORT\"),',\n\t\t\t' \"server\": $(janitor_json_escape \"$SERVER_PORT\"),',\n\t\t\t' \"mongo\": $(janitor_json_escape \"$MONGO_PORT\"),',\n\t\t\t' \"inspect\": $(janitor_json_escape \"$INSPECT_PORT\")',\n\t\t\t' },',\n\t\t\t' \"started_at\": $(janitor_json_escape \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"),',\n\t\t\t' \"heartbeat_at\": $(janitor_json_escape \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"),',\n\t\t\t' \"cleanup_status\": \"running\"',\n\t\t\t'}',\n\t\t\t'EOF',\n\t\t\t'}',\n\t\t\t'janitor_update_manifest_status() {',\n\t\t\t' local status=\"$1\"',\n\t\t\t' local manifest',\n\t\t\t' manifest=\"$(janitor_manifest_path)\"',\n\t\t\t' [ -f \"$manifest\" ] || return 0',\n\t\t\t' node - \"$manifest\" \"$status\" <<\\'JANITOR_UPDATE_STATUS\\'',\n\t\t\t'const fs = require(\"fs\");',\n\t\t\t'const [file, status] = process.argv.slice(2);',\n\t\t\t'try {',\n\t\t\t' const data = JSON.parse(fs.readFileSync(file, \"utf8\"));',\n\t\t\t' data.heartbeat_at = new Date().toISOString();',\n\t\t\t' data.cleanup_status = status;',\n\t\t\t' fs.writeFileSync(file, JSON.stringify(data, null, 2));',\n\t\t\t'} catch (error) {}',\n\t\t\t'JANITOR_UPDATE_STATUS',\n\t\t\t'}',\n\t\t\t'janitor_start_heartbeat() {',\n\t\t\t' local manifest',\n\t\t\t' manifest=\"$(janitor_manifest_path)\"',\n\t\t\t' ( while true; do janitor_update_manifest_status running; sleep 15; done ) >/dev/null 2>&1 &',\n\t\t\t' RUNNER_JANITOR_HEARTBEAT_PID=\"$!\"',\n\t\t\t'}',\n\t\t\t'janitor_stop_heartbeat() {',\n\t\t\t' [ -n \"$RUNNER_JANITOR_HEARTBEAT_PID\" ] || return 0',\n\t\t\t' kill \"$RUNNER_JANITOR_HEARTBEAT_PID\" >/dev/null 2>&1 || true',\n\t\t\t' RUNNER_JANITOR_HEARTBEAT_PID=\"\"',\n\t\t\t'}',\n\t\t\t'janitor_lock_is_live() {',\n\t\t\t' local lock_json=\"$LOCK_DIR/lock.json\"',\n\t\t\t' [ -f \"$lock_json\" ] || return 1',\n\t\t\t' node - \"$lock_json\" \"$RUNNER_JANITOR_STALE_TTL_SECONDS\" <<\\'JANITOR_LOCK_LIVE\\'',\n\t\t\t'const fs = require(\"fs\");',\n\t\t\t'const [file, ttlRaw] = process.argv.slice(2);',\n\t\t\t'const ttlMs = Math.max(1, Number(ttlRaw || 600)) * 1000;',\n\t\t\t'try {',\n\t\t\t' const data = JSON.parse(fs.readFileSync(file, \"utf8\"));',\n\t\t\t' const pid = Number(data.pid || 0);',\n\t\t\t' const heartbeat = Date.parse(data.heartbeat_at || data.started_at || \"\");',\n\t\t\t' if (!pid || !heartbeat || Date.now() - heartbeat > ttlMs) process.exit(1);',\n\t\t\t' process.kill(pid, 0);',\n\t\t\t' process.exit(0);',\n\t\t\t'} catch (error) { process.exit(1); }',\n\t\t\t'JANITOR_LOCK_LIVE',\n\t\t\t'}',\n\t\t\t'janitor_write_lock() {',\n\t\t\t' cat > \"$LOCK_DIR/lock.json\" <<EOF',\n\t\t\t'{',\n\t\t\t' \"pid\": $$,',\n\t\t\t' \"project_root\": $(janitor_json_escape \"$PROJECT_ROOT\"),',\n\t\t\t' \"manifest\": $(janitor_json_escape \"$(janitor_manifest_path)\"),',\n\t\t\t' \"started_at\": $(janitor_json_escape \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"),',\n\t\t\t' \"heartbeat_at\": $(janitor_json_escape \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\")',\n\t\t\t'}',\n\t\t\t'EOF',\n\t\t\t'}',\n\t\t\t'janitor_check_resources() {',\n\t\t\t' local available_mb disk_mb',\n\t\t\t' available_mb=\"$(awk \\'/MemAvailable:/ {print int($2/1024)}\\' /proc/meminfo 2>/dev/null || echo 999999)\"',\n\t\t\t' disk_mb=\"$(df -Pm \"$PROJECT_ROOT\" 2>/dev/null | awk \\'NR==2 {print $4}\\' || echo 999999)\"',\n\t\t\t' if [ -n \"$available_mb\" ] && [ \"$available_mb\" -lt \"$RUNNER_JANITOR_MIN_AVAILABLE_MEMORY_MB\" ]; then',\n\t\t\t' echo \"ResolveIO AI runner QA resource guard: only ${available_mb}MB memory available; need ${RUNNER_JANITOR_MIN_AVAILABLE_MEMORY_MB}MB before Angular QA.\" | tee -a \"$ARTIFACT_DIR/runner.log\"',\n\t\t\t' return 7',\n\t\t\t' fi',\n\t\t\t' if [ -n \"$disk_mb\" ] && [ \"$disk_mb\" -lt \"$RUNNER_JANITOR_MIN_AVAILABLE_DISK_MB\" ]; then',\n\t\t\t' echo \"ResolveIO AI runner QA resource guard: only ${disk_mb}MB disk available; need ${RUNNER_JANITOR_MIN_AVAILABLE_DISK_MB}MB before Angular QA.\" | tee -a \"$ARTIFACT_DIR/runner.log\"',\n\t\t\t' return 7',\n\t\t\t' fi',\n\t\t\t' return 0',\n\t\t\t'}',\n\t\t\t'janitor_write_cleanup_status() {',\n\t\t\t' local status=\"$1\"',\n\t\t\t' local killed=\"$2\"',\n\t\t\t' cat > \"$(janitor_status_path)\" <<EOF',\n\t\t\t'{',\n\t\t\t' \"status\": $(janitor_json_escape \"$status\"),',\n\t\t\t' \"killed_processes\": ${killed:-0},',\n\t\t\t' \"project_root\": $(janitor_json_escape \"$PROJECT_ROOT\"),',\n\t\t\t' \"updated_at\": $(janitor_json_escape \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\")',\n\t\t\t'}',\n\t\t\t'EOF',\n\t\t\t'}',\n\t\t\t''\n\t\t].join('\\n');\n\t}\n}\n\nexport function buildRunnerProcessJanitorShellLibrary(options: RunnerProcessJanitorScriptOptions = {}): string {\n\treturn RunnerProcessJanitor.buildShellLibrary(options);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/util/runner-process-janitor.ts"],"names":[],"mappings":"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","file":"runner-process-janitor.js","sourcesContent":["export interface RunnerProcessJanitorManifestInput {\n\tjobId?: string;\n\townerId?: string;\n\trunnerToken?: string;\n\tworkspaceRoot?: string;\n\tprojectRoot?: string;\n\tclientPort?: string | number;\n\tserverPort?: string | number;\n\tmongoPort?: string | number;\n\tinspectPort?: string | number;\n}\n\nexport interface RunnerProcessJanitorScriptOptions {\n\tmode?: 'support' | 'runner';\n\tstaleTtlSeconds?: number;\n\tminAvailableMemoryMb?: number;\n\tminAvailableDiskMb?: number;\n}\n\nexport class RunnerProcessJanitor {\n\tpublic static readonly defaultStaleTtlSeconds = 600;\n\tpublic static readonly defaultMinAvailableMemoryMb = 512;\n\tpublic static readonly defaultMinAvailableDiskMb = 1536;\n\n\tpublic static buildManifest(input: RunnerProcessJanitorManifestInput = {}): Record<string, unknown> {\n\t\treturn {\n\t\t\tjob_id: String(input.jobId || '').trim(),\n\t\t\towner_id: String(input.ownerId || '').trim(),\n\t\t\trunner_token: String(input.runnerToken || '').trim(),\n\t\t\tworkspace_root: String(input.workspaceRoot || '').trim(),\n\t\t\tproject_root: String(input.projectRoot || '').trim(),\n\t\t\tports: {\n\t\t\t\tclient: String(input.clientPort || '').trim(),\n\t\t\t\tserver: String(input.serverPort || '').trim(),\n\t\t\t\tmongo: String(input.mongoPort || '').trim(),\n\t\t\t\tinspect: String(input.inspectPort || '').trim()\n\t\t\t},\n\t\t\tstarted_at: new Date().toISOString(),\n\t\t\theartbeat_at: new Date().toISOString(),\n\t\t\tcleanup_status: 'running'\n\t\t};\n\t}\n\n\tpublic static buildShellLibrary(options: RunnerProcessJanitorScriptOptions = {}): string {\n\t\tconst mode = options.mode || 'runner';\n\t\tconst staleTtlSeconds = Number.isFinite(Number(options.staleTtlSeconds)) && Number(options.staleTtlSeconds) > 0\n\t\t\t? Number(options.staleTtlSeconds)\n\t\t\t: RunnerProcessJanitor.defaultStaleTtlSeconds;\n\t\tconst minAvailableMemoryMb = Number.isFinite(Number(options.minAvailableMemoryMb)) && Number(options.minAvailableMemoryMb) > 0\n\t\t\t? Number(options.minAvailableMemoryMb)\n\t\t\t: RunnerProcessJanitor.defaultMinAvailableMemoryMb;\n\t\tconst minAvailableDiskMb = Number.isFinite(Number(options.minAvailableDiskMb)) && Number(options.minAvailableDiskMb) > 0\n\t\t\t? Number(options.minAvailableDiskMb)\n\t\t\t: RunnerProcessJanitor.defaultMinAvailableDiskMb;\n\t\tconst prefix = mode === 'support' ? 'RESOLVEIO_SUPPORT_QA' : 'RESOLVEIO_RUNNER_QA';\n\t\tconst altPrefix = mode === 'support' ? 'RESOLVEIO_RUNNER_QA' : 'RESOLVEIO_SUPPORT_QA';\n\t\treturn [\n\t\t\t`RUNNER_JANITOR_STALE_TTL_SECONDS=\"\\${${prefix}_STALE_TTL_SECONDS:-\\${${altPrefix}_STALE_TTL_SECONDS:-${staleTtlSeconds}}}\"`,\n\t\t\t`RUNNER_JANITOR_MIN_AVAILABLE_MEMORY_MB=\"\\${${prefix}_MIN_AVAILABLE_MEMORY_MB:-\\${${altPrefix}_MIN_AVAILABLE_MEMORY_MB:-${minAvailableMemoryMb}}}\"`,\n\t\t\t`RUNNER_JANITOR_MIN_AVAILABLE_DISK_MB=\"\\${${prefix}_MIN_AVAILABLE_DISK_MB:-\\${${altPrefix}_MIN_AVAILABLE_DISK_MB:-${minAvailableDiskMb}}}\"`,\n\t\t\t'RUNNER_JANITOR_HEARTBEAT_PID=\"\"',\n\t\t\t'janitor_json_escape() { node -e \"process.stdout.write(JSON.stringify(process.argv[1] || \\'\\'))\" \"$1\"; }',\n\t\t\t'janitor_now_ms() { node -e \"console.log(Date.now())\"; }',\n\t\t\t'janitor_file_mtime_ms() { node -e \"const fs=require(\\'fs\\'); try{console.log(Math.floor(fs.statSync(process.argv[1]).mtimeMs||0))}catch(e){console.log(0)}\" \"$1\"; }',\n\t\t\t'janitor_bounded() {',\n\t\t\t' local seconds=\"$1\"',\n\t\t\t' shift || return 0',\n\t\t\t' if command -v timeout >/dev/null 2>&1; then',\n\t\t\t' timeout -k 2 \"$seconds\" \"$@\" 2>/dev/null || true',\n\t\t\t' else',\n\t\t\t' \"$@\" 2>/dev/null || true',\n\t\t\t' fi',\n\t\t\t'}',\n\t\t\t'janitor_manifest_path() { echo \"$ARTIFACT_DIR/runner-process-manifest.json\"; }',\n\t\t\t'janitor_status_path() { echo \"$ARTIFACT_DIR/runner-process-cleanup.json\"; }',\n\t\t\t'janitor_runner_token() {',\n\t\t\t` local token=\"\\${${prefix}_RUNNER_TOKEN:-\\${${altPrefix}_RUNNER_TOKEN:-}}\"`,\n\t\t\t' if [ -z \"$token\" ]; then token=\"$$-$(date +%s)-$RANDOM\"; fi',\n\t\t\t' echo \"$token\"',\n\t\t\t'}',\n\t\t\t'janitor_write_manifest() {',\n\t\t\t' local manifest token job owner',\n\t\t\t' manifest=\"$(janitor_manifest_path)\"',\n\t\t\t' token=\"$(janitor_runner_token)\"',\n\t\t\t` job=\"\\${${prefix}_JOB_ID:-\\${${altPrefix}_JOB_ID:-}}\"`,\n\t\t\t` owner=\"\\${${prefix}_OWNER_ID:-\\${${altPrefix}_OWNER_ID:-}}\"`,\n\t\t\t' cat > \"$manifest\" <<EOF',\n\t\t\t'{',\n\t\t\t' \"job_id\": $(janitor_json_escape \"$job\"),',\n\t\t\t' \"owner_id\": $(janitor_json_escape \"$owner\"),',\n\t\t\t' \"runner_token\": $(janitor_json_escape \"$token\"),',\n\t\t\t' \"pid\": $$,',\n\t\t\t' \"workspace_root\": $(janitor_json_escape \"${REPO_ROOT:-}\"),',\n\t\t\t' \"project_root\": $(janitor_json_escape \"$PROJECT_ROOT\"),',\n\t\t\t' \"ports\": {',\n\t\t\t' \"client\": $(janitor_json_escape \"$CLIENT_PORT\"),',\n\t\t\t' \"server\": $(janitor_json_escape \"$SERVER_PORT\"),',\n\t\t\t' \"mongo\": $(janitor_json_escape \"$MONGO_PORT\"),',\n\t\t\t' \"inspect\": $(janitor_json_escape \"$INSPECT_PORT\")',\n\t\t\t' },',\n\t\t\t' \"started_at\": $(janitor_json_escape \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"),',\n\t\t\t' \"heartbeat_at\": $(janitor_json_escape \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"),',\n\t\t\t' \"cleanup_status\": \"running\"',\n\t\t\t'}',\n\t\t\t'EOF',\n\t\t\t'}',\n\t\t\t'janitor_update_manifest_status() {',\n\t\t\t' local status=\"$1\"',\n\t\t\t' local manifest',\n\t\t\t' manifest=\"$(janitor_manifest_path)\"',\n\t\t\t' [ -f \"$manifest\" ] || return 0',\n\t\t\t' node - \"$manifest\" \"$status\" <<\\'JANITOR_UPDATE_STATUS\\'',\n\t\t\t'const fs = require(\"fs\");',\n\t\t\t'const [file, status] = process.argv.slice(2);',\n\t\t\t'try {',\n\t\t\t' const data = JSON.parse(fs.readFileSync(file, \"utf8\"));',\n\t\t\t' data.heartbeat_at = new Date().toISOString();',\n\t\t\t' data.cleanup_status = status;',\n\t\t\t' fs.writeFileSync(file, JSON.stringify(data, null, 2));',\n\t\t\t'} catch (error) {}',\n\t\t\t'JANITOR_UPDATE_STATUS',\n\t\t\t'}',\n\t\t\t'janitor_update_lock_heartbeat() {',\n\t\t\t' local lock_json=\"${LOCK_DIR:-}/lock.json\"',\n\t\t\t' [ -f \"$lock_json\" ] || return 0',\n\t\t\t' node - \"$lock_json\" <<\\'JANITOR_UPDATE_LOCK\\'',\n\t\t\t'const fs = require(\"fs\");',\n\t\t\t'const [file] = process.argv.slice(2);',\n\t\t\t'try {',\n\t\t\t' const data = JSON.parse(fs.readFileSync(file, \"utf8\"));',\n\t\t\t' data.heartbeat_at = new Date().toISOString();',\n\t\t\t' fs.writeFileSync(file, JSON.stringify(data, null, 2));',\n\t\t\t'} catch (error) {}',\n\t\t\t'JANITOR_UPDATE_LOCK',\n\t\t\t'}',\n\t\t\t'janitor_start_heartbeat() {',\n\t\t\t' local manifest',\n\t\t\t' manifest=\"$(janitor_manifest_path)\"',\n\t\t\t' ( while true; do janitor_update_manifest_status running; janitor_update_lock_heartbeat; sleep 15; done ) >/dev/null 2>&1 &',\n\t\t\t' RUNNER_JANITOR_HEARTBEAT_PID=\"$!\"',\n\t\t\t'}',\n\t\t\t'janitor_stop_heartbeat() {',\n\t\t\t' [ -n \"$RUNNER_JANITOR_HEARTBEAT_PID\" ] || return 0',\n\t\t\t' kill \"$RUNNER_JANITOR_HEARTBEAT_PID\" >/dev/null 2>&1 || true',\n\t\t\t' RUNNER_JANITOR_HEARTBEAT_PID=\"\"',\n\t\t\t'}',\n\t\t\t'janitor_lock_is_live() {',\n\t\t\t' local lock_json=\"$LOCK_DIR/lock.json\"',\n\t\t\t' [ -f \"$lock_json\" ] || return 1',\n\t\t\t' node - \"$lock_json\" \"$RUNNER_JANITOR_STALE_TTL_SECONDS\" <<\\'JANITOR_LOCK_LIVE\\'',\n\t\t\t'const fs = require(\"fs\");',\n\t\t\t'const [file, ttlRaw] = process.argv.slice(2);',\n\t\t\t'const ttlMs = Math.max(1, Number(ttlRaw || 600)) * 1000;',\n\t\t\t'try {',\n\t\t\t' const data = JSON.parse(fs.readFileSync(file, \"utf8\"));',\n\t\t\t' const pid = Number(data.pid || 0);',\n\t\t\t' const heartbeat = Date.parse(data.heartbeat_at || data.started_at || \"\");',\n\t\t\t' if (!pid || !heartbeat || Date.now() - heartbeat > ttlMs) process.exit(1);',\n\t\t\t' process.kill(pid, 0);',\n\t\t\t' process.exit(0);',\n\t\t\t'} catch (error) { process.exit(1); }',\n\t\t\t'JANITOR_LOCK_LIVE',\n\t\t\t'}',\n\t\t\t'janitor_write_lock() {',\n\t\t\t' cat > \"$LOCK_DIR/lock.json\" <<EOF',\n\t\t\t'{',\n\t\t\t' \"pid\": $$,',\n\t\t\t' \"project_root\": $(janitor_json_escape \"$PROJECT_ROOT\"),',\n\t\t\t' \"manifest\": $(janitor_json_escape \"$(janitor_manifest_path)\"),',\n\t\t\t' \"started_at\": $(janitor_json_escape \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"),',\n\t\t\t' \"heartbeat_at\": $(janitor_json_escape \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\")',\n\t\t\t'}',\n\t\t\t'EOF',\n\t\t\t'}',\n\t\t\t'janitor_check_resources() {',\n\t\t\t' local available_mb disk_mb',\n\t\t\t' available_mb=\"$(awk \\'/MemAvailable:/ {print int($2/1024)}\\' /proc/meminfo 2>/dev/null || echo 999999)\"',\n\t\t\t' disk_mb=\"$(df -Pm \"$PROJECT_ROOT\" 2>/dev/null | awk \\'NR==2 {print $4}\\' || echo 999999)\"',\n\t\t\t' if [ -n \"$available_mb\" ] && [ \"$available_mb\" -lt \"$RUNNER_JANITOR_MIN_AVAILABLE_MEMORY_MB\" ]; then',\n\t\t\t' echo \"ResolveIO AI runner QA resource guard: only ${available_mb}MB memory available; need ${RUNNER_JANITOR_MIN_AVAILABLE_MEMORY_MB}MB before Angular QA.\" | tee -a \"$ARTIFACT_DIR/runner.log\"',\n\t\t\t' return 7',\n\t\t\t' fi',\n\t\t\t' if [ -n \"$disk_mb\" ] && [ \"$disk_mb\" -lt \"$RUNNER_JANITOR_MIN_AVAILABLE_DISK_MB\" ]; then',\n\t\t\t' echo \"ResolveIO AI runner QA resource guard: only ${disk_mb}MB disk available; need ${RUNNER_JANITOR_MIN_AVAILABLE_DISK_MB}MB before Angular QA.\" | tee -a \"$ARTIFACT_DIR/runner.log\"',\n\t\t\t' return 7',\n\t\t\t' fi',\n\t\t\t' return 0',\n\t\t\t'}',\n\t\t\t'janitor_write_cleanup_status() {',\n\t\t\t' local status=\"$1\"',\n\t\t\t' local killed=\"$2\"',\n\t\t\t' cat > \"$(janitor_status_path)\" <<EOF',\n\t\t\t'{',\n\t\t\t' \"status\": $(janitor_json_escape \"$status\"),',\n\t\t\t' \"killed_processes\": ${killed:-0},',\n\t\t\t' \"project_root\": $(janitor_json_escape \"$PROJECT_ROOT\"),',\n\t\t\t' \"updated_at\": $(janitor_json_escape \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\")',\n\t\t\t'}',\n\t\t\t'EOF',\n\t\t\t'}',\n\t\t\t''\n\t\t].join('\\n');\n\t}\n}\n\nexport function buildRunnerProcessJanitorShellLibrary(options: RunnerProcessJanitorScriptOptions = {}): string {\n\treturn RunnerProcessJanitor.buildShellLibrary(options);\n}\n"]}
|