@osfactory/har 0.6.0 → 0.8.0

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.
@@ -104,12 +104,36 @@ If the repository contains more than one project or `.har` harness (check for `.
104
104
  - Keep a small `AGENT.md` / `CLAUDE.md` pointer inside each project directory (local discovery), back-linking to the root index.
105
105
  - One Cursor rule at repo root listing all harnesses — never one rule per project.
106
106
 
107
- ## Port allocation
107
+ ## Port allocation & shared services
108
108
 
109
- Agents run in parallel on configurable slot ids. Ports: `BASE + (AGENT_ID × 10)`.
109
+ Document ports in `.har/README.md` and configure them in `.har/harness.env`. Resolved values land in `.env.agent.<id>` and `.har/slots/agent-<id>.json` at launch never hardcode defaults in app code or tests.
110
+
111
+ ### Per-slot app ports (default / PM2 profile)
112
+
113
+ | Layer | Scope | Rule | On conflict |
114
+ |-------|-------|------|-------------|
115
+ | Frontend | Per slot | `HARNESS_FE_BASE_PORT + (AGENT_ID × HARNESS_PORT_STEP)` | Scan `STEP` increments within the slot lane |
116
+ | API | Per slot | `HARNESS_API_BASE_PORT + (AGENT_ID × STEP)` | Same scan policy |
117
+ | Node debug | Per slot | `9200 + (AGENT_ID × STEP)` | Same scan policy |
118
+
119
+ ### Shared infra ports (when listed in `HARNESS_INFRA_SERVICES`)
120
+
121
+ | Service | Default var | On conflict |
122
+ |---------|-------------|-------------|
123
+ | Postgres | `HARNESS_DB_PORT_DEFAULT` | Scan `HARNESS_DB_PORT_SCAN_START..END` |
124
+ | MinIO | `HARNESS_MINIO_PORT_DEFAULT` (+ console) | Scan configured ranges in `harness.env` |
125
+ | Mailpit | `HARNESS_MAILPIT_*_PORT_DEFAULT` | Scan configured ranges |
126
+ | Headless browser | `HARNESS_BROWSER_PORT_DEFAULT` | Scan configured ranges |
127
+
128
+ CLI and iOS profiles typically have no per-slot app ports; optional backends still use the shared-infra model.
110
129
 
111
130
  Set slot limits in `.har/stages.json` (`agentSlots`) and `.har/harness.env` (`HARNESS_AGENT_SLOT_MIN` / `HARNESS_AGENT_SLOT_MAX`) based on machine capacity.
112
131
 
132
+ ### Do not
133
+
134
+ - Hardcode `3000`, `15432`, `3847`, or other defaults in application code, tests, or agent docs
135
+ - Run raw `docker compose` for harness infrastructure — use `setup-infra.sh` / `launch.sh`
136
+
113
137
  ## Rules
114
138
 
115
139
  1. **Edit .har/ files directly** — no YAML runtime config
@@ -120,6 +144,7 @@ Set slot limits in `.har/stages.json` (`agentSlots`) and `.har/harness.env` (`HA
120
144
  6. **Do not edit manifest.json** — managed by har CLI
121
145
  7. **Run the cleanup checklist before finishing** — keep strictly what this repository needs:
122
146
  - compose file has only used services; `HARNESS_INFRA_SERVICES` matches exactly
147
+ - `harness.env` has port documentation vars for every enabled infra service; `.har/README.md` explains allocation and shared vs per-slot
123
148
  - `env.template` has no blocks for removed services; no dead branches left in scripts
124
149
  - unused harness files deleted (`deleteHarnessFile`), e.g. `attach.sh` when unused
125
150
  - `.har/README.md` file table matches the files that actually exist
@@ -132,10 +132,38 @@ Detailed agent instructions: commands, credentials, architecture, definition of
132
132
  ### `.har/env.template`, `setup-infra.sh`, `docker-compose.agent.yml`
133
133
  Adapt as needed for the project's infra.
134
134
 
135
- ### Port allocation
136
- Agents run in parallel on configurable slot ids. Ports: `BASE + (AGENT_ID × 10)`.
135
+ ### Port allocation & shared services
136
+
137
+ Document and configure ports in `.har/harness.env` and `.har/README.md`. Use the bundled template's port-allocation block as the contract — do not hardcode ports in app code or tests.
138
+
139
+ **Per-slot app ports** (default / PM2 profile only):
140
+
141
+ | Layer | Scope | Rule | On conflict |
142
+ |-------|-------|------|-------------|
143
+ | Frontend | Per slot | `HARNESS_FE_BASE_PORT + (AGENT_ID × HARNESS_PORT_STEP)` | Scan `STEP` increments within the slot lane |
144
+ | API | Per slot | `HARNESS_API_BASE_PORT + (AGENT_ID × STEP)` | Same scan policy |
145
+ | Node debug | Per slot | `9200 + (AGENT_ID × STEP)` | Same scan policy |
146
+
147
+ **Shared infra ports** (one per machine, all profiles when the service is in `HARNESS_INFRA_SERVICES`):
148
+
149
+ | Service | Default var | On conflict |
150
+ |---------|-------------|-------------|
151
+ | Postgres | `HARNESS_DB_PORT_DEFAULT` | Scan `HARNESS_DB_PORT_SCAN_START..END` |
152
+ | MinIO | `HARNESS_MINIO_PORT_DEFAULT` (+ console) | Scan configured ranges |
153
+ | Mailpit | `HARNESS_MAILPIT_*_PORT_DEFAULT` | Scan configured ranges |
154
+ | Headless browser | `HARNESS_BROWSER_PORT_DEFAULT` | Scan configured ranges |
155
+
137
156
  Set slot limits in `.har/stages.json` (`agentSlots`) and `.har/harness.env` (`HARNESS_AGENT_SLOT_MIN` / `HARNESS_AGENT_SLOT_MAX`) based on machine capacity.
138
157
 
158
+ **Port / infra checklist:**
159
+
160
+ - [ ] `.har/harness.env` has `HARNESS_FE_BASE_PORT`, `HARNESS_API_BASE_PORT`, `HARNESS_PORT_STEP` (default profile) or explains why they are absent (CLI/iOS)
161
+ - [ ] For each service in `HARNESS_INFRA_SERVICES`, matching `HARNESS_*_PORT_DEFAULT` and `SCAN_*` vars exist in `harness.env`
162
+ - [ ] `.har/README.md` has a **Port & shared services** section (allocation table, shared vs per-slot, do-not rules)
163
+ - [ ] App code and tests read ports from `.env.agent.<id>` / `agent-cli.sh` / slot registry — no hardcoded `3000`, `15432`, `3847`, etc.
164
+ - [ ] `env.template` and `CLAUDE.agent.md` show resolved ports via variables, not literals
165
+ - [ ] Monorepos with `har control up`: document slot-1 conflict if the app port overlaps (e.g. Mission Control on 3847)
166
+
139
167
  ### Git worktree
140
168
  `launch.sh` creates an isolated session worktree at `~/worktrees/<base>-<sha4>-har-agent-<id>-<rand4>` by default (`HARNESS_USE_WORKTREE=true`) and records it in `.har/slots/agent-<id>.json`. Agents should commit from that worktree, not the main checkout.
141
169
 
@@ -187,6 +215,8 @@ The boilerplate ships more than any single repository needs. Strip it down to st
187
215
  - [ ] If full seed/setup is skipped, `.har/` provides a minimal bootstrap or clearly documents why none is needed
188
216
  - [ ] All per-slot data stores are provisioned, not just the primary database
189
217
  - [ ] `.har/CLAUDE.agent.md` defines what "agent usable" means for this repo: login/API smoke, credentials, required default data, and known skipped dev-setup steps
218
+ - [ ] `.har/README.md` documents port allocation and shared-service model; `harness.env` has the port knobs for every enabled infra service
219
+ - [ ] No hardcoded default ports (`3000`, `15432`, `3847`, …) in app code, tests, or harness docs — use agent env / slot registry
190
220
 
191
221
  ## Rules
192
222
 
@@ -6,6 +6,8 @@ The harness already exists. Inspect what changed in the repo since the harness w
6
6
 
7
7
  **Do NOT** create a YAML config or JSON mapping file for runtime behavior. Put behavior directly in the harness scripts and templates.
8
8
 
9
+ {{MAINTAIN_BUNDLE_SECTION}}
10
+
9
11
  ## Step 1 — Inspect the repository
10
12
 
11
13
  Compare the current repo against the existing harness:
@@ -13,7 +15,7 @@ Compare the current repo against the existing harness:
13
15
  - Root manifests, CI, Docker, README
14
16
  - New or changed test, lint, build, migrate, or seed commands
15
17
  - New services, ports, or environment variables
16
- - Run `har env maintain` drift report (generator version, template checksum mismatches)
18
+ - Review `.har/maintain/drift-report.json` (generator version, template drift, **missing port documentation vars**)
17
19
 
18
20
  ## Step 2 — Update `.har/` files
19
21
 
@@ -64,9 +66,10 @@ When upgrading `@osfactory/har` or adopting new harness standards:
64
66
 
65
67
  **Generator 0.4.0 — primary app & shared infra services:**
66
68
 
67
- - Migrate `harness.env` from boolean `HARNESS_INFRA_*` flags to the `HARNESS_INFRA_SERVICES` list (space-separated compose service names, e.g. `"db mailpit"`) and add the `har_infra_enabled()` helper — copy both from the bundled template. Update every script that still tests `HARNESS_INFRA_POSTGRES`-style flags (`setup-infra.sh`, `launch.sh`, `teardown.sh`, `agent-cli.sh`) to use `har_infra_enabled <service>`.
69
+ - Migrate `harness.env` from boolean `HARNESS_INFRA_*` flags to the `HARNESS_INFRA_SERVICES` list (space-separated compose service names, e.g. `"db mailpit"`) and add the `har_infra_enabled()` helper — copy both from `.har/maintain/templates/harness.env`. Update every script that still tests `HARNESS_INFRA_POSTGRES`-style flags (`setup-infra.sh`, `launch.sh`, `teardown.sh`, `agent-cli.sh`) to use `har_infra_enabled <service>`.
68
70
  - Set `HARNESS_PRIMARY_APP` in `harness.env` to the ONE app agents modify. Ensure `ecosystem.agent.template.cjs` starts only that app's processes. Move any other in-repo services agents depend on but don't change to shared infra: compose services in `docker-compose.agent.yml` or an optional `.har/ecosystem.shared.config.cjs` (processes `har-shared-<name>`; `setup-infra.sh` starts it when present — resync `setup-infra.sh` from the template to get this hook).
69
71
  - Prune `docker-compose.agent.yml` to only the services this project uses; delete unused menu services and volumes.
72
+ - **Port & shared services:** ensure `.har/README.md` documents the allocation table and shared vs per-slot model; `harness.env` has every `HARNESS_*_PORT_*` var required for services in `HARNESS_INFRA_SERVICES` (copy missing vars from `.har/maintain/templates/harness.env` when drift reports them). Remove hardcoded ports from app code, tests, and `CLAUDE.agent.md`.
70
73
  - Run the **cleanup checklist**: no TODO placeholders, no env blocks for removed services in `env.template`, no dead script branches, `.har/README.md` file table matches the actual files, `CLAUDE.agent.md` shows only real URLs/ports and commands, unused files deleted.
71
74
 
72
75
  **Earlier standards:**
@@ -113,14 +113,48 @@ Always use `./.har/agent-cli.sh <id> ...` — never hardcoded ports.
113
113
 
114
114
  ## Architecture
115
115
 
116
- Each agent slot gets isolated ports: `BASE + (AGENT_ID × 10)`.
116
+ Each agent slot gets isolated app ports. Defaults follow `BASE + (AGENT_ID × HARNESS_PORT_STEP)`; when a default is busy, `launch.sh` scans the slot lane (`STEP` increments) and writes the resolved ports to `.env.agent.<id>` and `.har/slots/agent-<id>.json`.
117
117
 
118
118
  Configure how many slots your machine can run in parallel in `stages.json` (`agentSlots`) and `harness.env` (`HARNESS_AGENT_SLOT_MIN` / `HARNESS_AGENT_SLOT_MAX`). Keep both in sync.
119
119
 
120
- | Service | Agent 1 | Agent 2 |
121
- |---------|---------|---------|
120
+ | Service | Agent 1 (default) | Agent 2 (default) |
121
+ |---------|-------------------|-------------------|
122
122
  | Frontend | 3010 | 3020 |
123
123
  | API | 8010 | 8020 |
124
+ | Node debug | 9210 | 9220 |
125
+
126
+ ## Port & shared services
127
+
128
+ ### Port allocation
129
+
130
+ | Layer | Scope | Rule | On conflict |
131
+ |-------|-------|------|-------------|
132
+ | App — frontend | Per slot | `HARNESS_FE_BASE_PORT + (AGENT_ID × HARNESS_PORT_STEP)` | Scan `STEP` increments within the slot lane |
133
+ | App — API | Per slot | `HARNESS_API_BASE_PORT + (AGENT_ID × STEP)` | Same scan policy |
134
+ | Node debug | Per slot | `9200 + (AGENT_ID × STEP)` | Same scan policy |
135
+ | Shared Postgres | Per machine | `HARNESS_DB_PORT_DEFAULT` | Scan `HARNESS_DB_PORT_SCAN_START..END` |
136
+ | MinIO / S3 | Per machine | `HARNESS_MINIO_PORT_DEFAULT` (+ console port) | Scan configured ranges in `harness.env` |
137
+ | Mailpit | Per machine | `HARNESS_MAILPIT_*_PORT_DEFAULT` | Scan configured ranges |
138
+ | Headless browser | Per machine | `HARNESS_BROWSER_PORT_DEFAULT` | Scan configured ranges |
139
+
140
+ Resolved ports may differ from the formula when something else is already bound. Always use `./.har/agent-cli.sh <id>` or read `.har/slots/agent-<id>.json` — never hardcode `3010`, `15432`, etc. in app code or tests.
141
+
142
+ ### Shared vs per-slot
143
+
144
+ | Resource | Model | Configuration |
145
+ |----------|-------|---------------|
146
+ | Postgres | One shared container; per-slot database `agent_<id>` cloned from template | `HARNESS_INFRA_SERVICES="db"` |
147
+ | MinIO / S3 | One shared container; per-slot bucket `agent-<id>` | `HARNESS_INFRA_SERVICES="... minio"` |
148
+ | Mailpit, Redis, etc. | One shared container on a scanned host port | Listed in `HARNESS_INFRA_SERVICES` |
149
+ | Primary application | One PM2 ecosystem per slot (isolated ports) | `HARNESS_PRIMARY_APP`, `ecosystem.agent.template.cjs` |
150
+ | Internal supporting services | Shared across all slots | `docker-compose.agent.yml` or `ecosystem.shared.config.cjs` |
151
+
152
+ Shared infra starts once via `./.har/setup-infra.sh` (also run automatically by `launch.sh`). Per-slot databases are cloned in `launch.sh`.
153
+
154
+ ### Do not
155
+
156
+ - Hardcode default ports (`3000`, `15432`, `3847`, …) in application code, tests, or agent docs — read from `.env.agent.<id>`, `agent-cli.sh`, or the slot registry
157
+ - Run raw `docker compose` for harness infrastructure — use `setup-infra.sh` / `launch.sh` so ports are scanned and persisted in `.har/state/infra.env`
124
158
 
125
159
  ### Primary app vs shared services
126
160
 
@@ -156,6 +190,8 @@ work dir printed by launch, never in the main checkout.
156
190
  after explicit user approval.
157
191
  - `teardown` removes the worktree but **keeps the session branch** so you can push it
158
192
  or open a PR (`--delete-branch` to drop it).
193
+ - If launch fails after creating a worktree/env file, the registry records `status: failed`.
194
+ Resume without `--replace`: `har env launch <id> --resume` or `har env recover <id>`.
159
195
  - `har env complete <id>` finishes a session: full verify (recorded as a validation),
160
196
  then teardown — branch kept.
161
197
  - `--no-worktree` runs the slot from the repo root instead (single-agent mode).
@@ -17,51 +17,94 @@ COMMAND="${2:?Usage: agent-cli.sh <agent-id> <command> [args...]}"
17
17
 
18
18
  validate_agent_id "$AGENT_ID"
19
19
 
20
- FE_PORT=$(( HARNESS_FE_BASE_PORT + AGENT_ID * 10 ))
21
- API_PORT=$(( HARNESS_API_BASE_PORT + AGENT_ID * 10 ))
22
- DB_PORT="${AGENT_DB_PORT:-15432}"
20
+ load_agent_ports "$AGENT_ID" "$REPO_ROOT"
21
+ DB_PORT="${DB_PORT:-${AGENT_DB_PORT:-${HARNESS_DB_PORT_DEFAULT:-15432}}}"
23
22
  export PGPASSWORD="password"
24
23
 
24
+ PM2_SLOT_PREFIX="$(har_pm2_slot_prefix "$AGENT_ID")"
25
+
25
26
  case "$COMMAND" in
26
27
  status)
27
28
  ENV_FILE="$(resolve_agent_env_file "$AGENT_ID" "$REPO_ROOT" || true)"
28
29
  WORKTREE_DIR="$(existing_slot_worktree "$AGENT_ID")"
29
-
30
+ REG_FILE="$(slot_registry_file "$AGENT_ID")"
30
31
  PM2_RAW=$(npx --yes pm2 jlist 2>/dev/null || true)
31
32
  PM2_FOUND=false
33
+ PM2_FOREIGN=false
34
+ PM2_LEGACY=false
32
35
 
33
36
  if [ -n "$PM2_RAW" ]; then
37
+ set +e
34
38
  echo "$PM2_RAW" | node -e "
35
39
  const agentId = '${AGENT_ID}';
40
+ const project = '${HARNESS_PROJECT_NAME}';
41
+ const slotPrefix = 'har-' + project + '-agent-' + agentId + '-';
42
+ const legacyPrefix = 'agent-' + agentId + '-';
36
43
  let raw = '';
37
44
  process.stdin.on('data', c => raw += c);
38
45
  process.stdin.on('end', () => {
39
46
  try {
40
47
  const arr = JSON.parse(raw);
41
48
  if (!Array.isArray(arr)) process.exit(1);
42
- const procs = arr.filter(x => x.name && x.name.startsWith('agent-' + agentId + '-'));
43
- if (procs.length === 0) process.exit(1);
44
- console.log('Agent ' + agentId + ' processes:');
45
- procs.forEach(p => {
46
- const s = (p.pm2_env?.status || 'unknown').padEnd(10);
47
- const mem = Math.round((p.monit?.memory || 0) / 1024 / 1024) + 'MB';
48
- const cpu = (p.monit?.cpu || 0) + '%';
49
- console.log(' ' + p.name.padEnd(42) + s + ' mem=' + mem + ' cpu=' + cpu);
50
- });
49
+ const owned = arr.filter(x => x.name && x.name.startsWith(slotPrefix));
50
+ const foreignProject = arr.filter(x =>
51
+ x.name && x.name.startsWith('har-') && x.name.includes('-agent-' + agentId + '-') && !x.name.startsWith(slotPrefix));
52
+ const legacy = arr.filter(x => x.name && x.name.startsWith(legacyPrefix) && !x.name.startsWith('har-'));
53
+ if (owned.length > 0) {
54
+ console.log('Agent ' + agentId + ' processes (' + project + '):');
55
+ owned.forEach(p => {
56
+ const s = (p.pm2_env?.status || 'unknown').padEnd(10);
57
+ const mem = Math.round((p.monit?.memory || 0) / 1024 / 1024) + 'MB';
58
+ const cpu = (p.monit?.cpu || 0) + '%';
59
+ const cwd = p.pm2_env?.pm_cwd || p.pm2_env?.cwd || '';
60
+ console.log(' ' + p.name.padEnd(48) + s + ' mem=' + mem + ' cpu=' + cpu);
61
+ if (cwd) console.log(' cwd: ' + cwd);
62
+ });
63
+ process.exit(0);
64
+ }
65
+ if (foreignProject.length > 0 || legacy.length > 0) {
66
+ if (foreignProject.length > 0) {
67
+ console.log('ERROR: foreign PM2 processes match agent ' + agentId + ' but belong to another harness:');
68
+ foreignProject.forEach(p => console.log(' ' + p.name + ' cwd=' + (p.pm2_env?.pm_cwd || p.pm2_env?.cwd || 'unknown')));
69
+ }
70
+ if (legacy.length > 0) {
71
+ console.log('ERROR: legacy PM2 processes (pre project-scoping) match agent ' + agentId + ':');
72
+ legacy.forEach(p => console.log(' ' + p.name + ' cwd=' + (p.pm2_env?.pm_cwd || p.pm2_env?.cwd || 'unknown')));
73
+ }
74
+ process.exit(2);
75
+ }
76
+ process.exit(1);
51
77
  } catch {
52
78
  process.exit(1);
53
79
  }
54
80
  });
55
- " && PM2_FOUND=true
81
+ "
82
+ pm2_status=$?
83
+ set -e
84
+ case "$pm2_status" in
85
+ 0) PM2_FOUND=true ;;
86
+ 2) PM2_FOREIGN=true ;;
87
+ esac
88
+ fi
89
+
90
+ if [ "$PM2_FOREIGN" = true ]; then
91
+ exit 1
56
92
  fi
57
93
 
58
94
  if [ "$PM2_FOUND" = true ]; then
59
- REG_FILE="$(slot_registry_file "$AGENT_ID")"
60
95
  if [ ! -f "$REG_FILE" ]; then
61
- echo "Warning: slot registry missing at $REG_FILE verify/teardown may not resolve this slot."
96
+ echo "ERROR: slot registry missing at $REG_FILE but PM2 processes exist for this project." >&2
97
+ echo " Another harness may have been torn down incorrectly, or this slot was started outside launch.sh." >&2
98
+ exit 1
99
+ fi
100
+ REG_PROJECT="$(read_slot_field "$REG_FILE" projectName || true)"
101
+ if [ -n "$REG_PROJECT" ] && [ "$REG_PROJECT" != "$HARNESS_PROJECT_NAME" ]; then
102
+ echo "ERROR: slot registry projectName=${REG_PROJECT} does not match harness ${HARNESS_PROJECT_NAME}." >&2
103
+ exit 1
62
104
  fi
63
105
  if [ -z "$ENV_FILE" ]; then
64
- echo "Warning: .env.agent.${AGENT_ID} could not be resolved — relaunch with --replace when ready."
106
+ echo "ERROR: .env.agent.${AGENT_ID} could not be resolved — relaunch with --replace when ready." >&2
107
+ exit 1
65
108
  fi
66
109
  elif [ -n "$ENV_FILE" ] && [ -f "$ENV_FILE" ]; then
67
110
  echo "Agent ${AGENT_ID}: active (no PM2 processes)"
@@ -69,6 +112,8 @@ process.stdin.on('end', () => {
69
112
  source "$ENV_FILE" 2>/dev/null || true
70
113
  echo " Work dir: $(resolve_agent_work_dir "$ENV_FILE" "$AGENT_ID")"
71
114
  [ -n "$WORKTREE_DIR" ] && [ -d "$WORKTREE_DIR" ] && echo " Worktree: $WORKTREE_DIR"
115
+ echo " Frontend: http://localhost:${FE_PORT}"
116
+ echo " API: http://localhost:${API_PORT}"
72
117
  else
73
118
  echo "No active environment for agent ${AGENT_ID}"
74
119
  echo " Run: ./.har/launch.sh ${AGENT_ID}"
@@ -78,22 +123,23 @@ process.stdin.on('end', () => {
78
123
  logs)
79
124
  SERVICE="${3:-}"
80
125
  if [ -n "$SERVICE" ]; then
81
- npx pm2 logs "agent-${AGENT_ID}-${SERVICE}" --lines 100
126
+ npx pm2 logs "${PM2_SLOT_PREFIX}-${SERVICE}" --lines 100
82
127
  else
83
- npx pm2 logs --name "agent-${AGENT_ID}" --lines 100
128
+ npx pm2 logs --name "${PM2_SLOT_PREFIX}" --lines 100
84
129
  fi
85
130
  ;;
86
131
 
87
132
  restart)
88
133
  SERVICE="${3:-}"
89
134
  if [ -n "$SERVICE" ]; then
90
- npx pm2 restart "agent-${AGENT_ID}-${SERVICE}"
135
+ npx pm2 restart "${PM2_SLOT_PREFIX}-${SERVICE}"
91
136
  else
92
137
  npx pm2 jlist 2>/dev/null | node -e "
138
+ const prefix = '${PM2_SLOT_PREFIX}-';
93
139
  const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
94
- const names = d.filter(x => x.name.startsWith('agent-${AGENT_ID}-')).map(x => x.name);
140
+ const names = d.filter(x => x.name && x.name.startsWith(prefix)).map(x => x.name);
95
141
  names.forEach(n => require('child_process').execSync('npx pm2 restart ' + n, {stdio:'inherit'}));
96
- if (names.length === 0) console.log('No processes found for agent ${AGENT_ID}');
142
+ if (names.length === 0) console.log('No processes found for ${PM2_SLOT_PREFIX}');
97
143
  " 2>/dev/null || true
98
144
  fi
99
145
  ;;
@@ -122,9 +168,9 @@ try { console.log(JSON.stringify(JSON.parse(d), null, 2)); } catch { console.log
122
168
  echo "Frontend: http://localhost:${FE_PORT}"
123
169
  echo "API: http://localhost:${API_PORT}"
124
170
  har_infra_enabled db && echo "Database: agent_${AGENT_ID} @ localhost:${DB_PORT}"
125
- har_infra_enabled minio && echo "MinIO: http://localhost:19001"
126
- har_infra_enabled headless-browser && echo "Browser: http://localhost:13001"
127
- har_infra_enabled mailpit && echo "Mailpit: http://localhost:18025"
171
+ har_infra_enabled minio && echo "MinIO: http://localhost:${AGENT_MINIO_CONSOLE_PORT:-19001}"
172
+ har_infra_enabled headless-browser && echo "Browser: http://localhost:${AGENT_BROWSER_PORT:-13001}"
173
+ har_infra_enabled mailpit && echo "Mailpit: http://localhost:${AGENT_MAILPIT_WEB_PORT:-18025}"
128
174
  ;;
129
175
 
130
176
  reset-db)
@@ -160,7 +206,7 @@ LIMIT 20;" 2>/dev/null || echo "pg_stat_statements extension not available"
160
206
  ;;
161
207
 
162
208
  attach)
163
- SESSION="agent-${AGENT_ID}"
209
+ SESSION="$(har_tmux_session "$AGENT_ID")"
164
210
  if ! tmux has-session -t "$SESSION" 2>/dev/null; then
165
211
  echo "No tmux session found: $SESSION" >&2
166
212
  exit 1