@resolveio/server-lib 22.3.58 → 22.3.60

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resolveio/server-lib",
3
- "version": "22.3.58",
3
+ "version": "22.3.60",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "package": "./build_package.sh",
@@ -160,6 +160,9 @@ function buildResolveIORunnerLocalQaScript() {
160
160
  'else',
161
161
  ' KEEPALIVE="${RESOLVEIO_RUNNER_QA_KEEPALIVE:-${RESOLVEIO_SUPPORT_QA_KEEPALIVE:-false}}"',
162
162
  'fi',
163
+ 'case "${KEEPALIVE,,}" in',
164
+ ' true|1|yes|on) exec >> "$ARTIFACT_DIR/runner.log" 2>&1 ;;',
165
+ 'esac',
163
166
  'SERVER_STABLE_SECONDS="${RESOLVEIO_RUNNER_QA_SERVER_STABLE_SECONDS:-${RESOLVEIO_SUPPORT_QA_SERVER_STABLE_SECONDS:-20}}"',
164
167
  'LOCK_DIR="$ARTIFACT_DIR/.qa.lock"',
165
168
  'SERVER_PID=""',
@@ -193,6 +196,26 @@ function buildResolveIORunnerLocalQaScript() {
193
196
  ' done',
194
197
  ' sleep 1',
195
198
  '}',
199
+ 'kill_env_marked_processes() {',
200
+ ' [ -d /proc ] || return 0',
201
+ ' local job_id="${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}"',
202
+ ' local owner_id="${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}"',
203
+ ' local token="${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}"',
204
+ ' [ -n "$job_id$owner_id$token" ] || return 0',
205
+ ' for env_file in /proc/[0-9]*/environ; do',
206
+ ' pid="${env_file#/proc/}"',
207
+ ' pid="${pid%/environ}"',
208
+ ' skip_cleanup_pid "$pid" && continue',
209
+ ' [ "$pid" = "$$" ] && continue',
210
+ ' env_text="$(tr "\\0" "\\n" < "$env_file" 2>/dev/null || true)"',
211
+ ' [ -n "$env_text" ] || continue',
212
+ ' matched=0',
213
+ ' [ -n "$job_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$" && matched=1',
214
+ ' [ "$matched" = "0" ] && [ -n "$owner_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$" && matched=1',
215
+ ' [ "$matched" = "0" ] && [ -n "$token" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$" && matched=1',
216
+ ' [ "$matched" = "1" ] && kill_tree "$pid"',
217
+ ' done',
218
+ '}',
196
219
  'RUNNER_ANCESTOR_PIDS=" $$ ${PPID:-} "',
197
220
  'ancestor_pid="${PPID:-}"',
198
221
  'while [ -n "$ancestor_pid" ] && [ "$ancestor_pid" != "0" ] && [ "$ancestor_pid" != "1" ]; do',
@@ -212,6 +235,7 @@ function buildResolveIORunnerLocalQaScript() {
212
235
  ' done',
213
236
  ' for pass in 1 2 3; do',
214
237
  ' for port in "$CLIENT_PORT" "$SERVER_PORT" "$MONGO_PORT" "$INSPECT_PORT"; do kill_port_listeners "$port"; done',
238
+ ' kill_env_marked_processes',
215
239
  ' 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
240
  ' skip_cleanup_pid "$pid" && continue',
217
241
  ' kill_tree "$pid"',
@@ -235,6 +259,20 @@ function buildResolveIORunnerLocalQaScript() {
235
259
  ' for port in "$CLIENT_PORT" "$SERVER_PORT" "$MONGO_PORT" "$INSPECT_PORT"; do',
236
260
  ' if command -v lsof >/dev/null 2>&1 && [ -n "$(janitor_bounded 3 lsof -ti tcp:"$port")" ]; then found=1; fi',
237
261
  ' done',
262
+ ' if [ -d /proc ]; then',
263
+ ' job_id="${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}"',
264
+ ' owner_id="${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}"',
265
+ ' token="${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}"',
266
+ ' for env_file in /proc/[0-9]*/environ; do',
267
+ ' pid="${env_file#/proc/}"',
268
+ ' pid="${pid%/environ}"',
269
+ ' skip_cleanup_pid "$pid" && continue',
270
+ ' env_text="$(tr "\\0" "\\n" < "$env_file" 2>/dev/null || true)"',
271
+ ' [ -n "$job_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$" && found=1',
272
+ ' [ -n "$owner_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$" && found=1',
273
+ ' [ -n "$token" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$" && found=1',
274
+ ' done',
275
+ ' fi',
238
276
  ' 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
277
  ' skip_cleanup_pid "$pid" && continue',
240
278
  ' found=1',
@@ -253,13 +291,13 @@ function buildResolveIORunnerLocalQaScript() {
253
291
  ' cleanup_project_processes',
254
292
  ' janitor_update_manifest_status cleanup_complete',
255
293
  ' janitor_write_cleanup_status complete 0',
256
- ' rmdir "$LOCK_DIR" >/dev/null 2>&1 || true',
294
+ ' rm -rf "$LOCK_DIR" >/dev/null 2>&1 || true',
257
295
  '}',
258
296
  'trap cleanup EXIT',
259
297
  'detach_keepalive() {',
260
298
  ' janitor_update_manifest_status ready_keepalive',
261
299
  ' janitor_write_cleanup_status ready_keepalive 0',
262
- ' rmdir "$LOCK_DIR" >/dev/null 2>&1 || true',
300
+ ' rm -rf "$LOCK_DIR" >/dev/null 2>&1 || true',
263
301
  ' trap - EXIT',
264
302
  ' disown >/dev/null 2>&1 || true',
265
303
  ' echo "ResolveIO AI runner QA keepalive detached at $CLIENT_URL; stop with $TOOLS_DIR/stop-local-qa.sh $PROJECT_ROOT."',
@@ -359,7 +397,7 @@ function buildResolveIORunnerLocalQaScript() {
359
397
  ' fi',
360
398
  ' echo "ResolveIO AI runner QA lock is stale for $PROJECT_ROOT; cleaning scoped local QA processes before retrying." | tee "$ARTIFACT_DIR/runner.log"',
361
399
  ' cleanup_project_processes',
362
- ' rmdir "$LOCK_DIR" >/dev/null 2>&1 || true',
400
+ ' rm -rf "$LOCK_DIR" >/dev/null 2>&1 || true',
363
401
  ' if ! mkdir "$LOCK_DIR" 2>/dev/null; then',
364
402
  ' 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
403
  ' exit 6',
@@ -469,6 +507,26 @@ function buildResolveIORunnerLocalQaStopperScript() {
469
507
  ' done',
470
508
  ' sleep 1',
471
509
  '}',
510
+ 'kill_env_marked_processes() {',
511
+ ' [ -d /proc ] || return 0',
512
+ ' local job_id="${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}"',
513
+ ' local owner_id="${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}"',
514
+ ' local token="${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}"',
515
+ ' [ -n "$job_id$owner_id$token" ] || return 0',
516
+ ' for env_file in /proc/[0-9]*/environ; do',
517
+ ' pid="${env_file#/proc/}"',
518
+ ' pid="${pid%/environ}"',
519
+ ' skip_cleanup_pid "$pid" && continue',
520
+ ' [ "$pid" = "$$" ] && continue',
521
+ ' env_text="$(tr "\\0" "\\n" < "$env_file" 2>/dev/null || true)"',
522
+ ' [ -n "$env_text" ] || continue',
523
+ ' matched=0',
524
+ ' [ -n "$job_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$" && matched=1',
525
+ ' [ "$matched" = "0" ] && [ -n "$owner_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$" && matched=1',
526
+ ' [ "$matched" = "0" ] && [ -n "$token" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$" && matched=1',
527
+ ' [ "$matched" = "1" ] && kill_tree "$pid" && killed_count=$((killed_count + 1))',
528
+ ' done',
529
+ '}',
472
530
  'RUNNER_ANCESTOR_PIDS=" $$ ${PPID:-} "',
473
531
  'ancestor_pid="${PPID:-}"',
474
532
  'while [ -n "$ancestor_pid" ] && [ "$ancestor_pid" != "0" ] && [ "$ancestor_pid" != "1" ]; do',
@@ -490,6 +548,7 @@ function buildResolveIORunnerLocalQaStopperScript() {
490
548
  'done',
491
549
  'for pass in 1 2 3; do',
492
550
  ' for port in "$CLIENT_PORT" "$SERVER_PORT" "$MONGO_PORT" "$INSPECT_PORT"; do kill_port_listeners "$port"; done',
551
+ ' kill_env_marked_processes',
493
552
  ' 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
553
  ' skip_cleanup_pid "$pid" && continue',
495
554
  ' kill_tree "$pid"',
@@ -514,6 +573,20 @@ function buildResolveIORunnerLocalQaStopperScript() {
514
573
  ' for port in "$CLIENT_PORT" "$SERVER_PORT" "$MONGO_PORT" "$INSPECT_PORT"; do',
515
574
  ' if command -v lsof >/dev/null 2>&1 && [ -n "$(janitor_bounded 3 lsof -ti tcp:"$port")" ]; then found=1; fi',
516
575
  ' done',
576
+ ' if [ -d /proc ]; then',
577
+ ' job_id="${RESOLVEIO_RUNNER_QA_JOB_ID:-${RESOLVEIO_SUPPORT_QA_JOB_ID:-}}"',
578
+ ' owner_id="${RESOLVEIO_RUNNER_QA_OWNER_ID:-${RESOLVEIO_SUPPORT_QA_OWNER_ID:-}}"',
579
+ ' token="${RESOLVEIO_RUNNER_QA_RUNNER_TOKEN:-${RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN:-}}"',
580
+ ' for env_file in /proc/[0-9]*/environ; do',
581
+ ' pid="${env_file#/proc/}"',
582
+ ' pid="${pid%/environ}"',
583
+ ' skip_cleanup_pid "$pid" && continue',
584
+ ' env_text="$(tr "\\0" "\\n" < "$env_file" 2>/dev/null || true)"',
585
+ ' [ -n "$job_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_JOB_ID|RESOLVEIO_SUPPORT_QA_JOB_ID)=$job_id$" && found=1',
586
+ ' [ -n "$owner_id" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_OWNER_ID|RESOLVEIO_SUPPORT_QA_OWNER_ID)=$owner_id$" && found=1',
587
+ ' [ -n "$token" ] && echo "$env_text" | grep -Eq "^(RESOLVEIO_RUNNER_QA_RUNNER_TOKEN|RESOLVEIO_SUPPORT_QA_RUNNER_TOKEN)=$token$" && found=1',
588
+ ' done',
589
+ ' fi',
517
590
  ' 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
591
  ' skip_cleanup_pid "$pid" && continue',
519
592
  ' found=1',
@@ -521,7 +594,7 @@ function buildResolveIORunnerLocalQaStopperScript() {
521
594
  ' [ "$found" = "0" ] && break',
522
595
  ' sleep 2',
523
596
  'done',
524
- 'rmdir "$ARTIFACT_DIR/.qa.lock" >/dev/null 2>&1 || true',
597
+ 'rm -rf "$ARTIFACT_DIR/.qa.lock" >/dev/null 2>&1 || true',
525
598
  'janitor_update_manifest_status stopped',
526
599
  'janitor_write_cleanup_status stopped "$killed_count"',
527
600
  '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'case \"${KEEPALIVE,,}\" in',\n\t\t' true|1|yes|on) exec >> \"$ARTIFACT_DIR/runner.log\" 2>&1 ;;',\n\t\t'esac',\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"]}