@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.
- package/LICENSE +214 -668
- package/README.md +1 -3
- package/dist/index.js +168 -199
- package/dist/templates/adaptation-prompt-init.md +1 -1
- package/dist/templates/agent-skills/har-wt.md +2 -2
- package/dist/templates/cursor-rule.mdc.template +2 -3
- package/dist/templates/har-boilerplate/CLAUDE.agent.md +1 -1
- package/dist/templates/har-boilerplate/README.md +4 -4
- package/dist/templates/har-boilerplate/agent-cli.sh +1 -1
- package/dist/templates/har-boilerplate/agent-slot.sh +32 -68
- package/dist/templates/har-boilerplate/launch.sh +6 -15
- package/dist/templates/har-boilerplate/preflight.sh +3 -12
- package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +1 -1
- package/dist/templates/har-boilerplate-cli/README.md +3 -3
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +32 -66
- package/dist/templates/har-boilerplate-cli/launch.sh +6 -14
- package/dist/templates/har-boilerplate-cli/preflight.sh +3 -12
- package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +1 -1
- package/dist/templates/har-boilerplate-ios/README.md +3 -3
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +23 -50
- package/dist/templates/har-boilerplate-ios/launch.sh +9 -11
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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
|
-
-
|
|
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
|
|
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.
|
|
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:
|
|
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.
|
|
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
|
-
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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 \"
|
|
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>
|
|
324
|
-
# Exit 0 when ready; 2 when occupied
|
|
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
|
|
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 "
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
|
515
|
-
|
|
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
|
|
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 "
|
|
544
|
+
echo " Switch that checkout to your intended base before launching a new session." >&2
|
|
548
545
|
echo "" >&2
|
|
549
546
|
fi
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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 "
|
|
592
|
-
echo "
|
|
593
|
-
echo "
|
|
594
|
-
|
|
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] [--
|
|
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] [--
|
|
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"
|
|
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
|
-
|
|
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>
|
|
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>
|
|
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"
|
|
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.
|
|
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
|
-
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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
|
|
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 \"
|
|
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
|
|
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 "
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
495
|
-
|
|
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
|
|
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 "
|
|
526
|
+
echo " Switch that checkout to your intended base before launching a new session." >&2
|
|
528
527
|
echo "" >&2
|
|
529
528
|
fi
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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 "
|
|
572
|
-
echo "
|
|
573
|
-
echo "
|
|
574
|
-
|
|
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] [--
|
|
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] [--
|
|
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"
|
|
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
|
-
|
|
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>
|
|
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>
|
|
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"
|
|
23
|
+
if har_launch_preflight "$AGENT_ID"; then
|
|
33
24
|
echo "Slot ${AGENT_ID}: ready to launch."
|
|
34
25
|
exit 0
|
|
35
26
|
fi
|