@osfactory/har 0.5.0 → 0.7.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.
Files changed (36) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +1817 -651
  3. package/dist/prompts/system-authoring.md +27 -2
  4. package/dist/templates/adaptation-prompt-init.md +60 -5
  5. package/dist/templates/adaptation-prompt-maintain.md +7 -4
  6. package/dist/templates/har-boilerplate/CLAUDE.agent.md +5 -3
  7. package/dist/templates/har-boilerplate/README.md +51 -9
  8. package/dist/templates/har-boilerplate/agent-cli.sh +72 -26
  9. package/dist/templates/har-boilerplate/agent-slot.sh +351 -0
  10. package/dist/templates/har-boilerplate/attach.sh +7 -1
  11. package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +3 -1
  12. package/dist/templates/har-boilerplate/env.template +4 -1
  13. package/dist/templates/har-boilerplate/harness.env +32 -0
  14. package/dist/templates/har-boilerplate/launch.sh +167 -120
  15. package/dist/templates/har-boilerplate/preflight.sh +41 -0
  16. package/dist/templates/har-boilerplate/provision-toolchain.sh +285 -0
  17. package/dist/templates/har-boilerplate/setup-infra.sh +87 -11
  18. package/dist/templates/har-boilerplate/teardown.sh +2 -1
  19. package/dist/templates/har-boilerplate/verify.sh +91 -11
  20. package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +5 -2
  21. package/dist/templates/har-boilerplate-cli/README.md +40 -6
  22. package/dist/templates/har-boilerplate-cli/agent-cli.sh +1 -1
  23. package/dist/templates/har-boilerplate-cli/agent-slot.sh +331 -0
  24. package/dist/templates/har-boilerplate-cli/harness.env +13 -0
  25. package/dist/templates/har-boilerplate-cli/launch.sh +140 -95
  26. package/dist/templates/har-boilerplate-cli/preflight.sh +37 -0
  27. package/dist/templates/har-boilerplate-cli/provision-toolchain.sh +285 -0
  28. package/dist/templates/har-boilerplate-cli/setup-infra.sh +87 -11
  29. package/dist/templates/har-boilerplate-cli/verify.sh +93 -12
  30. package/dist/templates/har-boilerplate-ios/README.md +29 -2
  31. package/dist/templates/har-boilerplate-ios/harness.env +6 -1
  32. package/dist/templates/har-boilerplate-ios/launch.sh +8 -13
  33. package/dist/templates/har-boilerplate-ios/provision-toolchain.sh +285 -0
  34. package/dist/templates/har-boilerplate-ios/verify.sh +20 -20
  35. package/package.json +1 -1
  36. package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +0 -57
@@ -3,7 +3,7 @@
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
5
  #
6
- # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--claude] [--replace] [--force]
6
+ # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--claude] [--replace] [--force] [--resume]
7
7
  set -euo pipefail
8
8
 
9
9
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -19,6 +19,7 @@ USE_WORKTREE="${HARNESS_USE_WORKTREE:-true}"
19
19
  USE_CLAUDE=false
20
20
  FORCE=false
21
21
  REPLACE=false
22
+ RESUME=false
22
23
  PURPOSE="${HAR_SESSION_PURPOSE:-}"
23
24
 
24
25
  for arg in "$@"; do
@@ -28,12 +29,13 @@ for arg in "$@"; do
28
29
  --claude) USE_CLAUDE=true ;;
29
30
  --replace) REPLACE=true ;;
30
31
  --force) FORCE=true ;;
32
+ --resume) RESUME=true ;;
31
33
  --purpose=*) PURPOSE="${arg#--purpose=}" ;;
32
34
  esac
33
35
  done
34
36
 
35
37
  if [[ -z "$AGENT_ID" ]]; then
36
- echo "Usage: $0 <agent-id> [--no-worktree] [--claude] [--replace] [--force] [--purpose=label]" >&2
38
+ echo "Usage: $0 <agent-id> [--no-worktree] [--claude] [--replace] [--force] [--resume] [--purpose=label]" >&2
37
39
  echo " agent-id must be between ${HARNESS_AGENT_SLOT_MIN} and ${HARNESS_AGENT_SLOT_MAX}" >&2
38
40
  exit 1
39
41
  fi
@@ -42,24 +44,65 @@ validate_agent_id "$AGENT_ID"
42
44
 
43
45
  log() { echo "==> [agent-$AGENT_ID] $*" >&2; }
44
46
 
45
- FE_PORT=$(( HARNESS_FE_BASE_PORT + AGENT_ID * 10 ))
46
- API_PORT=$(( HARNESS_API_BASE_PORT + AGENT_ID * 10 ))
47
- DEBUG_PORT=$(( 9200 + AGENT_ID * 10 ))
48
- DB_PORT="${AGENT_DB_PORT:-15432}"
49
- MINIO_PORT="${AGENT_MINIO_PORT:-19000}"
50
- BROWSER_PORT="${AGENT_BROWSER_PORT:-13001}"
47
+ WORK_DIR="$REPO_ROOT"
48
+ WORKTREE_DIR=""
49
+ BRANCH=""
50
+ SUFFIX=""
51
+ BASE_BRANCH=""
52
+ BASE_COMMIT=""
53
+ ENV_FILE=""
54
+ REGISTRY_WRITTEN=false
55
+
56
+ if [ "$RESUME" = true ]; then
57
+ har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE" true || exit $?
58
+ eval "$(har_resume_session_assignments "$AGENT_ID")"
59
+ REGISTRY_WRITTEN=true
60
+ mark_slot_failed() {
61
+ local exit_code="$?"
62
+ if [ "$exit_code" != "0" ] && [ "$REGISTRY_WRITTEN" = true ]; then
63
+ log "Resume failed. Recording failed slot state..."
64
+ set +e
65
+ SLOT_AGENT_ID="$AGENT_ID" \
66
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
67
+ SLOT_WORK_DIR="$WORK_DIR" \
68
+ SLOT_SUFFIX="${SUFFIX:-}" \
69
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
70
+ SLOT_BRANCH="${BRANCH:-}" \
71
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
72
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
73
+ SLOT_PURPOSE="${PURPOSE}" \
74
+ SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT},\"db\":${DB_PORT}}" \
75
+ SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
76
+ SLOT_STATUS="failed" \
77
+ SLOT_LAST_ERROR="launch.sh --resume exited with code ${exit_code}" \
78
+ write_slot_registry
79
+ log " Work dir: ${WORK_DIR}"
80
+ log " Env file: ${ENV_FILE}"
81
+ log " Recovery: ./.har/launch.sh ${AGENT_ID} --resume"
82
+ fi
83
+ }
84
+ trap mark_slot_failed EXIT
85
+ else
86
+ # Preflight before worktree/install — ports, foreign PM2, Docker, occupied slot.
87
+ har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE" || exit $?
51
88
 
52
- log "Ports: frontend=$FE_PORT api=$API_PORT debug=$DEBUG_PORT"
89
+ # Replace any previous session for this slot — requires explicit confirmation.
90
+ if slot_is_occupied "$AGENT_ID"; then
91
+ require_slot_replace_confirm "$AGENT_ID" "$FORCE" "$REPLACE"
92
+ log "Replacing previous session for slot ${AGENT_ID}..."
93
+ "$SCRIPT_DIR/teardown.sh" "$AGENT_ID" >&2
94
+ fi
95
+ fi
53
96
 
54
- # Replace any previous session for this slot — requires explicit confirmation.
55
- if slot_is_occupied "$AGENT_ID"; then
56
- require_slot_replace_confirm "$AGENT_ID" "$FORCE" "$REPLACE"
57
- log "Replacing previous session for slot ${AGENT_ID}..."
58
- "$SCRIPT_DIR/teardown.sh" "$AGENT_ID" >&2
97
+ if har_harness_uses_pm2; then
98
+ log "Ports: frontend=$FE_PORT api=$API_PORT debug=$DEBUG_PORT"
59
99
  fi
60
100
 
61
- # Ensure shared infra is running
101
+ # Ensure shared infra is running (persists host ports in .har/state/infra.env).
62
102
  "$SCRIPT_DIR/setup-infra.sh"
103
+ DB_PORT="${AGENT_DB_PORT:-${HARNESS_DB_PORT_DEFAULT:-15432}}"
104
+ MINIO_PORT="${AGENT_MINIO_PORT:-${HARNESS_MINIO_PORT_DEFAULT:-19000}}"
105
+ BROWSER_PORT="${AGENT_BROWSER_PORT:-${HARNESS_BROWSER_PORT_DEFAULT:-13001}}"
63
106
 
64
107
  # Clone agent database from template
65
108
  if har_infra_enabled db && [ -n "${HARNESS_TEMPLATE_DB:-}" ]; then
@@ -85,111 +128,113 @@ if har_infra_enabled minio; then
85
128
  fi
86
129
 
87
130
  # Session worktree (default — use --no-worktree to work in repo root).
88
- # Name encodes what the session is based on: <base-branch>-<sha4>-har-agent-<id>-<rand4>.
89
- WORK_DIR="$REPO_ROOT"
90
- WORKTREE_DIR=""
91
- BRANCH=""
92
- SUFFIX=""
93
- BASE_BRANCH="$(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "detached")"
94
- BASE_COMMIT="$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || true)"
95
- if [ "$USE_WORKTREE" = true ]; then
96
- SHORT_SHA="$(git -C "$REPO_ROOT" rev-parse --short=4 HEAD)"
97
- SUFFIX="$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom 2>/dev/null | head -c 4 || true)"
98
- [ -n "$SUFFIX" ] || SUFFIX="$(printf '%04d' $(( RANDOM % 10000 )))"
99
- SESSION_NAME="${BASE_BRANCH//\//-}-${SHORT_SHA}-har-agent-${AGENT_ID}-${SUFFIX}"
100
- BRANCH="$SESSION_NAME"
101
- WORKTREE_DIR="$HOME/worktrees/${SESSION_NAME}"
102
- log "Creating session worktree at $WORKTREE_DIR (branch $BRANCH)..."
103
- git -C "$REPO_ROOT" worktree add "$WORKTREE_DIR" -b "$BRANCH"
104
- # Worktrees are always rooted at the git repo if this project lives in a
105
- # subdirectory (monorepo), point WORK_DIR at the project inside the worktree.
106
- REL_PREFIX="$(git -C "$REPO_ROOT" rev-parse --show-prefix 2>/dev/null || true)"
107
- WORK_DIR="${WORKTREE_DIR%/}/${REL_PREFIX}"
108
- WORK_DIR="${WORK_DIR%/}"
109
- else
110
- log "Using repo root (worktree disabled)"
111
- fi
112
-
113
- # Install dependencies (fresh worktrees have no node_modules — PM2 config needs them at load time)
114
- if [ -f "$WORK_DIR/package.json" ] && [ ! -d "$WORK_DIR/node_modules" ]; then
115
- log "Installing dependencies in $WORK_DIR..."
116
- (cd "$WORK_DIR" && npm install --silent)
117
- elif [ -f "$WORK_DIR/go.mod" ] && [ ! -d "$WORK_DIR/vendor" ]; then
118
- log "Go module detected in work dir (run go mod download if needed)"
119
- fi
131
+ if [ "$RESUME" != true ]; then
132
+ # Name encodes what the session is based on: <base-branch>-<sha4>-har-agent-<id>-<rand4>.
133
+ WORK_DIR="$REPO_ROOT"
134
+ WORKTREE_DIR=""
135
+ BRANCH=""
136
+ SUFFIX=""
137
+ BASE_BRANCH="$(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "detached")"
138
+ BASE_COMMIT="$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || true)"
139
+ if [ "$USE_WORKTREE" = true ]; then
140
+ SHORT_SHA="$(git -C "$REPO_ROOT" rev-parse --short=4 HEAD)"
141
+ SUFFIX="$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom 2>/dev/null | head -c 4 || true)"
142
+ [ -n "$SUFFIX" ] || SUFFIX="$(printf '%04d' $(( RANDOM % 10000 )))"
143
+ SESSION_NAME="${BASE_BRANCH//\//-}-${SHORT_SHA}-har-agent-${AGENT_ID}-${SUFFIX}"
144
+ BRANCH="$SESSION_NAME"
145
+ WORKTREE_DIR="$HOME/worktrees/${SESSION_NAME}"
146
+ log "Creating session worktree at $WORKTREE_DIR (branch $BRANCH)..."
147
+ git -C "$REPO_ROOT" worktree add "$WORKTREE_DIR" -b "$BRANCH"
148
+ REL_PREFIX="$(git -C "$REPO_ROOT" rev-parse --show-prefix 2>/dev/null || true)"
149
+ WORK_DIR="${WORKTREE_DIR%/}/${REL_PREFIX}"
150
+ WORK_DIR="${WORK_DIR%/}"
151
+ else
152
+ log "Using repo root (worktree disabled)"
153
+ REL_PREFIX="$(git -C "$REPO_ROOT" rev-parse --show-prefix 2>/dev/null || true)"
154
+ fi
120
155
 
121
- # Monorepo: file:/workspace deps may resolve modules from the repo root — install there too
122
- if [ -n "${REL_PREFIX:-}" ] && [ -f "$WORKTREE_DIR/package.json" ] && [ ! -d "$WORKTREE_DIR/node_modules" ]; then
123
- log "Installing monorepo root dependencies in $WORKTREE_DIR..."
124
- (cd "$WORKTREE_DIR" && npm install --silent)
125
- fi
156
+ GIT_EXCLUDE="$(git -C "$REPO_ROOT" rev-parse --git-common-dir 2>/dev/null)/info/exclude"
157
+ if [ -n "$GIT_EXCLUDE" ] && [ -d "$(dirname "$GIT_EXCLUDE")" ]; then
158
+ for pattern in '.env.agent.*' 'ecosystem.agent.*.config.cjs' '.har/venv'; do
159
+ grep -qxF "$pattern" "$GIT_EXCLUDE" 2>/dev/null || echo "$pattern" >> "$GIT_EXCLUDE"
160
+ done
161
+ fi
126
162
 
127
- # Generate .env.agent.N
128
- # Keep harness-generated files out of accidental `git add -A` commits in repos
129
- # whose .gitignore doesn't know about har (applies to all worktrees too).
130
- GIT_EXCLUDE="$(git -C "$REPO_ROOT" rev-parse --git-common-dir 2>/dev/null)/info/exclude"
131
- if [ -n "$GIT_EXCLUDE" ] && [ -d "$(dirname "$GIT_EXCLUDE")" ]; then
132
- for pattern in '.env.agent.*' 'ecosystem.agent.*.config.cjs'; do
133
- grep -qxF "$pattern" "$GIT_EXCLUDE" 2>/dev/null || echo "$pattern" >> "$GIT_EXCLUDE"
134
- done
135
- fi
163
+ ENV_FILE="$WORK_DIR/.env.agent.${AGENT_ID}"
164
+ log "Generating $ENV_FILE..."
165
+ har_regenerate_agent_env_file "$AGENT_ID" "$WORK_DIR" "$ENV_FILE"
136
166
 
137
- ENV_FILE="$WORK_DIR/.env.agent.${AGENT_ID}"
138
- log "Generating $ENV_FILE..."
139
- AGENT_ID="$AGENT_ID" \
140
- API_PORT="$API_PORT" \
141
- FE_PORT="$FE_PORT" \
142
- DEBUG_PORT="$DEBUG_PORT" \
143
- DB_PORT="$DB_PORT" \
144
- MINIO_PORT="$MINIO_PORT" \
145
- BROWSER_PORT="$BROWSER_PORT" \
146
- REPO_ROOT="$WORK_DIR" \
147
- envsubst '${AGENT_ID} ${API_PORT} ${FE_PORT} ${DEBUG_PORT} ${DB_PORT} ${MINIO_PORT} ${BROWSER_PORT} ${REPO_ROOT}' \
148
- < "$SCRIPT_DIR/env.template" > "$ENV_FILE"
167
+ REGISTRY_WRITTEN=false
168
+ mark_slot_failed() {
169
+ local exit_code="$?"
170
+ if [ "$exit_code" != "0" ] && [ "$REGISTRY_WRITTEN" = true ]; then
171
+ log "Launch failed after creating the session. Recording failed slot state..."
172
+ set +e
173
+ SLOT_AGENT_ID="$AGENT_ID" \
174
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
175
+ SLOT_WORK_DIR="$WORK_DIR" \
176
+ SLOT_SUFFIX="${SUFFIX:-}" \
177
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
178
+ SLOT_BRANCH="${BRANCH:-}" \
179
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
180
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
181
+ SLOT_PURPOSE="${PURPOSE}" \
182
+ SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT},\"db\":${DB_PORT}}" \
183
+ SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
184
+ SLOT_STATUS="failed" \
185
+ SLOT_LAST_ERROR="launch.sh exited with code ${exit_code}" \
186
+ write_slot_registry
187
+ log " Work dir: ${WORK_DIR}"
188
+ log " Env file: ${ENV_FILE}"
189
+ log " Recovery: ./.har/launch.sh ${AGENT_ID} --resume"
190
+ fi
191
+ }
192
+ trap mark_slot_failed EXIT
149
193
 
150
- REGISTRY_WRITTEN=false
151
- mark_slot_failed() {
152
- local exit_code="$?"
153
- if [ "$exit_code" != "0" ] && [ "$REGISTRY_WRITTEN" = true ]; then
154
- log "Launch failed after creating the session. Recording failed slot state..."
155
- set +e
156
- SLOT_AGENT_ID="$AGENT_ID" \
157
- SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
158
- SLOT_WORK_DIR="$WORK_DIR" \
159
- SLOT_SUFFIX="${SUFFIX:-}" \
160
- SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
161
- SLOT_BRANCH="${BRANCH:-}" \
162
- SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
163
- SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
164
- SLOT_PURPOSE="${PURPOSE}" \
165
- SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT}}" \
166
- SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
167
- SLOT_STATUS="failed" \
168
- SLOT_LAST_ERROR="launch.sh exited with code ${exit_code}" \
169
- write_slot_registry
170
- log " Work dir: ${WORK_DIR}"
171
- log " Env file: ${ENV_FILE}"
172
- log " Recovery: fix the failure, then relaunch with --replace when ready."
173
- fi
174
- }
175
- trap mark_slot_failed EXIT
194
+ SLOT_AGENT_ID="$AGENT_ID" \
195
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
196
+ SLOT_WORK_DIR="$WORK_DIR" \
197
+ SLOT_SUFFIX="${SUFFIX:-}" \
198
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
199
+ SLOT_BRANCH="${BRANCH:-}" \
200
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
201
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
202
+ SLOT_PURPOSE="${PURPOSE}" \
203
+ SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT},\"db\":${DB_PORT}}" \
204
+ SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
205
+ SLOT_STATUS="starting" \
206
+ write_slot_registry
207
+ REGISTRY_WRITTEN=true
208
+ else
209
+ REL_PREFIX="$(git -C "$REPO_ROOT" rev-parse --show-prefix 2>/dev/null || true)"
210
+ log "Resuming session at ${WORK_DIR}"
211
+ har_regenerate_agent_env_file "$AGENT_ID" "$WORK_DIR" "$ENV_FILE"
212
+ SLOT_AGENT_ID="$AGENT_ID" \
213
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
214
+ SLOT_WORK_DIR="$WORK_DIR" \
215
+ SLOT_SUFFIX="${SUFFIX:-}" \
216
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
217
+ SLOT_BRANCH="${BRANCH:-}" \
218
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
219
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
220
+ SLOT_PURPOSE="${PURPOSE}" \
221
+ SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT},\"db\":${DB_PORT}}" \
222
+ SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
223
+ SLOT_STATUS="starting" \
224
+ write_slot_registry
225
+ fi
176
226
 
177
- # Record the session before slow or fragile setup so verify/status/teardown can
178
- # recover partial launches that already created a worktree and env file.
179
- SLOT_AGENT_ID="$AGENT_ID" \
180
- SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
181
- SLOT_WORK_DIR="$WORK_DIR" \
182
- SLOT_SUFFIX="${SUFFIX:-}" \
183
- SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
184
- SLOT_BRANCH="${BRANCH:-}" \
185
- SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
186
- SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
187
- SLOT_PURPOSE="${PURPOSE}" \
188
- SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT}}" \
189
- SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
190
- SLOT_STATUS="starting" \
191
- write_slot_registry
192
- REGISTRY_WRITTEN=true
227
+ if [ "$RESUME" = true ] && har_toolchain_ready "$WORK_DIR"; then
228
+ log "Toolchain already provisioned skipping install."
229
+ else
230
+ log "Provisioning toolchain (see harness.env: HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD)..."
231
+ HAR_WORK_DIR="$WORK_DIR" \
232
+ HAR_ENV_FILE="$ENV_FILE" \
233
+ HAR_WORKTREE_DIR="${WORKTREE_DIR:-}" \
234
+ HAR_REL_PREFIX="${REL_PREFIX:-}" \
235
+ HAR_AGENT_ID="$AGENT_ID" \
236
+ "$SCRIPT_DIR/provision-toolchain.sh"
237
+ fi
193
238
 
194
239
  if [ -n "${HARNESS_DB_MINIMAL_BOOTSTRAP_CMD:-}" ]; then
195
240
  log "Running minimal data bootstrap..."
@@ -200,13 +245,15 @@ fi
200
245
  ECOSYSTEM_FILE="$WORK_DIR/ecosystem.agent.${AGENT_ID}.config.cjs"
201
246
  log "Generating $ECOSYSTEM_FILE..."
202
247
  AGENT_ID="$AGENT_ID" \
248
+ HARNESS_PROJECT_NAME="$HARNESS_PROJECT_NAME" \
203
249
  FE_PORT="$FE_PORT" \
204
250
  DEBUG_PORT="$DEBUG_PORT" \
205
- envsubst '${AGENT_ID} ${FE_PORT} ${DEBUG_PORT}' \
251
+ envsubst '${AGENT_ID} ${HARNESS_PROJECT_NAME} ${FE_PORT} ${DEBUG_PORT}' \
206
252
  < "$SCRIPT_DIR/ecosystem.agent.template.cjs" > "$ECOSYSTEM_FILE"
207
253
 
208
- # Stop existing processes for this agent
209
- npx --yes pm2 delete "/^agent-${AGENT_ID}-/" 2>/dev/null || true
254
+ PM2_REGEX="$(har_pm2_delete_regex "$AGENT_ID")"
255
+ # Stop existing processes for this agent (project-scoped never touch other harnesses).
256
+ npx --yes pm2 delete "$PM2_REGEX" 2>/dev/null || true
210
257
 
211
258
  # Start PM2 processes
212
259
  log "Starting PM2 processes..."
@@ -246,14 +293,14 @@ SLOT_BRANCH="${BRANCH:-}" \
246
293
  SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
247
294
  SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
248
295
  SLOT_PURPOSE="${PURPOSE}" \
249
- SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT}}" \
296
+ SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT},\"db\":${DB_PORT}}" \
250
297
  SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
251
298
  SLOT_STATUS="active" \
252
299
  write_slot_registry
253
300
 
254
301
  # Launch Claude Code in tmux (optional)
255
302
  if [ "$USE_CLAUDE" = true ]; then
256
- TMUX_SESSION="agent-${AGENT_ID}"
303
+ TMUX_SESSION="$(har_tmux_session "$AGENT_ID")"
257
304
  if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
258
305
  tmux kill-session -t "$TMUX_SESSION"
259
306
  fi
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ # Launch readiness gate — run before launch.sh or standalone.
3
+ # Usage: ./.har/preflight.sh <agent-id> [--replace] [--force]
4
+ # JSON: har env preflight <id> --json
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
9
+
10
+ # shellcheck source=/dev/null
11
+ source "$SCRIPT_DIR/harness.env"
12
+ # shellcheck source=/dev/null
13
+ source "$SCRIPT_DIR/agent-slot.sh"
14
+
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
+
26
+ if [[ -z "$AGENT_ID" ]]; then
27
+ echo "Usage: $0 <agent-id> [--replace] [--force]" >&2
28
+ exit 1
29
+ fi
30
+
31
+ validate_agent_id "$AGENT_ID"
32
+
33
+ if har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE"; then
34
+ echo "Slot ${AGENT_ID}: ready to launch."
35
+ if har_harness_uses_pm2; then
36
+ echo " Ports: frontend=${FE_PORT} api=${API_PORT} debug=${DEBUG_PORT}"
37
+ fi
38
+ exit 0
39
+ fi
40
+
41
+ exit $?