@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.
- package/README.md +2 -0
- package/dist/index.js +1811 -648
- package/dist/prompts/system-authoring.md +27 -2
- package/dist/templates/adaptation-prompt-init.md +32 -2
- package/dist/templates/adaptation-prompt-maintain.md +5 -2
- package/dist/templates/har-boilerplate/README.md +39 -3
- package/dist/templates/har-boilerplate/agent-cli.sh +72 -26
- package/dist/templates/har-boilerplate/agent-slot.sh +351 -0
- package/dist/templates/har-boilerplate/attach.sh +7 -1
- package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +1 -1
- package/dist/templates/har-boilerplate/env.template +1 -1
- package/dist/templates/har-boilerplate/harness.env +27 -0
- package/dist/templates/har-boilerplate/launch.sh +167 -114
- package/dist/templates/har-boilerplate/preflight.sh +41 -0
- package/dist/templates/har-boilerplate/setup-infra.sh +87 -11
- package/dist/templates/har-boilerplate/teardown.sh +2 -1
- package/dist/templates/har-boilerplate-cli/README.md +28 -0
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +331 -0
- package/dist/templates/har-boilerplate-cli/harness.env +7 -0
- package/dist/templates/har-boilerplate-cli/launch.sh +145 -95
- package/dist/templates/har-boilerplate-cli/preflight.sh +37 -0
- package/dist/templates/har-boilerplate-cli/setup-infra.sh +87 -11
- package/dist/templates/har-boilerplate-ios/README.md +26 -0
- package/dist/templates/har-boilerplate-ios/harness.env +2 -1
- package/package.json +1 -1
- 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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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,105 +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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
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
|
|
112
155
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
grep -qxF "$pattern" "$GIT_EXCLUDE" 2>/dev/null || echo "$pattern" >> "$GIT_EXCLUDE"
|
|
120
|
-
done
|
|
121
|
-
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
|
|
122
162
|
|
|
123
|
-
ENV_FILE="$WORK_DIR/.env.agent.${AGENT_ID}"
|
|
124
|
-
log "Generating $ENV_FILE..."
|
|
125
|
-
|
|
126
|
-
API_PORT="$API_PORT" \
|
|
127
|
-
FE_PORT="$FE_PORT" \
|
|
128
|
-
DEBUG_PORT="$DEBUG_PORT" \
|
|
129
|
-
DB_PORT="$DB_PORT" \
|
|
130
|
-
MINIO_PORT="$MINIO_PORT" \
|
|
131
|
-
BROWSER_PORT="$BROWSER_PORT" \
|
|
132
|
-
REPO_ROOT="$WORK_DIR" \
|
|
133
|
-
envsubst '${AGENT_ID} ${API_PORT} ${FE_PORT} ${DEBUG_PORT} ${DB_PORT} ${MINIO_PORT} ${BROWSER_PORT} ${REPO_ROOT}' \
|
|
134
|
-
< "$SCRIPT_DIR/env.template" > "$ENV_FILE"
|
|
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"
|
|
135
166
|
|
|
136
|
-
REGISTRY_WRITTEN=false
|
|
137
|
-
mark_slot_failed() {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
trap mark_slot_failed EXIT
|
|
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
|
|
162
193
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
179
226
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
"$
|
|
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
|
|
187
238
|
|
|
188
239
|
if [ -n "${HARNESS_DB_MINIMAL_BOOTSTRAP_CMD:-}" ]; then
|
|
189
240
|
log "Running minimal data bootstrap..."
|
|
@@ -194,13 +245,15 @@ fi
|
|
|
194
245
|
ECOSYSTEM_FILE="$WORK_DIR/ecosystem.agent.${AGENT_ID}.config.cjs"
|
|
195
246
|
log "Generating $ECOSYSTEM_FILE..."
|
|
196
247
|
AGENT_ID="$AGENT_ID" \
|
|
248
|
+
HARNESS_PROJECT_NAME="$HARNESS_PROJECT_NAME" \
|
|
197
249
|
FE_PORT="$FE_PORT" \
|
|
198
250
|
DEBUG_PORT="$DEBUG_PORT" \
|
|
199
|
-
envsubst '${AGENT_ID} ${FE_PORT} ${DEBUG_PORT}' \
|
|
251
|
+
envsubst '${AGENT_ID} ${HARNESS_PROJECT_NAME} ${FE_PORT} ${DEBUG_PORT}' \
|
|
200
252
|
< "$SCRIPT_DIR/ecosystem.agent.template.cjs" > "$ECOSYSTEM_FILE"
|
|
201
253
|
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
204
257
|
|
|
205
258
|
# Start PM2 processes
|
|
206
259
|
log "Starting PM2 processes..."
|
|
@@ -240,14 +293,14 @@ SLOT_BRANCH="${BRANCH:-}" \
|
|
|
240
293
|
SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
|
|
241
294
|
SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
|
|
242
295
|
SLOT_PURPOSE="${PURPOSE}" \
|
|
243
|
-
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}}" \
|
|
244
297
|
SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
|
|
245
298
|
SLOT_STATUS="active" \
|
|
246
299
|
write_slot_registry
|
|
247
300
|
|
|
248
301
|
# Launch Claude Code in tmux (optional)
|
|
249
302
|
if [ "$USE_CLAUDE" = true ]; then
|
|
250
|
-
TMUX_SESSION="
|
|
303
|
+
TMUX_SESSION="$(har_tmux_session "$AGENT_ID")"
|
|
251
304
|
if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
|
|
252
305
|
tmux kill-session -t "$TMUX_SESSION"
|
|
253
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 $?
|
|
@@ -11,26 +11,101 @@ set -euo pipefail
|
|
|
11
11
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
12
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
13
|
COMPOSE_FILE="$SCRIPT_DIR/docker-compose.agent.yml"
|
|
14
|
+
INFRA_STATE="$SCRIPT_DIR/state/infra.env"
|
|
14
15
|
|
|
15
16
|
# shellcheck source=/dev/null
|
|
16
17
|
source "$SCRIPT_DIR/harness.env"
|
|
18
|
+
# shellcheck source=/dev/null
|
|
19
|
+
source "$SCRIPT_DIR/agent-slot.sh"
|
|
17
20
|
|
|
18
21
|
COMPOSE_PROJECT="har-${HARNESS_PROJECT_NAME}"
|
|
19
|
-
DB_PORT="${AGENT_DB_PORT:-15432}"
|
|
20
22
|
PSQL="har_pg psql -d postgres"
|
|
21
23
|
|
|
22
24
|
log() { echo "==> $*" >&2; }
|
|
23
25
|
|
|
26
|
+
har_compose_service_running() {
|
|
27
|
+
local service="$1"
|
|
28
|
+
docker ps --filter "name=${COMPOSE_PROJECT}-${service}-1" --format '{{.Names}}' 2>/dev/null \
|
|
29
|
+
| grep -q "^${COMPOSE_PROJECT}-${service}-1\$"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
har_resolve_infra_port() {
|
|
33
|
+
local var_name="$1"
|
|
34
|
+
local default_port="$2"
|
|
35
|
+
local scan_start="$3"
|
|
36
|
+
local scan_end="$4"
|
|
37
|
+
local service="${5:-}"
|
|
38
|
+
local current="${!var_name:-}"
|
|
39
|
+
|
|
40
|
+
if [ -n "$current" ] && { ! port_in_use "$current" || { [ -n "$service" ] && har_compose_service_running "$service"; }; }; then
|
|
41
|
+
echo "$current"
|
|
42
|
+
return 0
|
|
43
|
+
fi
|
|
44
|
+
har_allocate_port "$default_port" "$scan_start" "$scan_end"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
mkdir -p "$(dirname "$INFRA_STATE")"
|
|
48
|
+
if [ -f "$INFRA_STATE" ]; then
|
|
49
|
+
# shellcheck source=/dev/null
|
|
50
|
+
source "$INFRA_STATE"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
DB_PORT="$(har_resolve_infra_port AGENT_DB_PORT \
|
|
54
|
+
"${HARNESS_DB_PORT_DEFAULT:-15432}" \
|
|
55
|
+
"${HARNESS_DB_PORT_SCAN_START:-15432}" \
|
|
56
|
+
"${HARNESS_DB_PORT_SCAN_END:-15499}" \
|
|
57
|
+
db)"
|
|
58
|
+
MINIO_PORT="$(har_resolve_infra_port AGENT_MINIO_PORT \
|
|
59
|
+
"${HARNESS_MINIO_PORT_DEFAULT:-19000}" \
|
|
60
|
+
"${HARNESS_MINIO_PORT_SCAN_START:-19000}" \
|
|
61
|
+
"${HARNESS_MINIO_PORT_SCAN_END:-19099}" \
|
|
62
|
+
minio)"
|
|
63
|
+
MINIO_CONSOLE_PORT="$(har_resolve_infra_port AGENT_MINIO_CONSOLE_PORT \
|
|
64
|
+
"${HARNESS_MINIO_CONSOLE_PORT_DEFAULT:-19001}" \
|
|
65
|
+
"${HARNESS_MINIO_CONSOLE_PORT_SCAN_START:-19001}" \
|
|
66
|
+
"${HARNESS_MINIO_CONSOLE_PORT_SCAN_END:-19099}")"
|
|
67
|
+
BROWSER_PORT="$(har_resolve_infra_port AGENT_BROWSER_PORT \
|
|
68
|
+
"${HARNESS_BROWSER_PORT_DEFAULT:-13001}" \
|
|
69
|
+
"${HARNESS_BROWSER_PORT_SCAN_START:-13001}" \
|
|
70
|
+
"${HARNESS_BROWSER_PORT_SCAN_END:-13099}" \
|
|
71
|
+
headless-browser)"
|
|
72
|
+
MAILPIT_WEB_PORT="$(har_resolve_infra_port AGENT_MAILPIT_WEB_PORT \
|
|
73
|
+
"${HARNESS_MAILPIT_WEB_PORT_DEFAULT:-18025}" \
|
|
74
|
+
"${HARNESS_MAILPIT_WEB_PORT_SCAN_START:-18025}" \
|
|
75
|
+
"${HARNESS_MAILPIT_WEB_PORT_SCAN_END:-18099}" \
|
|
76
|
+
mailpit)"
|
|
77
|
+
MAILPIT_SMTP_PORT="$(har_resolve_infra_port AGENT_MAILPIT_SMTP_PORT \
|
|
78
|
+
"${HARNESS_MAILPIT_SMTP_PORT_DEFAULT:-11025}" \
|
|
79
|
+
"${HARNESS_MAILPIT_SMTP_PORT_SCAN_START:-11025}" \
|
|
80
|
+
"${HARNESS_MAILPIT_SMTP_PORT_SCAN_END:-11099}")"
|
|
81
|
+
|
|
82
|
+
export AGENT_DB_PORT="$DB_PORT"
|
|
83
|
+
export AGENT_MINIO_PORT="$MINIO_PORT"
|
|
84
|
+
export AGENT_MINIO_CONSOLE_PORT="$MINIO_CONSOLE_PORT"
|
|
85
|
+
export AGENT_BROWSER_PORT="$BROWSER_PORT"
|
|
86
|
+
export AGENT_MAILPIT_WEB_PORT="$MAILPIT_WEB_PORT"
|
|
87
|
+
export AGENT_MAILPIT_SMTP_PORT="$MAILPIT_SMTP_PORT"
|
|
88
|
+
|
|
89
|
+
cat > "$INFRA_STATE" <<EOF
|
|
90
|
+
# Persisted by setup-infra.sh — host ports for shared docker compose services.
|
|
91
|
+
export AGENT_DB_PORT=${DB_PORT}
|
|
92
|
+
export AGENT_MINIO_PORT=${MINIO_PORT}
|
|
93
|
+
export AGENT_MINIO_CONSOLE_PORT=${MINIO_CONSOLE_PORT}
|
|
94
|
+
export AGENT_BROWSER_PORT=${BROWSER_PORT}
|
|
95
|
+
export AGENT_MAILPIT_WEB_PORT=${MAILPIT_WEB_PORT}
|
|
96
|
+
export AGENT_MAILPIT_SMTP_PORT=${MAILPIT_SMTP_PORT}
|
|
97
|
+
EOF
|
|
98
|
+
|
|
24
99
|
SERVICES="${HARNESS_INFRA_SERVICES:-}"
|
|
25
100
|
|
|
26
101
|
if [ -n "$SERVICES" ]; then
|
|
27
102
|
log "Starting shared infrastructure (project: $COMPOSE_PROJECT): $SERVICES"
|
|
28
103
|
AGENT_DB_PORT="$DB_PORT" \
|
|
29
|
-
AGENT_MINIO_PORT="$
|
|
30
|
-
AGENT_MINIO_CONSOLE_PORT="$
|
|
31
|
-
AGENT_BROWSER_PORT="$
|
|
32
|
-
AGENT_MAILPIT_WEB_PORT="$
|
|
33
|
-
AGENT_MAILPIT_SMTP_PORT="$
|
|
104
|
+
AGENT_MINIO_PORT="$MINIO_PORT" \
|
|
105
|
+
AGENT_MINIO_CONSOLE_PORT="$MINIO_CONSOLE_PORT" \
|
|
106
|
+
AGENT_BROWSER_PORT="$BROWSER_PORT" \
|
|
107
|
+
AGENT_MAILPIT_WEB_PORT="$MAILPIT_WEB_PORT" \
|
|
108
|
+
AGENT_MAILPIT_SMTP_PORT="$MAILPIT_SMTP_PORT" \
|
|
34
109
|
docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE" up -d $SERVICES
|
|
35
110
|
else
|
|
36
111
|
log "No shared infra services enabled in harness.env (HARNESS_INFRA_SERVICES)"
|
|
@@ -85,18 +160,19 @@ fi
|
|
|
85
160
|
# Shared app services — supporting services of a monolith/monorepo that agents
|
|
86
161
|
# depend on but do not modify. Started ONCE on fixed ports, shared by all slots.
|
|
87
162
|
# Create .har/ecosystem.shared.config.cjs (PM2 format, processes named
|
|
88
|
-
# "har-shared-<name>") only when the primary app needs
|
|
163
|
+
# "har-${HARNESS_PROJECT_NAME}-shared-<name>") only when the primary app needs
|
|
164
|
+
# sibling services running.
|
|
89
165
|
SHARED_ECOSYSTEM="$SCRIPT_DIR/ecosystem.shared.config.cjs"
|
|
90
166
|
if [ -f "$SHARED_ECOSYSTEM" ]; then
|
|
91
167
|
log "Starting shared app services from ecosystem.shared.config.cjs..."
|
|
92
168
|
(cd "$REPO_ROOT" && npx --yes pm2 startOrReload "$SHARED_ECOSYSTEM" >/dev/null)
|
|
93
|
-
log "Shared app services running (pm2 ls | grep har-shared-)."
|
|
169
|
+
log "Shared app services running (pm2 ls | grep har-${HARNESS_PROJECT_NAME}-shared-)."
|
|
94
170
|
fi
|
|
95
171
|
|
|
96
172
|
echo ""
|
|
97
173
|
log "Infrastructure is ready."
|
|
98
174
|
har_infra_enabled db && log " PostgreSQL: localhost:$DB_PORT"
|
|
99
|
-
har_infra_enabled minio && log " MinIO: http://localhost:${
|
|
100
|
-
har_infra_enabled headless-browser && log " Browser: http://localhost:${
|
|
101
|
-
har_infra_enabled mailpit && log " Mailpit: http://localhost:${
|
|
175
|
+
har_infra_enabled minio && log " MinIO: http://localhost:${MINIO_CONSOLE_PORT}"
|
|
176
|
+
har_infra_enabled headless-browser && log " Browser: http://localhost:${BROWSER_PORT}"
|
|
177
|
+
har_infra_enabled mailpit && log " Mailpit: http://localhost:${MAILPIT_WEB_PORT}"
|
|
102
178
|
exit 0
|
|
@@ -40,7 +40,8 @@ if [ -f "$REGISTRY_FILE" ]; then
|
|
|
40
40
|
fi
|
|
41
41
|
[ -n "$WORKTREE_PATH" ] || WORKTREE_PATH="$HOME/worktrees/${HARNESS_PROJECT_NAME}-agent-${AGENT_ID}"
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
PM2_REGEX="$(har_pm2_delete_regex "$AGENT_ID")"
|
|
44
|
+
npx --yes pm2 delete "$PM2_REGEX" 2>/dev/null || true
|
|
44
45
|
echo "✓ Stopped PM2 processes"
|
|
45
46
|
|
|
46
47
|
if har_infra_enabled db; then
|
|
@@ -97,6 +97,32 @@ Work in the isolated git worktree created by launch. Use `./.har/agent-cli.sh <i
|
|
|
97
97
|
|
|
98
98
|
When the project needs Postgres, Redis, or similar, add the service to `docker-compose.agent.yml` (or keep one from the menu), list it in `HARNESS_INFRA_SERVICES` in `harness.env`, and use `setup-infra.sh` — never run raw `docker compose` for shared infra. Shared services run once on fixed ports and serve every agent slot.
|
|
99
99
|
|
|
100
|
+
## Port & shared services (CLI profile)
|
|
101
|
+
|
|
102
|
+
This profile has **no PM2 app ports** — agents run project commands directly in their worktree. Port variables in `harness.env` exist for optional test servers and for shared Docker infra.
|
|
103
|
+
|
|
104
|
+
### Port allocation
|
|
105
|
+
|
|
106
|
+
| Layer | Scope | Rule | On conflict |
|
|
107
|
+
|-------|-------|------|-------------|
|
|
108
|
+
| Shared Postgres | Per machine | `HARNESS_DB_PORT_DEFAULT` | Scan `HARNESS_DB_PORT_SCAN_START..END` |
|
|
109
|
+
| Other compose services | Per machine | `HARNESS_*_PORT_DEFAULT` for that service | Scan configured ranges in `harness.env` |
|
|
110
|
+
|
|
111
|
+
When a repo adds a local HTTP server for integration tests, prefer reading ports from `.env.agent.<id>` or `./.har/agent-cli.sh <id>` rather than hardcoding values.
|
|
112
|
+
|
|
113
|
+
### Shared vs per-slot
|
|
114
|
+
|
|
115
|
+
| Resource | Model | Configuration |
|
|
116
|
+
|----------|-------|---------------|
|
|
117
|
+
| Postgres / Redis / mail / … | One shared container on a scanned host port | `HARNESS_INFRA_SERVICES` + matching vars in `harness.env` |
|
|
118
|
+
| Per-slot databases | Cloned from template DB when `db` is enabled | `launch.sh` |
|
|
119
|
+
| Application code | Isolated git worktree per slot | `HARNESS_USE_WORKTREE=true` |
|
|
120
|
+
|
|
121
|
+
### Do not
|
|
122
|
+
|
|
123
|
+
- Hardcode `15432` or other default infra ports in tests — read `AGENT_DB_PORT` from `.env.agent.<id>` or `har_pg`
|
|
124
|
+
- Run raw `docker compose` for harness infrastructure — use `setup-infra.sh`
|
|
125
|
+
|
|
100
126
|
## Maintaining this harness
|
|
101
127
|
|
|
102
128
|
When the project stack changes (new test commands, database needs, env vars):
|
|
@@ -122,6 +148,8 @@ work dir printed by launch, never in the main checkout.
|
|
|
122
148
|
after explicit user approval.
|
|
123
149
|
- `teardown` removes the worktree but **keeps the session branch** so you can push it
|
|
124
150
|
or open a PR (`--delete-branch` to drop it).
|
|
151
|
+
- If launch fails after creating a worktree/env file, resume with
|
|
152
|
+
`har env launch <id> --resume` or `har env recover <id>`.
|
|
125
153
|
- `har env complete <id>` finishes a session: full verify (recorded as a validation),
|
|
126
154
|
then teardown — branch kept.
|
|
127
155
|
- `--no-worktree` runs the slot from the repo root instead (single-agent mode).
|