@osfactory/har 0.1.1
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 +674 -0
- package/README.md +208 -0
- package/control/docker-compose.build.yml +7 -0
- package/control/docker-compose.yml +31 -0
- package/dist/index.js +41753 -0
- package/dist/prompts/system-authoring.md +104 -0
- package/dist/templates/AGENT.md.template +40 -0
- package/dist/templates/CLAUDE.md.template +5 -0
- package/dist/templates/adaptation-prompt-init.md +124 -0
- package/dist/templates/adaptation-prompt-maintain.md +73 -0
- package/dist/templates/cursor-rule.mdc.template +101 -0
- package/dist/templates/har-boilerplate/CLAUDE.agent.md +54 -0
- package/dist/templates/har-boilerplate/README.md +134 -0
- package/dist/templates/har-boilerplate/agent-cli.sh +172 -0
- package/dist/templates/har-boilerplate/agent-slot.sh +270 -0
- package/dist/templates/har-boilerplate/attach.sh +16 -0
- package/dist/templates/har-boilerplate/docker-compose.agent.yml +66 -0
- package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +44 -0
- package/dist/templates/har-boilerplate/env.template +37 -0
- package/dist/templates/har-boilerplate/harness.env +50 -0
- package/dist/templates/har-boilerplate/justfile +39 -0
- package/dist/templates/har-boilerplate/launch.sh +229 -0
- package/dist/templates/har-boilerplate/setup-infra.sh +102 -0
- package/dist/templates/har-boilerplate/stages/README.sh +9 -0
- package/dist/templates/har-boilerplate/stages.json +67 -0
- package/dist/templates/har-boilerplate/teardown.sh +86 -0
- package/dist/templates/har-boilerplate/verify.sh +163 -0
- package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +41 -0
- package/dist/templates/har-boilerplate-cli/README.md +112 -0
- package/dist/templates/har-boilerplate-cli/agent-cli.sh +162 -0
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +270 -0
- package/dist/templates/har-boilerplate-cli/docker-compose.agent.yml +64 -0
- package/dist/templates/har-boilerplate-cli/harness.env +40 -0
- package/dist/templates/har-boilerplate-cli/justfile +27 -0
- package/dist/templates/har-boilerplate-cli/launch.sh +132 -0
- package/dist/templates/har-boilerplate-cli/setup-infra.sh +102 -0
- package/dist/templates/har-boilerplate-cli/stages/README.sh +9 -0
- package/dist/templates/har-boilerplate-cli/stages.json +61 -0
- package/dist/templates/har-boilerplate-cli/teardown.sh +66 -0
- package/dist/templates/har-boilerplate-cli/verify.sh +113 -0
- package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +57 -0
- package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +65 -0
- package/dist/templates/har-boilerplate-ios/README.md +88 -0
- package/dist/templates/har-boilerplate-ios/agent-cli.sh +125 -0
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +270 -0
- package/dist/templates/har-boilerplate-ios/docker-compose.agent.yml +17 -0
- package/dist/templates/har-boilerplate-ios/harness.env +42 -0
- package/dist/templates/har-boilerplate-ios/justfile +33 -0
- package/dist/templates/har-boilerplate-ios/launch.sh +132 -0
- package/dist/templates/har-boilerplate-ios/setup-infra.sh +84 -0
- package/dist/templates/har-boilerplate-ios/stages/README.sh +12 -0
- package/dist/templates/har-boilerplate-ios/stages.json +61 -0
- package/dist/templates/har-boilerplate-ios/teardown.sh +64 -0
- package/dist/templates/har-boilerplate-ios/verify.sh +165 -0
- package/dist/templates/stage-templates/playwright/.github/workflows/playwright.yml +30 -0
- package/dist/templates/stage-templates/playwright/.har/stages/PLAYWRIGHT.md +21 -0
- package/dist/templates/stage-templates/playwright/.har/stages/browser-e2e.sh +83 -0
- package/dist/templates/stage-templates/playwright/README.md +14 -0
- package/dist/templates/stage-templates/playwright/package.fragment.json +14 -0
- package/dist/templates/stage-templates/playwright/playwright.config.js +30 -0
- package/dist/templates/stage-templates/playwright/template.manifest.json +44 -0
- package/dist/templates/stage-templates/playwright/tests/a11y/smoke.spec.js +14 -0
- package/dist/templates/stage-templates/playwright/tests/api/health.spec.js +10 -0
- package/dist/templates/stage-templates/playwright/tests/frontend/smoke.spec.js +9 -0
- package/dist/templates/stage-templates/rocketsim/.har/stages/ROCKETSIM.md +126 -0
- package/dist/templates/stage-templates/rocketsim/.har/stages/rocketsim-flows.sh +188 -0
- package/dist/templates/stage-templates/rocketsim/README.md +66 -0
- package/dist/templates/stage-templates/rocketsim/flows/README.md +36 -0
- package/dist/templates/stage-templates/rocketsim/flows/example-smoke.sh +60 -0
- package/dist/templates/stage-templates/rocketsim/template.manifest.json +38 -0
- package/package.json +78 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Agent harness shortcuts — requires `just` (https://github.com/casey/just)
|
|
2
|
+
|
|
3
|
+
set shell := ["bash", "-cu"]
|
|
4
|
+
|
|
5
|
+
setup:
|
|
6
|
+
./setup-infra.sh
|
|
7
|
+
|
|
8
|
+
launch id:
|
|
9
|
+
./launch.sh {{id}}
|
|
10
|
+
|
|
11
|
+
teardown id:
|
|
12
|
+
./teardown.sh {{id}}
|
|
13
|
+
|
|
14
|
+
verify id:
|
|
15
|
+
./verify.sh {{id}}
|
|
16
|
+
|
|
17
|
+
verify-full id:
|
|
18
|
+
./verify.sh {{id}} --full
|
|
19
|
+
|
|
20
|
+
status:
|
|
21
|
+
#!/usr/bin/env bash
|
|
22
|
+
for id in 1 2 3 4 5; do
|
|
23
|
+
./agent-cli.sh "$id" status 2>/dev/null || true
|
|
24
|
+
done
|
|
25
|
+
|
|
26
|
+
launch-all count:
|
|
27
|
+
#!/usr/bin/env bash
|
|
28
|
+
for id in $(seq 1 {{count}}); do
|
|
29
|
+
./launch.sh "$id"
|
|
30
|
+
done
|
|
31
|
+
|
|
32
|
+
teardown-all:
|
|
33
|
+
#!/usr/bin/env bash
|
|
34
|
+
for id in 1 2 3 4 5; do
|
|
35
|
+
./teardown.sh "$id" 2>/dev/null || true
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
cli id cmd *args:
|
|
39
|
+
./agent-cli.sh {{id}} {{cmd}} {{args}}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Launches a single agent instance with isolated ports, database, and PM2 processes.
|
|
3
|
+
# Every launch starts a FRESH session: any previous session for the slot is torn
|
|
4
|
+
# down (its branch is kept) and a new suffixed worktree is created from HEAD.
|
|
5
|
+
#
|
|
6
|
+
# Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--claude] [--replace] [--force]
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
+
|
|
12
|
+
# shellcheck source=/dev/null
|
|
13
|
+
source "$SCRIPT_DIR/harness.env"
|
|
14
|
+
# shellcheck source=/dev/null
|
|
15
|
+
source "$SCRIPT_DIR/agent-slot.sh"
|
|
16
|
+
|
|
17
|
+
AGENT_ID="${1:-}"
|
|
18
|
+
USE_WORKTREE="${HARNESS_USE_WORKTREE:-true}"
|
|
19
|
+
USE_CLAUDE=false
|
|
20
|
+
FORCE=false
|
|
21
|
+
REPLACE=false
|
|
22
|
+
PURPOSE="${HAR_SESSION_PURPOSE:-}"
|
|
23
|
+
|
|
24
|
+
for arg in "$@"; do
|
|
25
|
+
case "$arg" in
|
|
26
|
+
--no-worktree) USE_WORKTREE=false ;;
|
|
27
|
+
--worktree) USE_WORKTREE=true ;;
|
|
28
|
+
--claude) USE_CLAUDE=true ;;
|
|
29
|
+
--replace) REPLACE=true ;;
|
|
30
|
+
--force) FORCE=true ;;
|
|
31
|
+
--purpose=*) PURPOSE="${arg#--purpose=}" ;;
|
|
32
|
+
esac
|
|
33
|
+
done
|
|
34
|
+
|
|
35
|
+
if [[ -z "$AGENT_ID" ]]; then
|
|
36
|
+
echo "Usage: $0 <agent-id> [--no-worktree] [--claude] [--replace] [--force] [--purpose=label]" >&2
|
|
37
|
+
echo " agent-id must be between ${HARNESS_AGENT_SLOT_MIN} and ${HARNESS_AGENT_SLOT_MAX}" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
validate_agent_id "$AGENT_ID"
|
|
42
|
+
|
|
43
|
+
log() { echo "==> [agent-$AGENT_ID] $*" >&2; }
|
|
44
|
+
|
|
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}"
|
|
51
|
+
|
|
52
|
+
log "Ports: frontend=$FE_PORT api=$API_PORT debug=$DEBUG_PORT"
|
|
53
|
+
|
|
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
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Ensure shared infra is running
|
|
62
|
+
"$SCRIPT_DIR/setup-infra.sh"
|
|
63
|
+
|
|
64
|
+
# Clone agent database from template
|
|
65
|
+
if har_infra_enabled db && [ -n "${HARNESS_TEMPLATE_DB:-}" ]; then
|
|
66
|
+
AGENT_DB="agent_${AGENT_ID}"
|
|
67
|
+
PSQL="har_pg psql -d postgres"
|
|
68
|
+
if $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '$AGENT_DB'" | grep -q 1; then
|
|
69
|
+
log "Database '$AGENT_DB' already exists."
|
|
70
|
+
else
|
|
71
|
+
log "Creating database '$AGENT_DB' from template..."
|
|
72
|
+
$PSQL -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$HARNESS_TEMPLATE_DB' AND pid <> pg_backend_pid()" >/dev/null 2>&1 || true
|
|
73
|
+
har_pg createdb -T "$HARNESS_TEMPLATE_DB" "$AGENT_DB"
|
|
74
|
+
log "Database '$AGENT_DB' created."
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# Create MinIO bucket
|
|
79
|
+
if har_infra_enabled minio; then
|
|
80
|
+
MINIO_BUCKET="agent-${AGENT_ID}"
|
|
81
|
+
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
|
|
82
|
+
-u "minioadmin:minioadmin" \
|
|
83
|
+
"http://localhost:${MINIO_PORT}/${MINIO_BUCKET}/" 2>/dev/null) || true
|
|
84
|
+
log "MinIO bucket '$MINIO_BUCKET' ready (HTTP $HTTP_STATUS)."
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# 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
|
|
120
|
+
|
|
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
|
|
126
|
+
|
|
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
|
|
136
|
+
|
|
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"
|
|
149
|
+
|
|
150
|
+
# Generate PM2 ecosystem config
|
|
151
|
+
ECOSYSTEM_FILE="$WORK_DIR/ecosystem.agent.${AGENT_ID}.config.cjs"
|
|
152
|
+
log "Generating $ECOSYSTEM_FILE..."
|
|
153
|
+
AGENT_ID="$AGENT_ID" \
|
|
154
|
+
FE_PORT="$FE_PORT" \
|
|
155
|
+
DEBUG_PORT="$DEBUG_PORT" \
|
|
156
|
+
envsubst '${AGENT_ID} ${FE_PORT} ${DEBUG_PORT}' \
|
|
157
|
+
< "$SCRIPT_DIR/ecosystem.agent.template.cjs" > "$ECOSYSTEM_FILE"
|
|
158
|
+
|
|
159
|
+
# Stop existing processes for this agent
|
|
160
|
+
npx --yes pm2 delete "/^agent-${AGENT_ID}-/" 2>/dev/null || true
|
|
161
|
+
|
|
162
|
+
# Start PM2 processes
|
|
163
|
+
log "Starting PM2 processes..."
|
|
164
|
+
cd "$WORK_DIR"
|
|
165
|
+
npx pm2 start "$ECOSYSTEM_FILE"
|
|
166
|
+
npx pm2 save --force >/dev/null 2>&1 || true
|
|
167
|
+
|
|
168
|
+
# Health check
|
|
169
|
+
if [ -n "${HARNESS_HEALTH_CHECK_PATH:-}" ]; then
|
|
170
|
+
HEALTH_URL="http://localhost:${API_PORT}${HARNESS_HEALTH_CHECK_PATH}"
|
|
171
|
+
log "Waiting for health check at $HEALTH_URL..."
|
|
172
|
+
TIMEOUT=60
|
|
173
|
+
ELAPSED=0
|
|
174
|
+
while [ "$ELAPSED" -lt "$TIMEOUT" ]; do
|
|
175
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" 2>/dev/null) || true
|
|
176
|
+
if [ "$HTTP_CODE" = "200" ]; then
|
|
177
|
+
log "Health check passed!"
|
|
178
|
+
break
|
|
179
|
+
fi
|
|
180
|
+
sleep 2
|
|
181
|
+
ELAPSED=$((ELAPSED + 2))
|
|
182
|
+
done
|
|
183
|
+
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
|
|
184
|
+
log "Warning: Health check did not pass within ${TIMEOUT}s."
|
|
185
|
+
log "Check logs: ./.har/agent-cli.sh $AGENT_ID logs"
|
|
186
|
+
fi
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Record the session in the slot registry — the source of truth for where
|
|
190
|
+
# this slot's code lives (status/verify/teardown resolve through it).
|
|
191
|
+
SLOT_AGENT_ID="$AGENT_ID" \
|
|
192
|
+
SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
|
|
193
|
+
SLOT_WORK_DIR="$WORK_DIR" \
|
|
194
|
+
SLOT_SUFFIX="${SUFFIX:-}" \
|
|
195
|
+
SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
|
|
196
|
+
SLOT_BRANCH="${BRANCH:-}" \
|
|
197
|
+
SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
|
|
198
|
+
SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
|
|
199
|
+
SLOT_PURPOSE="${PURPOSE}" \
|
|
200
|
+
SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT}}" \
|
|
201
|
+
SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
|
|
202
|
+
write_slot_registry
|
|
203
|
+
|
|
204
|
+
# Launch Claude Code in tmux (optional)
|
|
205
|
+
if [ "$USE_CLAUDE" = true ]; then
|
|
206
|
+
TMUX_SESSION="agent-${AGENT_ID}"
|
|
207
|
+
if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
|
|
208
|
+
tmux kill-session -t "$TMUX_SESSION"
|
|
209
|
+
fi
|
|
210
|
+
tmux new-session -d -s "$TMUX_SESSION" -c "$WORK_DIR" "claude"
|
|
211
|
+
log "Claude session: tmux attach -t $TMUX_SESSION"
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
echo ""
|
|
215
|
+
log "Agent $AGENT_ID is ready!"
|
|
216
|
+
log ""
|
|
217
|
+
log " WORK DIR (make ALL file edits under this path — never the main checkout):"
|
|
218
|
+
log " ${WORK_DIR}"
|
|
219
|
+
if [ "$USE_WORKTREE" = true ]; then
|
|
220
|
+
log " Branch: ${BRANCH} (based on ${BASE_BRANCH} @ ${BASE_COMMIT})"
|
|
221
|
+
fi
|
|
222
|
+
log " Frontend: http://localhost:${FE_PORT}"
|
|
223
|
+
log " API: http://localhost:${API_PORT}"
|
|
224
|
+
har_infra_enabled db && log " Database: agent_${AGENT_ID} @ localhost:${DB_PORT}"
|
|
225
|
+
log ""
|
|
226
|
+
log " Edits under the work dir hot-reload in the running slot;"
|
|
227
|
+
log " use ./.har/agent-cli.sh $AGENT_ID restart if a change doesn't take."
|
|
228
|
+
log " Verify: ./.har/verify.sh $AGENT_ID"
|
|
229
|
+
log " Teardown: ./.har/teardown.sh $AGENT_ID (keeps the branch)"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Sets up shared infrastructure for all agents.
|
|
3
|
+
# Starts the docker compose services listed in HARNESS_INFRA_SERVICES, creates
|
|
4
|
+
# the template database (when "db" is enabled), and starts optional shared app
|
|
5
|
+
# services (ecosystem.shared.config.cjs). One instance serves every agent slot.
|
|
6
|
+
# Idempotent — safe to run multiple times.
|
|
7
|
+
#
|
|
8
|
+
# Usage: ./.har/setup-infra.sh
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
|
+
COMPOSE_FILE="$SCRIPT_DIR/docker-compose.agent.yml"
|
|
14
|
+
|
|
15
|
+
# shellcheck source=/dev/null
|
|
16
|
+
source "$SCRIPT_DIR/harness.env"
|
|
17
|
+
|
|
18
|
+
COMPOSE_PROJECT="har-${HARNESS_PROJECT_NAME}"
|
|
19
|
+
DB_PORT="${AGENT_DB_PORT:-15432}"
|
|
20
|
+
PSQL="har_pg psql -d postgres"
|
|
21
|
+
|
|
22
|
+
log() { echo "==> $*" >&2; }
|
|
23
|
+
|
|
24
|
+
SERVICES="${HARNESS_INFRA_SERVICES:-}"
|
|
25
|
+
|
|
26
|
+
if [ -n "$SERVICES" ]; then
|
|
27
|
+
log "Starting shared infrastructure (project: $COMPOSE_PROJECT): $SERVICES"
|
|
28
|
+
AGENT_DB_PORT="$DB_PORT" \
|
|
29
|
+
AGENT_MINIO_PORT="${AGENT_MINIO_PORT:-19000}" \
|
|
30
|
+
AGENT_MINIO_CONSOLE_PORT="${AGENT_MINIO_CONSOLE_PORT:-19001}" \
|
|
31
|
+
AGENT_BROWSER_PORT="${AGENT_BROWSER_PORT:-13001}" \
|
|
32
|
+
AGENT_MAILPIT_WEB_PORT="${AGENT_MAILPIT_WEB_PORT:-18025}" \
|
|
33
|
+
AGENT_MAILPIT_SMTP_PORT="${AGENT_MAILPIT_SMTP_PORT:-11025}" \
|
|
34
|
+
docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE" up -d $SERVICES
|
|
35
|
+
else
|
|
36
|
+
log "No shared infra services enabled in harness.env (HARNESS_INFRA_SERVICES)"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Wait for PostgreSQL and prepare the template database
|
|
40
|
+
if har_infra_enabled db; then
|
|
41
|
+
log "Waiting for PostgreSQL on port $DB_PORT..."
|
|
42
|
+
for i in $(seq 1 30); do
|
|
43
|
+
if har_pg pg_isready -q 2>/dev/null; then
|
|
44
|
+
log "PostgreSQL is ready."
|
|
45
|
+
break
|
|
46
|
+
fi
|
|
47
|
+
if [ "$i" = "30" ]; then
|
|
48
|
+
echo "Error: PostgreSQL did not become ready within 30 seconds." >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
sleep 1
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
log "Enabling pg_stat_statements extension..."
|
|
55
|
+
$PSQL -c "CREATE EXTENSION IF NOT EXISTS pg_stat_statements" 2>/dev/null || true
|
|
56
|
+
|
|
57
|
+
if [ -n "${HARNESS_TEMPLATE_DB:-}" ]; then
|
|
58
|
+
if $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '$HARNESS_TEMPLATE_DB'" | grep -q 1; then
|
|
59
|
+
log "Template database '$HARNESS_TEMPLATE_DB' already exists. Skipping creation."
|
|
60
|
+
else
|
|
61
|
+
log "Creating template database '$HARNESS_TEMPLATE_DB'..."
|
|
62
|
+
$PSQL -c "CREATE DATABASE $HARNESS_TEMPLATE_DB"
|
|
63
|
+
|
|
64
|
+
if [ -n "${HARNESS_DB_MIGRATE_CMD:-}" ] && [ "$HARNESS_DB_MIGRATE_CMD" != "echo 'TODO: set migrate command'" ]; then
|
|
65
|
+
log "Running migrations..."
|
|
66
|
+
PGPASSWORD=password PGHOST=localhost PGPORT="$DB_PORT" PGUSER=postgres \
|
|
67
|
+
PGDATABASE="$HARNESS_TEMPLATE_DB" \
|
|
68
|
+
bash -c "cd '$REPO_ROOT' && $HARNESS_DB_MIGRATE_CMD"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
if [ -n "${HARNESS_DB_SEED_CMD:-}" ] && [ "$HARNESS_DB_SEED_CMD" != "echo 'TODO: set seed command'" ]; then
|
|
72
|
+
log "Running seeds..."
|
|
73
|
+
PGPASSWORD=password PGHOST=localhost PGPORT="$DB_PORT" PGUSER=postgres \
|
|
74
|
+
PGDATABASE="$HARNESS_TEMPLATE_DB" \
|
|
75
|
+
bash -c "cd '$REPO_ROOT' && $HARNESS_DB_SEED_CMD"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
log "Marking '$HARNESS_TEMPLATE_DB' as a PostgreSQL template..."
|
|
79
|
+
$PSQL -c "UPDATE pg_database SET datistemplate = true WHERE datname = '$HARNESS_TEMPLATE_DB'"
|
|
80
|
+
log "Template database ready: $HARNESS_TEMPLATE_DB"
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Shared app services — supporting services of a monolith/monorepo that agents
|
|
86
|
+
# depend on but do not modify. Started ONCE on fixed ports, shared by all slots.
|
|
87
|
+
# Create .har/ecosystem.shared.config.cjs (PM2 format, processes named
|
|
88
|
+
# "har-shared-<name>") only when the primary app needs sibling services running.
|
|
89
|
+
SHARED_ECOSYSTEM="$SCRIPT_DIR/ecosystem.shared.config.cjs"
|
|
90
|
+
if [ -f "$SHARED_ECOSYSTEM" ]; then
|
|
91
|
+
log "Starting shared app services from ecosystem.shared.config.cjs..."
|
|
92
|
+
(cd "$REPO_ROOT" && npx --yes pm2 startOrReload "$SHARED_ECOSYSTEM" >/dev/null)
|
|
93
|
+
log "Shared app services running (pm2 ls | grep har-shared-)."
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
echo ""
|
|
97
|
+
log "Infrastructure is ready."
|
|
98
|
+
har_infra_enabled db && log " PostgreSQL: localhost:$DB_PORT"
|
|
99
|
+
har_infra_enabled minio && log " MinIO: http://localhost:${AGENT_MINIO_CONSOLE_PORT:-19001}"
|
|
100
|
+
har_infra_enabled headless-browser && log " Browser: http://localhost:${AGENT_BROWSER_PORT:-13001}"
|
|
101
|
+
har_infra_enabled mailpit && log " Mailpit: http://localhost:${AGENT_MAILPIT_WEB_PORT:-18025}"
|
|
102
|
+
exit 0
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Optional per-stage scripts live here.
|
|
3
|
+
# Example: ./.har/stages/browser-e2e.sh <agent-id>
|
|
4
|
+
#
|
|
5
|
+
# Register custom stages in .har/stages.json with a project-owned command:
|
|
6
|
+
# "command": "./.har/stages/browser-e2e.sh {agentId}"
|
|
7
|
+
|
|
8
|
+
echo "Add stage scripts under .har/stages/ and register them in stages.json." >&2
|
|
9
|
+
exit 1
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1",
|
|
3
|
+
"agentSlots": {
|
|
4
|
+
"min": 1,
|
|
5
|
+
"max": 5
|
|
6
|
+
},
|
|
7
|
+
"stages": [
|
|
8
|
+
{
|
|
9
|
+
"id": "setup-infra",
|
|
10
|
+
"kind": "setup",
|
|
11
|
+
"description": "Shared infrastructure (Docker, template DB)",
|
|
12
|
+
"command": "./.har/setup-infra.sh"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "launch",
|
|
16
|
+
"kind": "launch",
|
|
17
|
+
"description": "Launch an isolated agent environment slot",
|
|
18
|
+
"command": "./.har/launch.sh {agentId}",
|
|
19
|
+
"artifacts": [
|
|
20
|
+
{
|
|
21
|
+
"path": ".env.agent.{agentId}",
|
|
22
|
+
"kind": "file",
|
|
23
|
+
"description": "Generated per-agent environment file"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "verify",
|
|
29
|
+
"kind": "verify",
|
|
30
|
+
"description": "Verification pipeline (quick by default; --full adds lint and browser-e2e when installed)",
|
|
31
|
+
"command": "./.har/verify.sh {agentId}",
|
|
32
|
+
"acceptsArgs": ["--full"],
|
|
33
|
+
"artifacts": [
|
|
34
|
+
{
|
|
35
|
+
"path": ".har/artifacts",
|
|
36
|
+
"kind": "directory",
|
|
37
|
+
"description": "Optional verification outputs such as reports, traces, screenshots, and logs"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "status",
|
|
43
|
+
"kind": "inspect",
|
|
44
|
+
"description": "Inspect a running agent environment slot",
|
|
45
|
+
"command": "./.har/agent-cli.sh {agentId} status",
|
|
46
|
+
"artifacts": [
|
|
47
|
+
{
|
|
48
|
+
"path": ".har/logs",
|
|
49
|
+
"kind": "directory",
|
|
50
|
+
"description": "Optional harness logs"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "logs",
|
|
56
|
+
"kind": "inspect",
|
|
57
|
+
"description": "Read recent logs for an agent slot",
|
|
58
|
+
"command": "./.har/agent-cli.sh {agentId} logs"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "teardown",
|
|
62
|
+
"kind": "teardown",
|
|
63
|
+
"description": "Stop processes and release agent slot resources",
|
|
64
|
+
"command": "./.har/teardown.sh {agentId}"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Safely tears down one agent: stops PM2, drops DB, removes bucket, cleans up files.
|
|
3
|
+
# The session's git branch is KEPT by default so you can push it / open a PR —
|
|
4
|
+
# pass --delete-branch to remove it too.
|
|
5
|
+
#
|
|
6
|
+
# Usage: ./.har/teardown.sh <agent-id> [--delete-branch]
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
+
|
|
12
|
+
# shellcheck source=/dev/null
|
|
13
|
+
source "$SCRIPT_DIR/harness.env"
|
|
14
|
+
# shellcheck source=/dev/null
|
|
15
|
+
source "$SCRIPT_DIR/agent-slot.sh"
|
|
16
|
+
|
|
17
|
+
AGENT_ID="${1:?Usage: teardown.sh <agent-id> [--delete-branch]}"
|
|
18
|
+
DELETE_BRANCH=false
|
|
19
|
+
|
|
20
|
+
for arg in "${@:2}"; do
|
|
21
|
+
case "$arg" in
|
|
22
|
+
--delete-branch) DELETE_BRANCH=true ;;
|
|
23
|
+
esac
|
|
24
|
+
done
|
|
25
|
+
|
|
26
|
+
validate_agent_id "$AGENT_ID"
|
|
27
|
+
|
|
28
|
+
echo "==> Tearing down agent ${AGENT_ID}..."
|
|
29
|
+
|
|
30
|
+
# Resolve the session from the registry; fall back to the legacy fixed path so
|
|
31
|
+
# pre-registry sessions stay removable.
|
|
32
|
+
REGISTRY_FILE="$(slot_registry_file "$AGENT_ID")"
|
|
33
|
+
WORKTREE_PATH=""
|
|
34
|
+
WORK_DIR=""
|
|
35
|
+
BRANCH=""
|
|
36
|
+
if [ -f "$REGISTRY_FILE" ]; then
|
|
37
|
+
WORKTREE_PATH="$(read_slot_field "$REGISTRY_FILE" worktreePath || true)"
|
|
38
|
+
WORK_DIR="$(read_slot_field "$REGISTRY_FILE" workDir || true)"
|
|
39
|
+
BRANCH="$(read_slot_field "$REGISTRY_FILE" branch || true)"
|
|
40
|
+
fi
|
|
41
|
+
[ -n "$WORKTREE_PATH" ] || WORKTREE_PATH="$HOME/worktrees/${HARNESS_PROJECT_NAME}-agent-${AGENT_ID}"
|
|
42
|
+
|
|
43
|
+
npx --yes pm2 delete "/^agent-${AGENT_ID}-/" 2>/dev/null || true
|
|
44
|
+
echo "✓ Stopped PM2 processes"
|
|
45
|
+
|
|
46
|
+
if har_infra_enabled db; then
|
|
47
|
+
har_pg psql -d postgres -c \
|
|
48
|
+
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='agent_${AGENT_ID}';" \
|
|
49
|
+
>/dev/null 2>&1 || true
|
|
50
|
+
har_pg dropdb --if-exists "agent_${AGENT_ID}" 2>/dev/null || true
|
|
51
|
+
echo "✓ Dropped database: agent_${AGENT_ID}"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
if har_infra_enabled minio; then
|
|
55
|
+
curl -sf -X DELETE "http://minioadmin:minioadmin@localhost:19000/agent-${AGENT_ID}?force=true" \
|
|
56
|
+
>/dev/null 2>&1 || true
|
|
57
|
+
echo "✓ Removed MinIO bucket: agent-${AGENT_ID}"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
rm -f "$REPO_ROOT/.env.agent.${AGENT_ID}"
|
|
61
|
+
rm -f "$REPO_ROOT/ecosystem.agent.${AGENT_ID}.config.cjs"
|
|
62
|
+
if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
|
|
63
|
+
rm -f "$WORK_DIR/.env.agent.${AGENT_ID}"
|
|
64
|
+
rm -f "$WORK_DIR/ecosystem.agent.${AGENT_ID}.config.cjs"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
if [ -d "$WORKTREE_PATH" ]; then
|
|
68
|
+
rm -f "$WORKTREE_PATH/.env.agent.${AGENT_ID}"
|
|
69
|
+
rm -f "$WORKTREE_PATH/ecosystem.agent.${AGENT_ID}.config.cjs"
|
|
70
|
+
git -C "$REPO_ROOT" worktree remove "$WORKTREE_PATH" --force 2>/dev/null || rm -rf "$WORKTREE_PATH"
|
|
71
|
+
echo "✓ Removed worktree: $WORKTREE_PATH"
|
|
72
|
+
fi
|
|
73
|
+
git -C "$REPO_ROOT" worktree prune 2>/dev/null || true
|
|
74
|
+
|
|
75
|
+
if [ -n "$BRANCH" ]; then
|
|
76
|
+
if [ "$DELETE_BRANCH" = true ]; then
|
|
77
|
+
git -C "$REPO_ROOT" branch -D "$BRANCH" 2>/dev/null || true
|
|
78
|
+
echo "✓ Deleted branch: $BRANCH"
|
|
79
|
+
else
|
|
80
|
+
echo "✓ Kept branch: $BRANCH (push it or delete with: git branch -D $BRANCH)"
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
remove_slot_registry "$AGENT_ID"
|
|
85
|
+
|
|
86
|
+
echo "✓ Agent ${AGENT_ID} torn down"
|