@osfactory/har 0.36.2 → 0.38.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.
@@ -153,7 +153,7 @@ Document and configure ports in `.har/harness.env` and `.har/README.md`. Use the
153
153
  | Mailpit | `HARNESS_MAILPIT_*_PORT_DEFAULT` | Scan configured ranges |
154
154
  | Headless browser | `HARNESS_BROWSER_PORT_DEFAULT` | Scan configured ranges |
155
155
 
156
- Set slot limits in `.har/stages.json` (`agentSlots`) based on machine capacity. `har env maintain --finalize` syncs legacy `HARNESS_AGENT_SLOT_*` exports in `harness.env`.
156
+ Onboarding may set an initial `agentSlots.max` in `.har/stages.json`. After you know the real stack cost, **re-tune** that limit for machine capacity (lighter stack → you can raise; heavy Docker/DB → lower). `har env maintain --finalize` syncs legacy `HARNESS_AGENT_SLOT_*` exports in `harness.env`.
157
157
 
158
158
  **Port / infra checklist:**
159
159
 
@@ -11,7 +11,7 @@ har env status # or ./.har/agent-cli.sh 1 status
11
11
  ```
12
12
 
13
13
  - One slot ≈ one task. If slot 1 is occupied by unrelated work, use slot 2+ (limit: `agentSlots` in `.har/stages.json`).
14
- - Never replace an occupied slot (`--replace`/`--force`) without the user explicitly approving it.
14
+ - Occupied slots always block a new launch: run `har env teardown <id>` (or `complete <id>`) first, then launch again.
15
15
 
16
16
  ## 2. Launch — BEFORE editing any file
17
17
 
@@ -19,7 +19,7 @@ har env status # or ./.har/agent-cli.sh 1 status
19
19
  har env launch <id> # or ./.har/launch.sh <id>
20
20
  ```
21
21
 
22
- Launch creates a fresh session worktree from HEAD and prints its **work dir** (also recorded in `.har/slots/agent-<id>.json`, path like `~/worktrees/<base>-<sha4>-har-agent-<id>-<rand4>`). If a previous launch failed partway, retry with `--resume` instead of replacing.
22
+ Launch creates a fresh session worktree from HEAD and prints its **work dir** (also recorded in `.har/slots/agent-<id>.json`, path like `~/worktrees/<base>-<sha4>-har-agent-<id>-<rand4>`). If a previous launch failed partway, retry with `--resume` instead of starting fresh.
23
23
 
24
24
  If the task already has a stable issue or ticket ID, bind it without changing the
25
25
  methodology that produced it:
@@ -15,20 +15,19 @@ work around it with ad-hoc commands.
15
15
 
16
16
  ## Before making changes
17
17
 
18
- 1. **On the main checkout, switch to the intended base** (usually `main`) — every launch creates a worktree from that HEAD. `--replace` does not select the base.
18
+ 1. **On the main checkout, switch to the intended base** (usually `main`) — every launch creates a worktree from that HEAD.
19
19
  2. **Launch your slot FIRST — before editing any file.** Use `har_launch_environment` with `agentId: 1` (MCP) or `har env launch 1`. Launch always creates a **fresh session worktree** and returns its **work dir**. If the task binds to a durable external work item (a GitHub issue, a Linear ticket, a task from the user's tracker), pass `workUnitId` + `title` on launch (`har_launch_environment` `workUnitId`/`title`, or CLI `--work-id`/`--work-title`) so Mission Control attributes usage and events to that work item instead of leaving them session-only.
20
20
  3. **Make ALL file edits under the returned work dir — never in the main checkout.** The work dir looks like `~/worktrees/<base-branch>-<sha4>-har-agent-<id>-<rand4>` (recorded in `.har/slots/agent-<id>.json`). Edits there hot-reload in the running slot; use `har env restart <id>` or `./.har/agent-cli.sh <id> restart` if a change doesn't take.
21
21
  4. Read [AGENT.md](./AGENT.md) at the repo root
22
22
  5. Read [.har/README.md](./.har/README.md) and [.har/stages.json](./.har/stages.json)
23
23
  6. After launch, read [.har/CLAUDE.agent.md](./.har/CLAUDE.agent.md) for slot URLs and definition of done
24
24
 
25
- When a slot is occupied: prefer `complete` / `teardown` if the previous task is finished, then launch. Use `--replace` / `confirmReplace=true` only to reuse the same slot id immediately (destroys the old worktree; the old branch is kept if committed). If the previous session has uncommitted changes, you must also pass `force`/`--force` after **explicit user approval** — never autonomously. Failed launches: `--resume` / `recover`, not `--replace`.
25
+ When a slot is occupied: it always blocks a new launch. Run `complete` / `teardown` on that slot id first, then `launch` again. Occupied slots do not choose the base branch either way — switch the main checkout to your intended base before launching. Failed/starting launches can be resumed instead: `--resume` / `recover`.
26
26
 
27
27
  ### Slot safety (read before launch)
28
28
 
29
29
  - **One slot ≈ one task** — do not share slot 1 across unrelated chats; use slot 2+ for parallel work.
30
30
  - **Call `har_get_status` / `har env status`** before launch when a slot may be occupied.
31
- - **Never** set `force`/`confirmReplace` without the user explicitly approving replacement.
32
31
  - **Commit early and often** in the session worktree — teardown keeps the branch, not uncommitted work.
33
32
  - **Gitignored paths are ephemeral** (`state/`, `runs/`, local clones) — they are lost when the worktree is removed.
34
33
  - With telemetry on (default, including prompts), Mission Control derives session purpose from the first captured user prompt.
@@ -12,7 +12,7 @@
12
12
  | **Database** | `agent_${AGENT_ID}` on `localhost:${DB_PORT}` (when `db` is in `HARNESS_INFRA_SERVICES`) |
13
13
  | **Work dir** | Fresh session worktree per launch — see the launch output or `.har/slots/agent-${AGENT_ID}.json` |
14
14
 
15
- **Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. Edits there hot-reload in the running slot; use `./.har/agent-cli.sh ${AGENT_ID} restart` if a change doesn't take. Relaunching replaces the session (branch kept) and requires explicit confirmation; dirty worktrees also need `--force` after user approval.
15
+ **Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. Edits there hot-reload in the running slot; use `./.har/agent-cli.sh ${AGENT_ID} restart` if a change doesn't take. An occupied slot always blocks a new launch — run `./.har/teardown.sh ${AGENT_ID}` (or `complete`) first, then launch again.
16
16
 
17
17
  This slot runs **only the primary application** (`HARNESS_PRIMARY_APP`). External dependencies and any supporting services run once, shared by all slots — `setup-infra.sh` manages them; never start them yourself.
18
18
 
@@ -187,13 +187,13 @@ Switch that checkout to your intended base before launch. The session is recorde
187
187
  the work dir through it. Make ALL file edits under the work dir printed by launch,
188
188
  never in the main checkout.
189
189
 
190
- - Prefer `complete` / `teardown` when a task is finished, then launch.
191
- - `--replace` destroys the previous worktree (requires confirmation); it does **not**
192
- choose `main`. Uncommitted changes also need `--force` after explicit user approval.
190
+ - Occupied slots always block a new launch: `har env complete <id>` (or `teardown <id>`),
191
+ then `har env launch <id>`. A new launch never chooses `main` for you — switch the
192
+ main checkout to your intended base first.
193
193
  - `teardown` removes the worktree but **keeps the session branch** so you can push it
194
194
  or open a PR (`--delete-branch` to drop it).
195
195
  - If launch fails after creating a worktree/env file, the registry records `status: failed`.
196
- Resume without `--replace`: `har env launch <id> --resume` or `har env recover <id>`.
196
+ Resume it instead of starting fresh: `har env launch <id> --resume` or `har env recover <id>`.
197
197
  - `har env complete <id>` finishes a session: full verify (recorded as a validation),
198
198
  then teardown — branch kept.
199
199
  - `--no-worktree` runs the slot from the repo root instead (single-agent mode).
@@ -103,7 +103,7 @@ process.stdin.on('end', () => {
103
103
  exit 1
104
104
  fi
105
105
  if [ -z "$ENV_FILE" ]; then
106
- echo "ERROR: .env.agent.${AGENT_ID} could not be resolved — relaunch with --replace when ready." >&2
106
+ echo "ERROR: .env.agent.${AGENT_ID} could not be resolved — teardown this slot, then relaunch." >&2
107
107
  exit 1
108
108
  fi
109
109
  elif [ -n "$ENV_FILE" ] && [ -f "$ENV_FILE" ]; then
@@ -53,12 +53,18 @@ validate_agent_id() {
53
53
  fi
54
54
 
55
55
  if [[ -z "$id" ]] || ! [[ "$id" =~ ^[0-9]+$ ]]; then
56
- echo "Error: agent-id must be a positive integer between ${min} and ${max}" >&2
56
+ echo "Error: invalid agent slot id: ${id:-"(missing)"}" >&2
57
+ echo " Must be an integer between ${min} and ${max} (see .har/stages.json → agentSlots)" >&2
57
58
  exit 1
58
59
  fi
59
60
 
60
61
  if (( id < min || id > max )); then
61
- echo "Error: agent-id must be between ${min} and ${max}" >&2
62
+ echo "Error: invalid agent slot id: ${id}" >&2
63
+ echo " Valid slots: ${min}–${max} (configured in .har/stages.json → agentSlots)" >&2
64
+ echo " Run: har env status # see which slots are in use" >&2
65
+ if (( id > max )); then
66
+ echo " To allow slot ${id}, raise agentSlots.max in .har/stages.json." >&2
67
+ fi
62
68
  exit 1
63
69
  fi
64
70
  }
@@ -239,7 +245,7 @@ har_resume_session_assignments() {
239
245
  local status
240
246
  status="$(slot_session_status "$agent_id")"
241
247
  echo "echo \"ERROR: slot ${agent_id} is not resumable (status=${status:-none}; need failed or starting).\" >&2" >&2
242
- echo "echo \" Use a normal launch, or --replace to start a fresh session.\" >&2"
248
+ echo "echo \" Free the slot first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}.\" >&2"
243
249
  echo 'exit 2'
244
250
  return 1
245
251
  fi
@@ -320,36 +326,27 @@ har_regenerate_agent_env_file() {
320
326
  fi
321
327
  }
322
328
 
323
- # har_launch_preflight <agent_id> <force> <replace> [resume]
324
- # Exit 0 when ready; 2 when occupied/replace required; 1 on machine blockers.
329
+ # har_launch_preflight <agent_id> [resume]
330
+ # Exit 0 when ready; 2 when occupied (free the slot first) or not resumable; 1 on machine blockers.
325
331
  # Sets FE_PORT/API_PORT/DEBUG_PORT when this harness uses PM2.
326
332
  har_launch_preflight() {
327
333
  local agent_id="$1"
328
- local force="${2:-false}"
329
- local replace="${3:-false}"
330
- local resume="${4:-false}"
334
+ local resume="${2:-false}"
331
335
 
332
336
  if [ "$resume" = true ]; then
333
337
  if ! slot_is_resumable "$agent_id"; then
334
338
  local status
335
339
  status="$(slot_session_status "$agent_id")"
336
340
  echo "ERROR: slot ${agent_id} is not resumable (status=${status:-none}; need failed or starting)." >&2
337
- echo " Use a normal launch, or --replace to start a fresh session." >&2
341
+ echo " Free the slot first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}." >&2
338
342
  return 2
339
343
  fi
340
344
  echo "==> [agent-${agent_id}] Resuming partial launch (worktree and deps preserved)..." >&2
341
345
  elif slot_is_occupied "$agent_id"; then
342
- if [ "$replace" != true ] && [ "${HAR_CONFIRM_REPLACE:-}" != "1" ]; then
343
- print_slot_replace_warning "$agent_id"
344
- echo "ERROR: slot ${agent_id} is occupied — pass --replace to proceed." >&2
345
- return 2
346
- fi
347
- local wt
348
- wt="$(existing_slot_worktree "$agent_id")"
349
- if [ -n "$wt" ] && slot_worktree_dirty "$wt" && [ "$force" != true ]; then
350
- echo "ERROR: dirty worktree requires --force after explicit user approval." >&2
351
- return 2
352
- fi
346
+ print_slot_occupied_warning "$agent_id"
347
+ echo "ERROR: slot ${agent_id} is occupied." >&2
348
+ echo " Free it first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}." >&2
349
+ return 2
353
350
  fi
354
351
 
355
352
  if har_harness_uses_pm2; then
@@ -511,8 +508,8 @@ slot_dirty_summary() {
511
508
  echo "dirty (${count} changed)"
512
509
  }
513
510
 
514
- # Print a warning before replacing an occupied slot (stdout — visible to agents).
515
- print_slot_replace_warning() {
511
+ # Print details about an occupied slot (stdout — visible to agents).
512
+ print_slot_occupied_warning() {
516
513
  local agent_id="$1"
517
514
  local reg wt branch work_dir created status last_error dirty_summary head
518
515
  reg="$(slot_registry_file "$agent_id")"
@@ -528,7 +525,7 @@ print_slot_replace_warning() {
528
525
  fi
529
526
 
530
527
  echo "" >&2
531
- echo "⚠ Slot ${agent_id} is already in use — replacing will REMOVE the worktree." >&2
528
+ echo "⚠ Slot ${agent_id} is already in use." >&2
532
529
  [ -n "$wt" ] && echo " Worktree: ${wt}" >&2
533
530
  [ -n "$work_dir" ] && echo " Work dir: ${work_dir}" >&2
534
531
  [ -n "$branch" ] && echo " Branch: ${branch}${head:+ @ ${head}}" >&2
@@ -544,54 +541,21 @@ print_slot_replace_warning() {
544
541
  base_sha="$(git -C "$base_root" rev-parse --short HEAD 2>/dev/null || true)"
545
542
  echo " New session will be based on: ${base_branch}${base_sha:+ @ ${base_sha}}" >&2
546
543
  echo " (HEAD of ${base_root})" >&2
547
- echo " --replace does not choose this base — switch that checkout first for a new unrelated task." >&2
544
+ echo " Switch that checkout to your intended base before launching a new session." >&2
548
545
  echo "" >&2
549
546
  fi
550
- echo " Prefer complete/teardown when the previous task is finished, then launch." >&2
551
- echo " The session branch is kept only if you committed. Gitignored paths" >&2
552
- echo " (state/, runs/, local clones, .env.local) are NOT preserved." >&2
553
- echo "" >&2
554
- }
555
-
556
- # Require explicit confirmation before replacing an occupied slot.
557
- # $1 agent_id $2 force(true/false) $3 replace(true/false)
558
- # Exits 1 when replacement must not proceed.
559
- require_slot_replace_confirm() {
560
- local agent_id="$1"
561
- local force="$2"
562
- local replace="$3"
563
- local wt
564
-
565
- slot_is_occupied "$agent_id" || return 0
566
-
567
- print_slot_replace_warning "$agent_id"
568
- wt="$(existing_slot_worktree "$agent_id")"
569
-
570
- if [ -n "$wt" ] && slot_worktree_dirty "$wt" && [ "$force" != true ]; then
571
- echo "ERROR: previous session for slot ${agent_id} has uncommitted changes." >&2
572
- echo " Commit them in the worktree (branch is kept on teardown), or pass --force to discard." >&2
573
- echo " --force destroys uncommitted work — only use after explicit user approval." >&2
574
- exit 1
575
- fi
576
-
577
- if [ "$replace" = true ] || [ "${HAR_CONFIRM_REPLACE:-}" = "1" ]; then
578
- return 0
579
- fi
580
-
581
- if [ -t 0 ] && [ -t 1 ]; then
582
- local answer
583
- read -r -p "Replace slot ${agent_id}? Uncommitted work is lost unless committed. [y/N] " answer
584
- if [[ "$answer" =~ ^[Yy]$ ]]; then
585
- return 0
586
- fi
587
- echo "Aborted — slot ${agent_id} left unchanged." >&2
588
- exit 2
547
+ if [ "$dirty_summary" != "clean" ] && [ "$dirty_summary" != "unknown" ]; then
548
+ echo " Commit or discard changes in the worktree, then teardown/complete the slot:" >&2
549
+ else
550
+ echo " Free the slot, then launch again:" >&2
589
551
  fi
590
-
591
- echo "ERROR: slot ${agent_id} is occupied." >&2
592
- echo " Pass --replace (or set HAR_CONFIRM_REPLACE=1) after reviewing the warning above." >&2
593
- echo " If the worktree is dirty, also pass --force (only after explicit user approval)." >&2
594
- exit 2
552
+ echo " har env complete ${agent_id} # or: har env teardown ${agent_id}" >&2
553
+ echo " har env launch ${agent_id}" >&2
554
+ echo "" >&2
555
+ echo " complete/teardown remove the worktree. The session branch is kept only if" >&2
556
+ echo " you committed. Gitignored paths (state/, runs/, local clones, .env.local)" >&2
557
+ echo " are NOT preserved." >&2
558
+ echo "" >&2
595
559
  }
596
560
 
597
561
  git_common_dir() {
@@ -2,8 +2,9 @@
2
2
  # Launches a single agent instance with isolated ports, database, and PM2 processes.
3
3
  # Every launch starts a FRESH session: any previous session for the slot is torn
4
4
  # down (its branch is kept) and a new suffixed worktree is created from HEAD.
5
+ # Occupied slots always block: run ./.har/teardown.sh (or complete it) first, then relaunch.
5
6
  #
6
- # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--claude] [--replace] [--force] [--resume]
7
+ # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--claude] [--resume]
7
8
  set -euo pipefail
8
9
 
9
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -17,8 +18,6 @@ source "$SCRIPT_DIR/agent-slot.sh"
17
18
  AGENT_ID="${1:-}"
18
19
  USE_WORKTREE="${HARNESS_USE_WORKTREE:-true}"
19
20
  USE_CLAUDE=false
20
- FORCE=false
21
- REPLACE=false
22
21
  RESUME=false
23
22
  WORK_UNIT_ID="${HAR_WORK_UNIT_ID:-}"
24
23
  ATTEMPT_ID="${HAR_ATTEMPT_ID:-}"
@@ -28,8 +27,6 @@ for arg in "$@"; do
28
27
  --no-worktree) USE_WORKTREE=false ;;
29
28
  --worktree) USE_WORKTREE=true ;;
30
29
  --claude) USE_CLAUDE=true ;;
31
- --replace) REPLACE=true ;;
32
- --force) FORCE=true ;;
33
30
  --resume) RESUME=true ;;
34
31
  --work-id=*) WORK_UNIT_ID="${arg#*=}" ;;
35
32
  --attempt-id=*) ATTEMPT_ID="${arg#*=}" ;;
@@ -44,7 +41,7 @@ export SLOT_ATTEMPT_ID="$ATTEMPT_ID"
44
41
 
45
42
  if [[ -z "$AGENT_ID" ]]; then
46
43
  har_load_agent_slot_limits
47
- echo "Usage: $0 <agent-id> [--no-worktree] [--claude] [--replace] [--force] [--resume] " >&2
44
+ echo "Usage: $0 <agent-id> [--no-worktree] [--claude] [--resume]" >&2
48
45
  echo " agent-id must be between ${HARNESS_AGENT_SLOT_MIN} and ${HARNESS_AGENT_SLOT_MAX}" >&2
49
46
  exit 1
50
47
  fi
@@ -63,7 +60,7 @@ ENV_FILE=""
63
60
  REGISTRY_WRITTEN=false
64
61
 
65
62
  if [ "$RESUME" = true ]; then
66
- har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE" true || exit $?
63
+ har_launch_preflight "$AGENT_ID" true || exit $?
67
64
  eval "$(har_resume_session_assignments "$AGENT_ID")"
68
65
  REGISTRY_WRITTEN=true
69
66
  mark_slot_failed() {
@@ -92,14 +89,8 @@ if [ "$RESUME" = true ]; then
92
89
  trap mark_slot_failed EXIT
93
90
  else
94
91
  # Preflight before worktree/install — ports, foreign PM2, Docker, occupied slot.
95
- har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE" || exit $?
96
-
97
- # Replace any previous session for this slot — requires explicit confirmation.
98
- if slot_is_occupied "$AGENT_ID"; then
99
- require_slot_replace_confirm "$AGENT_ID" "$FORCE" "$REPLACE"
100
- log "Replacing previous session for slot ${AGENT_ID}..."
101
- "$SCRIPT_DIR/teardown.sh" "$AGENT_ID" >&2
102
- fi
92
+ # Occupied slots always block here: teardown/complete first, then relaunch.
93
+ har_launch_preflight "$AGENT_ID" || exit $?
103
94
  fi
104
95
 
105
96
  if har_harness_uses_pm2; then
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # Launch readiness gate — run before launch.sh or standalone.
3
- # Usage: ./.har/preflight.sh <agent-id> [--replace] [--force]
3
+ # Usage: ./.har/preflight.sh <agent-id>
4
4
  # JSON: har env preflight <id> --json
5
5
  set -euo pipefail
6
6
 
@@ -13,24 +13,15 @@ source "$SCRIPT_DIR/harness.env"
13
13
  source "$SCRIPT_DIR/agent-slot.sh"
14
14
 
15
15
  AGENT_ID="${1:-}"
16
- FORCE=false
17
- REPLACE=false
18
-
19
- for arg in "$@"; do
20
- case "$arg" in
21
- --replace) REPLACE=true ;;
22
- --force) FORCE=true ;;
23
- esac
24
- done
25
16
 
26
17
  if [[ -z "$AGENT_ID" ]]; then
27
- echo "Usage: $0 <agent-id> [--replace] [--force]" >&2
18
+ echo "Usage: $0 <agent-id>" >&2
28
19
  exit 1
29
20
  fi
30
21
 
31
22
  validate_agent_id "$AGENT_ID"
32
23
 
33
- if har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE"; then
24
+ if har_launch_preflight "$AGENT_ID"; then
34
25
  echo "Slot ${AGENT_ID}: ready to launch."
35
26
  if har_harness_uses_pm2; then
36
27
  echo " Ports: frontend=${FE_PORT} api=${API_PORT} debug=${DEBUG_PORT}"
@@ -9,7 +9,7 @@
9
9
  | **Agent ID** | ${AGENT_ID} |
10
10
  | **Work dir** | Fresh session worktree per launch — see the launch output or `.har/slots/agent-${AGENT_ID}.json` |
11
11
 
12
- **Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. Relaunching replaces the session (branch kept) and requires explicit confirmation (`--replace` / `confirmReplace`); dirty worktrees also need `--force` after user approval — never autonomously.
12
+ **Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. An occupied slot always blocks a new launch — run `har env teardown <id>` (or `complete <id>`) first, then launch again.
13
13
 
14
14
  ```bash
15
15
  ./.har/agent-cli.sh ${AGENT_ID} status
@@ -145,9 +145,9 @@ Switch that checkout to your intended base before launch. The session is recorde
145
145
  the work dir through it. Make ALL file edits under the work dir printed by launch,
146
146
  never in the main checkout.
147
147
 
148
- - Prefer `complete` / `teardown` when a task is finished, then launch.
149
- - `--replace` destroys the previous worktree (requires confirmation); it does **not**
150
- choose `main`. Uncommitted changes also need `--force` after explicit user approval.
148
+ - Occupied slots always block a new launch: `har env complete <id>` (or `teardown <id>`),
149
+ then `har env launch <id>`. A new launch never chooses `main` for you — switch the
150
+ main checkout to your intended base first.
151
151
  - `teardown` removes the worktree but **keeps the session branch** so you can push it
152
152
  or open a PR (`--delete-branch` to drop it).
153
153
  - If launch fails after creating a worktree/env file, resume with
@@ -53,12 +53,18 @@ validate_agent_id() {
53
53
  fi
54
54
 
55
55
  if [[ -z "$id" ]] || ! [[ "$id" =~ ^[0-9]+$ ]]; then
56
- echo "Error: agent-id must be a positive integer between ${min} and ${max}" >&2
56
+ echo "Error: invalid agent slot id: ${id:-"(missing)"}" >&2
57
+ echo " Must be an integer between ${min} and ${max} (see .har/stages.json → agentSlots)" >&2
57
58
  exit 1
58
59
  fi
59
60
 
60
61
  if (( id < min || id > max )); then
61
- echo "Error: agent-id must be between ${min} and ${max}" >&2
62
+ echo "Error: invalid agent slot id: ${id}" >&2
63
+ echo " Valid slots: ${min}–${max} (configured in .har/stages.json → agentSlots)" >&2
64
+ echo " Run: har env status # see which slots are in use" >&2
65
+ if (( id > max )); then
66
+ echo " To allow slot ${id}, raise agentSlots.max in .har/stages.json." >&2
67
+ fi
62
68
  exit 1
63
69
  fi
64
70
  }
@@ -217,7 +223,7 @@ har_resume_session_assignments() {
217
223
  local status
218
224
  status="$(slot_session_status "$agent_id")"
219
225
  echo "echo \"ERROR: slot ${agent_id} is not resumable (status=${status:-none}; need failed or starting).\" >&2" >&2
220
- echo "echo \" Use a normal launch, or --replace to start a fresh session.\" >&2"
226
+ echo "echo \" Free the slot first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}.\" >&2"
221
227
  echo 'exit 2'
222
228
  return 1
223
229
  fi
@@ -304,33 +310,26 @@ EOF
304
310
  fi
305
311
  }
306
312
 
313
+ # har_launch_preflight <agent_id> [resume]
314
+ # Exit 0 when ready; 2 when occupied (free the slot first) or not resumable; 1 on machine blockers.
307
315
  har_launch_preflight() {
308
316
  local agent_id="$1"
309
- local force="${2:-false}"
310
- local replace="${3:-false}"
311
- local resume="${4:-false}"
317
+ local resume="${2:-false}"
312
318
 
313
319
  if [ "$resume" = true ]; then
314
320
  if ! slot_is_resumable "$agent_id"; then
315
321
  local status
316
322
  status="$(slot_session_status "$agent_id")"
317
323
  echo "ERROR: slot ${agent_id} is not resumable (status=${status:-none}; need failed or starting)." >&2
318
- echo " Use a normal launch, or --replace to start a fresh session." >&2
324
+ echo " Free the slot first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}." >&2
319
325
  return 2
320
326
  fi
321
327
  echo "==> [agent-${agent_id}] Resuming partial launch (worktree and deps preserved)..." >&2
322
328
  elif slot_is_occupied "$agent_id"; then
323
- if [ "$replace" != true ] && [ "${HAR_CONFIRM_REPLACE:-}" != "1" ]; then
324
- print_slot_replace_warning "$agent_id"
325
- echo "ERROR: slot ${agent_id} is occupied — pass --replace to proceed." >&2
326
- return 2
327
- fi
328
- local wt
329
- wt="$(existing_slot_worktree "$agent_id")"
330
- if [ -n "$wt" ] && slot_worktree_dirty "$wt" && [ "$force" != true ]; then
331
- echo "ERROR: dirty worktree requires --force after explicit user approval." >&2
332
- return 2
333
- fi
329
+ print_slot_occupied_warning "$agent_id"
330
+ echo "ERROR: slot ${agent_id} is occupied." >&2
331
+ echo " Free it first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}." >&2
332
+ return 2
334
333
  fi
335
334
 
336
335
  if har_harness_uses_pm2; then
@@ -491,8 +490,8 @@ slot_dirty_summary() {
491
490
  echo "dirty (${count} changed)"
492
491
  }
493
492
 
494
- # Print a warning before replacing an occupied slot (stdout — visible to agents).
495
- print_slot_replace_warning() {
493
+ # Print details about an occupied slot (stdout — visible to agents).
494
+ print_slot_occupied_warning() {
496
495
  local agent_id="$1"
497
496
  local reg wt branch work_dir created status last_error dirty_summary head
498
497
  reg="$(slot_registry_file "$agent_id")"
@@ -508,7 +507,7 @@ print_slot_replace_warning() {
508
507
  fi
509
508
 
510
509
  echo "" >&2
511
- echo "⚠ Slot ${agent_id} is already in use — replacing will REMOVE the worktree." >&2
510
+ echo "⚠ Slot ${agent_id} is already in use." >&2
512
511
  [ -n "$wt" ] && echo " Worktree: ${wt}" >&2
513
512
  [ -n "$work_dir" ] && echo " Work dir: ${work_dir}" >&2
514
513
  [ -n "$branch" ] && echo " Branch: ${branch}${head:+ @ ${head}}" >&2
@@ -524,54 +523,21 @@ print_slot_replace_warning() {
524
523
  base_sha="$(git -C "$base_root" rev-parse --short HEAD 2>/dev/null || true)"
525
524
  echo " New session will be based on: ${base_branch}${base_sha:+ @ ${base_sha}}" >&2
526
525
  echo " (HEAD of ${base_root})" >&2
527
- echo " --replace does not choose this base — switch that checkout first for a new unrelated task." >&2
526
+ echo " Switch that checkout to your intended base before launching a new session." >&2
528
527
  echo "" >&2
529
528
  fi
530
- echo " Prefer complete/teardown when the previous task is finished, then launch." >&2
531
- echo " The session branch is kept only if you committed. Gitignored paths" >&2
532
- echo " (state/, runs/, local clones, .env.local) are NOT preserved." >&2
533
- echo "" >&2
534
- }
535
-
536
- # Require explicit confirmation before replacing an occupied slot.
537
- # $1 agent_id $2 force(true/false) $3 replace(true/false)
538
- # Exits 1 when replacement must not proceed.
539
- require_slot_replace_confirm() {
540
- local agent_id="$1"
541
- local force="$2"
542
- local replace="$3"
543
- local wt
544
-
545
- slot_is_occupied "$agent_id" || return 0
546
-
547
- print_slot_replace_warning "$agent_id"
548
- wt="$(existing_slot_worktree "$agent_id")"
549
-
550
- if [ -n "$wt" ] && slot_worktree_dirty "$wt" && [ "$force" != true ]; then
551
- echo "ERROR: previous session for slot ${agent_id} has uncommitted changes." >&2
552
- echo " Commit them in the worktree (branch is kept on teardown), or pass --force to discard." >&2
553
- echo " --force destroys uncommitted work — only use after explicit user approval." >&2
554
- exit 1
555
- fi
556
-
557
- if [ "$replace" = true ] || [ "${HAR_CONFIRM_REPLACE:-}" = "1" ]; then
558
- return 0
559
- fi
560
-
561
- if [ -t 0 ] && [ -t 1 ]; then
562
- local answer
563
- read -r -p "Replace slot ${agent_id}? Uncommitted work is lost unless committed. [y/N] " answer
564
- if [[ "$answer" =~ ^[Yy]$ ]]; then
565
- return 0
566
- fi
567
- echo "Aborted — slot ${agent_id} left unchanged." >&2
568
- exit 2
529
+ if [ "$dirty_summary" != "clean" ] && [ "$dirty_summary" != "unknown" ]; then
530
+ echo " Commit or discard changes in the worktree, then teardown/complete the slot:" >&2
531
+ else
532
+ echo " Free the slot, then launch again:" >&2
569
533
  fi
570
-
571
- echo "ERROR: slot ${agent_id} is occupied." >&2
572
- echo " Pass --replace (or set HAR_CONFIRM_REPLACE=1) after reviewing the warning above." >&2
573
- echo " If the worktree is dirty, also pass --force (only after explicit user approval)." >&2
574
- exit 2
534
+ echo " har env complete ${agent_id} # or: har env teardown ${agent_id}" >&2
535
+ echo " har env launch ${agent_id}" >&2
536
+ echo "" >&2
537
+ echo " complete/teardown remove the worktree. The session branch is kept only if" >&2
538
+ echo " you committed. Gitignored paths (state/, runs/, local clones, .env.local)" >&2
539
+ echo " are NOT preserved." >&2
540
+ echo "" >&2
575
541
  }
576
542
 
577
543
  git_common_dir() {
@@ -2,8 +2,9 @@
2
2
  # Launch an agent slot for CLI/library repos (git worktree by default, optional Docker infra).
3
3
  # Every launch starts a FRESH session: any previous session for the slot is torn
4
4
  # down (its branch is kept) and a new suffixed worktree is created from HEAD.
5
+ # Occupied slots always block: run ./.har/teardown.sh (or complete it) first, then relaunch.
5
6
  #
6
- # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--replace] [--force] [--resume]
7
+ # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--resume]
7
8
  set -euo pipefail
8
9
 
9
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -16,8 +17,6 @@ source "$SCRIPT_DIR/agent-slot.sh"
16
17
 
17
18
  AGENT_ID="${1:-}"
18
19
  USE_WORKTREE="${HARNESS_USE_WORKTREE:-true}"
19
- FORCE=false
20
- REPLACE=false
21
20
  RESUME=false
22
21
  WORK_UNIT_ID="${HAR_WORK_UNIT_ID:-}"
23
22
  ATTEMPT_ID="${HAR_ATTEMPT_ID:-}"
@@ -26,8 +25,6 @@ for arg in "$@"; do
26
25
  case "$arg" in
27
26
  --no-worktree) USE_WORKTREE=false ;;
28
27
  --worktree) USE_WORKTREE=true ;;
29
- --replace) REPLACE=true ;;
30
- --force) FORCE=true ;;
31
28
  --resume) RESUME=true ;;
32
29
  --work-id=*) WORK_UNIT_ID="${arg#*=}" ;;
33
30
  --attempt-id=*) ATTEMPT_ID="${arg#*=}" ;;
@@ -42,7 +39,7 @@ export SLOT_ATTEMPT_ID="$ATTEMPT_ID"
42
39
 
43
40
  if [[ -z "$AGENT_ID" ]]; then
44
41
  har_load_agent_slot_limits
45
- echo "Usage: $0 <agent-id> [--no-worktree] [--replace] [--force] [--resume] " >&2
42
+ echo "Usage: $0 <agent-id> [--no-worktree] [--resume]" >&2
46
43
  echo " agent-id must be between ${HARNESS_AGENT_SLOT_MIN} and ${HARNESS_AGENT_SLOT_MAX}" >&2
47
44
  exit 1
48
45
  fi
@@ -61,7 +58,7 @@ ENV_FILE=""
61
58
  REGISTRY_WRITTEN=false
62
59
 
63
60
  if [ "$RESUME" = true ]; then
64
- har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE" true || exit $?
61
+ har_launch_preflight "$AGENT_ID" true || exit $?
65
62
  eval "$(har_resume_session_assignments "$AGENT_ID")"
66
63
  REGISTRY_WRITTEN=true
67
64
  mark_slot_failed() {
@@ -87,13 +84,8 @@ if [ "$RESUME" = true ]; then
87
84
  }
88
85
  trap mark_slot_failed EXIT
89
86
  else
90
- har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE" || exit $?
91
-
92
- if slot_is_occupied "$AGENT_ID"; then
93
- require_slot_replace_confirm "$AGENT_ID" "$FORCE" "$REPLACE"
94
- log "Replacing previous session for slot ${AGENT_ID}..."
95
- "$SCRIPT_DIR/teardown.sh" "$AGENT_ID" >&2
96
- fi
87
+ # Occupied slots always block here: teardown/complete first, then relaunch.
88
+ har_launch_preflight "$AGENT_ID" || exit $?
97
89
  fi
98
90
 
99
91
  "$SCRIPT_DIR/setup-infra.sh"
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # Launch readiness gate for CLI/library harnesses (occupied slot only).
3
- # Usage: ./.har/preflight.sh <agent-id> [--replace] [--force]
3
+ # Usage: ./.har/preflight.sh <agent-id>
4
4
  set -euo pipefail
5
5
 
6
6
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -12,24 +12,15 @@ source "$SCRIPT_DIR/harness.env"
12
12
  source "$SCRIPT_DIR/agent-slot.sh"
13
13
 
14
14
  AGENT_ID="${1:-}"
15
- FORCE=false
16
- REPLACE=false
17
-
18
- for arg in "$@"; do
19
- case "$arg" in
20
- --replace) REPLACE=true ;;
21
- --force) FORCE=true ;;
22
- esac
23
- done
24
15
 
25
16
  if [[ -z "$AGENT_ID" ]]; then
26
- echo "Usage: $0 <agent-id> [--replace] [--force]" >&2
17
+ echo "Usage: $0 <agent-id>" >&2
27
18
  exit 1
28
19
  fi
29
20
 
30
21
  validate_agent_id "$AGENT_ID"
31
22
 
32
- if har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE"; then
23
+ if har_launch_preflight "$AGENT_ID"; then
33
24
  echo "Slot ${AGENT_ID}: ready to launch."
34
25
  exit 0
35
26
  fi