@imdeadpool/guardex 6.0.0 → 6.1.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/bin/multiagent-safety.js +21 -5
- package/package.json +3 -1
- package/templates/AGENTS.multiagent-safety.md +1 -0
- package/templates/githooks/post-checkout +68 -0
- package/templates/githooks/post-merge +3 -39
- package/templates/githooks/pre-commit +193 -27
- package/templates/githooks/pre-push +0 -0
- package/templates/scripts/agent-branch-finish.sh +702 -70
- package/templates/scripts/agent-branch-start.sh +877 -76
- package/templates/scripts/agent-worktree-prune.sh +353 -65
- package/templates/scripts/codex-agent.sh +238 -626
- package/templates/scripts/install-agent-git-hooks.sh +27 -4
- package/templates/scripts/openspec/init-change-workspace.sh +50 -4
- package/templates/scripts/openspec/init-plan-workspace.sh +495 -48
- package/templates/scripts/review-bot-watch.sh +11 -11
|
@@ -6,33 +6,11 @@ AGENT_NAME="${GUARDEX_AGENT_NAME:-agent}"
|
|
|
6
6
|
BASE_BRANCH="${GUARDEX_BASE_BRANCH:-}"
|
|
7
7
|
BASE_BRANCH_EXPLICIT=0
|
|
8
8
|
CODEX_BIN="${GUARDEX_CODEX_BIN:-codex}"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
|
|
15
|
-
OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
|
|
16
|
-
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
|
|
17
|
-
|
|
18
|
-
normalize_bool() {
|
|
19
|
-
local raw="${1:-}"
|
|
20
|
-
local fallback="${2:-0}"
|
|
21
|
-
local lowered
|
|
22
|
-
lowered="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
|
|
23
|
-
case "$lowered" in
|
|
24
|
-
1|true|yes|on) printf '1' ;;
|
|
25
|
-
0|false|no|off) printf '0' ;;
|
|
26
|
-
'') printf '%s' "$fallback" ;;
|
|
27
|
-
*) printf '%s' "$fallback" ;;
|
|
28
|
-
esac
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
AUTO_FINISH="$(normalize_bool "$AUTO_FINISH_RAW" "1")"
|
|
32
|
-
AUTO_REVIEW_ON_CONFLICT="$(normalize_bool "$AUTO_REVIEW_ON_CONFLICT_RAW" "1")"
|
|
33
|
-
AUTO_CLEANUP="$(normalize_bool "$AUTO_CLEANUP_RAW" "1")"
|
|
34
|
-
AUTO_WAIT_FOR_MERGE="$(normalize_bool "$AUTO_WAIT_FOR_MERGE_RAW" "1")"
|
|
35
|
-
OPENSPEC_AUTO_INIT="$(normalize_bool "$OPENSPEC_AUTO_INIT_RAW" "1")"
|
|
9
|
+
GH_PR_REF="${GUARDEX_GH_PR_REF:-}"
|
|
10
|
+
GH_REPO_REF="${GUARDEX_GH_REPO:-}"
|
|
11
|
+
GH_SYNC_FLAG=""
|
|
12
|
+
ROLE=""
|
|
13
|
+
CODEX_EXTRA_ARGS=()
|
|
36
14
|
|
|
37
15
|
if [[ -n "$BASE_BRANCH" ]]; then
|
|
38
16
|
BASE_BRANCH_EXPLICIT=1
|
|
@@ -57,37 +35,25 @@ while [[ $# -gt 0 ]]; do
|
|
|
57
35
|
CODEX_BIN="${2:-$CODEX_BIN}"
|
|
58
36
|
shift 2
|
|
59
37
|
;;
|
|
60
|
-
--
|
|
61
|
-
|
|
62
|
-
shift
|
|
63
|
-
;;
|
|
64
|
-
--no-auto-finish)
|
|
65
|
-
AUTO_FINISH=0
|
|
66
|
-
shift
|
|
67
|
-
;;
|
|
68
|
-
--auto-review-on-conflict)
|
|
69
|
-
AUTO_REVIEW_ON_CONFLICT=1
|
|
70
|
-
shift
|
|
71
|
-
;;
|
|
72
|
-
--no-auto-review-on-conflict)
|
|
73
|
-
AUTO_REVIEW_ON_CONFLICT=0
|
|
74
|
-
shift
|
|
38
|
+
--pr)
|
|
39
|
+
GH_PR_REF="${2:-$GH_PR_REF}"
|
|
40
|
+
shift 2
|
|
75
41
|
;;
|
|
76
|
-
--
|
|
77
|
-
|
|
78
|
-
shift
|
|
42
|
+
--repo)
|
|
43
|
+
GH_REPO_REF="${2:-$GH_REPO_REF}"
|
|
44
|
+
shift 2
|
|
79
45
|
;;
|
|
80
|
-
--
|
|
81
|
-
|
|
46
|
+
--gh-sync)
|
|
47
|
+
GH_SYNC_FLAG="--gh-sync"
|
|
82
48
|
shift
|
|
83
49
|
;;
|
|
84
|
-
--
|
|
85
|
-
|
|
50
|
+
--no-gh-sync)
|
|
51
|
+
GH_SYNC_FLAG="--no-gh-sync"
|
|
86
52
|
shift
|
|
87
53
|
;;
|
|
88
|
-
--
|
|
89
|
-
|
|
90
|
-
shift
|
|
54
|
+
--role)
|
|
55
|
+
ROLE="${2:-$ROLE}"
|
|
56
|
+
shift 2
|
|
91
57
|
;;
|
|
92
58
|
--)
|
|
93
59
|
shift
|
|
@@ -130,160 +96,6 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
|
130
96
|
fi
|
|
131
97
|
repo_root="$(git rev-parse --show-toplevel)"
|
|
132
98
|
|
|
133
|
-
sanitize_slug() {
|
|
134
|
-
local raw="$1"
|
|
135
|
-
local fallback="${2:-task}"
|
|
136
|
-
local slug
|
|
137
|
-
slug="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g')"
|
|
138
|
-
if [[ -z "$slug" ]]; then
|
|
139
|
-
slug="$fallback"
|
|
140
|
-
fi
|
|
141
|
-
printf '%s' "$slug"
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
resolve_openspec_plan_slug() {
|
|
145
|
-
local branch_name="$1"
|
|
146
|
-
local task_slug
|
|
147
|
-
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
|
|
148
|
-
if [[ -n "$OPENSPEC_PLAN_SLUG_OVERRIDE" ]]; then
|
|
149
|
-
sanitize_slug "$OPENSPEC_PLAN_SLUG_OVERRIDE" "$task_slug"
|
|
150
|
-
return 0
|
|
151
|
-
fi
|
|
152
|
-
sanitize_slug "${branch_name//\//-}" "$task_slug"
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
resolve_openspec_change_slug() {
|
|
156
|
-
local branch_name="$1"
|
|
157
|
-
local task_slug
|
|
158
|
-
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
|
|
159
|
-
if [[ -n "$OPENSPEC_CHANGE_SLUG_OVERRIDE" ]]; then
|
|
160
|
-
sanitize_slug "$OPENSPEC_CHANGE_SLUG_OVERRIDE" "$task_slug"
|
|
161
|
-
return 0
|
|
162
|
-
fi
|
|
163
|
-
sanitize_slug "${branch_name//\//-}" "$task_slug"
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
resolve_openspec_capability_slug() {
|
|
167
|
-
local task_slug
|
|
168
|
-
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
|
|
169
|
-
if [[ -n "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" ]]; then
|
|
170
|
-
sanitize_slug "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" "$task_slug"
|
|
171
|
-
return 0
|
|
172
|
-
fi
|
|
173
|
-
sanitize_slug "$task_slug" "general-behavior"
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
hydrate_local_helper_in_worktree() {
|
|
177
|
-
local worktree="$1"
|
|
178
|
-
local relative_path="$2"
|
|
179
|
-
local worktree_target="${worktree}/${relative_path}"
|
|
180
|
-
local source_path=""
|
|
181
|
-
|
|
182
|
-
if [[ -e "$worktree_target" ]]; then
|
|
183
|
-
return 0
|
|
184
|
-
fi
|
|
185
|
-
|
|
186
|
-
if [[ -f "${repo_root}/${relative_path}" ]]; then
|
|
187
|
-
source_path="${repo_root}/${relative_path}"
|
|
188
|
-
elif [[ -f "${repo_root}/templates/${relative_path}" ]]; then
|
|
189
|
-
source_path="${repo_root}/templates/${relative_path}"
|
|
190
|
-
fi
|
|
191
|
-
|
|
192
|
-
if [[ -z "$source_path" ]]; then
|
|
193
|
-
return 0
|
|
194
|
-
fi
|
|
195
|
-
|
|
196
|
-
mkdir -p "$(dirname "$worktree_target")"
|
|
197
|
-
cp "$source_path" "$worktree_target"
|
|
198
|
-
if [[ -x "$source_path" ]]; then
|
|
199
|
-
chmod +x "$worktree_target"
|
|
200
|
-
fi
|
|
201
|
-
|
|
202
|
-
echo "[codex-agent] Hydrated local helper in sandbox: ${relative_path}"
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
resolve_start_base_branch() {
|
|
206
|
-
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -n "$BASE_BRANCH" ]]; then
|
|
207
|
-
printf '%s' "$BASE_BRANCH"
|
|
208
|
-
return 0
|
|
209
|
-
fi
|
|
210
|
-
|
|
211
|
-
local configured_base
|
|
212
|
-
configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
|
|
213
|
-
if [[ -n "$configured_base" ]]; then
|
|
214
|
-
printf '%s' "$configured_base"
|
|
215
|
-
return 0
|
|
216
|
-
fi
|
|
217
|
-
|
|
218
|
-
printf 'dev'
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
resolve_start_ref() {
|
|
222
|
-
local base_branch="$1"
|
|
223
|
-
git -C "$repo_root" fetch origin "$base_branch" --quiet >/dev/null 2>&1 || true
|
|
224
|
-
if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${base_branch}"; then
|
|
225
|
-
printf 'origin/%s' "$base_branch"
|
|
226
|
-
return 0
|
|
227
|
-
fi
|
|
228
|
-
if git -C "$repo_root" show-ref --verify --quiet "refs/heads/${base_branch}"; then
|
|
229
|
-
printf '%s' "$base_branch"
|
|
230
|
-
return 0
|
|
231
|
-
fi
|
|
232
|
-
return 1
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
restore_repo_branch_if_changed() {
|
|
236
|
-
local expected_branch="$1"
|
|
237
|
-
if [[ -z "$expected_branch" || "$expected_branch" == "HEAD" ]]; then
|
|
238
|
-
return 0
|
|
239
|
-
fi
|
|
240
|
-
local current_branch
|
|
241
|
-
current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
242
|
-
if [[ -z "$current_branch" || "$current_branch" == "$expected_branch" ]]; then
|
|
243
|
-
return 0
|
|
244
|
-
fi
|
|
245
|
-
git -C "$repo_root" checkout "$expected_branch" >/dev/null 2>&1
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
start_sandbox_fallback() {
|
|
249
|
-
local base_branch start_ref timestamp task_slug agent_slug branch_name_base branch_name suffix
|
|
250
|
-
local worktree_root worktree_path
|
|
251
|
-
|
|
252
|
-
base_branch="$(resolve_start_base_branch)"
|
|
253
|
-
if ! start_ref="$(resolve_start_ref "$base_branch")"; then
|
|
254
|
-
echo "[codex-agent] Unable to resolve base ref for fallback sandbox start: ${base_branch}" >&2
|
|
255
|
-
return 1
|
|
256
|
-
fi
|
|
257
|
-
|
|
258
|
-
timestamp="$(date +%Y%m%d-%H%M%S)"
|
|
259
|
-
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
|
|
260
|
-
agent_slug="$(sanitize_slug "$AGENT_NAME" "agent")"
|
|
261
|
-
branch_name_base="agent/${agent_slug}/${timestamp}-${task_slug}"
|
|
262
|
-
branch_name="$branch_name_base"
|
|
263
|
-
suffix=2
|
|
264
|
-
while git -C "$repo_root" show-ref --verify --quiet "refs/heads/${branch_name}"; do
|
|
265
|
-
branch_name="${branch_name_base}-${suffix}"
|
|
266
|
-
suffix=$((suffix + 1))
|
|
267
|
-
done
|
|
268
|
-
|
|
269
|
-
worktree_root="${repo_root}/.omx/agent-worktrees"
|
|
270
|
-
mkdir -p "$worktree_root"
|
|
271
|
-
worktree_path="${worktree_root}/${branch_name//\//__}"
|
|
272
|
-
if [[ -e "$worktree_path" ]]; then
|
|
273
|
-
echo "[codex-agent] Fallback worktree path already exists: $worktree_path" >&2
|
|
274
|
-
return 1
|
|
275
|
-
fi
|
|
276
|
-
|
|
277
|
-
git -C "$repo_root" worktree add -b "$branch_name" "$worktree_path" "$start_ref" >/dev/null
|
|
278
|
-
git -C "$repo_root" config "branch.${branch_name}.guardexBase" "$base_branch" >/dev/null 2>&1 || true
|
|
279
|
-
if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${base_branch}"; then
|
|
280
|
-
git -C "$worktree_path" branch --set-upstream-to="origin/${base_branch}" "$branch_name" >/dev/null 2>&1 || true
|
|
281
|
-
fi
|
|
282
|
-
|
|
283
|
-
printf '[agent-branch-start] Created branch: %s\n' "$branch_name"
|
|
284
|
-
printf '[agent-branch-start] Worktree: %s\n' "$worktree_path"
|
|
285
|
-
}
|
|
286
|
-
|
|
287
99
|
if [[ ! -x "${repo_root}/scripts/agent-branch-start.sh" ]]; then
|
|
288
100
|
echo "[codex-agent] Missing scripts/agent-branch-start.sh. Run: gx setup" >&2
|
|
289
101
|
exit 1
|
|
@@ -293,489 +105,291 @@ start_args=("$TASK_NAME" "$AGENT_NAME")
|
|
|
293
105
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
|
|
294
106
|
start_args+=("$BASE_BRANCH")
|
|
295
107
|
fi
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
start_output=""
|
|
299
|
-
start_status=0
|
|
300
|
-
set +e
|
|
301
|
-
start_output="$(GUARDEX_OPENSPEC_AUTO_INIT=0 bash "${repo_root}/scripts/agent-branch-start.sh" "${start_args[@]}" 2>&1)"
|
|
302
|
-
start_status=$?
|
|
303
|
-
set -e
|
|
304
|
-
|
|
305
|
-
worktree_path="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | tail -n1)"
|
|
306
|
-
current_repo_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
307
|
-
resolved_repo_root="$(cd "$repo_root" && pwd -P)"
|
|
308
|
-
resolved_worktree_path=""
|
|
309
|
-
if [[ -n "$worktree_path" && -d "$worktree_path" ]]; then
|
|
310
|
-
resolved_worktree_path="$(cd "$worktree_path" && pwd -P)"
|
|
311
|
-
fi
|
|
312
|
-
|
|
313
|
-
fallback_reason=""
|
|
314
|
-
if [[ "$start_status" -ne 0 ]]; then
|
|
315
|
-
fallback_reason="starter exited with status ${start_status}"
|
|
316
|
-
elif [[ -z "$worktree_path" ]]; then
|
|
317
|
-
fallback_reason="starter did not report worktree path"
|
|
318
|
-
elif [[ -n "$resolved_worktree_path" && "$resolved_worktree_path" == "$resolved_repo_root" ]]; then
|
|
319
|
-
fallback_reason="starter pointed to active checkout path"
|
|
320
|
-
elif [[ -n "$initial_repo_branch" && -n "$current_repo_branch" && "$current_repo_branch" != "$initial_repo_branch" ]]; then
|
|
321
|
-
fallback_reason="starter switched active checkout branch"
|
|
108
|
+
if [[ -n "$GH_PR_REF" ]]; then
|
|
109
|
+
start_args+=(--pr "$GH_PR_REF")
|
|
322
110
|
fi
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if ! restore_repo_branch_if_changed "$initial_repo_branch"; then
|
|
326
|
-
echo "[codex-agent] agent-branch-start changed the active checkout branch and restore failed." >&2
|
|
327
|
-
echo "[codex-agent] Run 'gx setup --target ${repo_root}' and 'gx doctor --target ${repo_root}', then retry." >&2
|
|
328
|
-
exit 1
|
|
329
|
-
fi
|
|
330
|
-
if [[ -n "$start_output" ]]; then
|
|
331
|
-
printf '%s\n' "$start_output" >&2
|
|
332
|
-
fi
|
|
333
|
-
echo "[codex-agent] Unsafe starter output (${fallback_reason}); creating sandbox worktree directly." >&2
|
|
334
|
-
start_output="$(start_sandbox_fallback)"
|
|
335
|
-
printf '%s\n' "$start_output"
|
|
336
|
-
worktree_path="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | tail -n1)"
|
|
337
|
-
else
|
|
338
|
-
printf '%s\n' "$start_output"
|
|
339
|
-
fi
|
|
340
|
-
|
|
341
|
-
if [[ -z "$worktree_path" ]]; then
|
|
342
|
-
echo "[codex-agent] Could not determine sandbox worktree path from sandbox startup output." >&2
|
|
343
|
-
echo "[codex-agent] Run 'gx setup --target ${repo_root}' and 'gx doctor --target ${repo_root}', then retry." >&2
|
|
344
|
-
exit 1
|
|
111
|
+
if [[ -n "$GH_REPO_REF" ]]; then
|
|
112
|
+
start_args+=(--repo "$GH_REPO_REF")
|
|
345
113
|
fi
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
echo "[codex-agent] Reported worktree path does not exist: $worktree_path" >&2
|
|
349
|
-
exit 1
|
|
114
|
+
if [[ -n "$GH_SYNC_FLAG" ]]; then
|
|
115
|
+
start_args+=("$GH_SYNC_FLAG")
|
|
350
116
|
fi
|
|
351
117
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
resolve_worktree_base_branch() {
|
|
357
|
-
local _wt="$1"
|
|
358
|
-
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -n "$BASE_BRANCH" ]]; then
|
|
359
|
-
printf '%s' "$BASE_BRANCH"
|
|
360
|
-
return 0
|
|
361
|
-
fi
|
|
362
|
-
|
|
363
|
-
local configured_base
|
|
364
|
-
configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
|
|
365
|
-
if [[ -n "$configured_base" ]]; then
|
|
366
|
-
printf '%s' "$configured_base"
|
|
367
|
-
return 0
|
|
368
|
-
fi
|
|
369
|
-
|
|
370
|
-
printf 'dev'
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
sync_worktree_with_base() {
|
|
374
|
-
local wt="$1"
|
|
375
|
-
if ! has_origin_remote; then
|
|
376
|
-
return 0
|
|
377
|
-
fi
|
|
378
|
-
|
|
379
|
-
local base_branch
|
|
380
|
-
base_branch="$(resolve_worktree_base_branch "$wt")"
|
|
381
|
-
if [[ -z "$base_branch" ]]; then
|
|
382
|
-
return 0
|
|
383
|
-
fi
|
|
384
|
-
|
|
385
|
-
if ! git -C "$wt" fetch origin "$base_branch" --quiet; then
|
|
386
|
-
echo "[codex-agent] Warning: could not fetch origin/${base_branch} before task start." >&2
|
|
387
|
-
return 0
|
|
388
|
-
fi
|
|
389
|
-
|
|
390
|
-
if ! git -C "$wt" show-ref --verify --quiet "refs/remotes/origin/${base_branch}"; then
|
|
391
|
-
return 0
|
|
392
|
-
fi
|
|
393
|
-
|
|
394
|
-
local behind_count
|
|
395
|
-
behind_count="$(git -C "$wt" rev-list --left-right --count "HEAD...origin/${base_branch}" 2>/dev/null | awk '{print $2}')"
|
|
396
|
-
behind_count="${behind_count:-0}"
|
|
397
|
-
if [[ "$behind_count" -le 0 ]]; then
|
|
398
|
-
return 0
|
|
399
|
-
fi
|
|
118
|
+
derive_worktree_session_key() {
|
|
119
|
+
local worktree="$1"
|
|
120
|
+
local digest=""
|
|
400
121
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
echo "[codex-agent] Task sync failed. Resolve and continue in sandbox:" >&2
|
|
406
|
-
echo " git -C \"$wt\" rebase --continue" >&2
|
|
407
|
-
echo " # or abort" >&2
|
|
408
|
-
echo " git -C \"$wt\" rebase --abort" >&2
|
|
409
|
-
return 1
|
|
122
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
123
|
+
digest="$(printf '%s' "$worktree" | sha256sum | awk '{print $1}' | cut -c1-20)"
|
|
124
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
125
|
+
digest="$(printf '%s' "$worktree" | shasum -a 256 | awk '{print $1}' | cut -c1-20)"
|
|
410
126
|
fi
|
|
411
|
-
echo "[codex-agent] Task sync complete."
|
|
412
|
-
return 0
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
ensure_openspec_plan_workspace() {
|
|
416
|
-
local wt="$1"
|
|
417
|
-
local branch="$2"
|
|
418
127
|
|
|
419
|
-
if [[ "$
|
|
420
|
-
|
|
128
|
+
if [[ -z "$digest" ]]; then
|
|
129
|
+
digest="$(printf '%s' "$worktree" | tr -cs 'a-zA-Z0-9' '-' | sed -E 's/^-+//; s/-+$//' | cut -c1-40)"
|
|
421
130
|
fi
|
|
422
131
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
local openspec_script="${wt}/scripts/openspec/init-plan-workspace.sh"
|
|
426
|
-
if [[ ! -f "$openspec_script" ]]; then
|
|
427
|
-
echo "[codex-agent] Missing OpenSpec init script in sandbox: ${openspec_script}" >&2
|
|
428
|
-
echo "[codex-agent] Run 'gx setup --target ${repo_root}' and retry." >&2
|
|
429
|
-
return 1
|
|
430
|
-
fi
|
|
431
|
-
if [[ ! -x "$openspec_script" ]]; then
|
|
432
|
-
chmod +x "$openspec_script" 2>/dev/null || true
|
|
132
|
+
if [[ -z "$digest" ]]; then
|
|
133
|
+
digest="sandbox"
|
|
433
134
|
fi
|
|
434
135
|
|
|
435
|
-
|
|
436
|
-
plan_slug="$(resolve_openspec_plan_slug "$branch")"
|
|
437
|
-
local init_output=""
|
|
438
|
-
if ! init_output="$(
|
|
439
|
-
cd "$wt"
|
|
440
|
-
bash "scripts/openspec/init-plan-workspace.sh" "$plan_slug" 2>&1
|
|
441
|
-
)"; then
|
|
442
|
-
printf '%s\n' "$init_output" >&2
|
|
443
|
-
echo "[codex-agent] OpenSpec workspace initialization failed for plan '${plan_slug}'." >&2
|
|
444
|
-
return 1
|
|
445
|
-
fi
|
|
446
|
-
if [[ -n "$init_output" ]]; then
|
|
447
|
-
printf '%s\n' "$init_output"
|
|
448
|
-
fi
|
|
449
|
-
echo "[codex-agent] OpenSpec plan workspace: ${wt}/openspec/plan/${plan_slug}"
|
|
136
|
+
printf 'worktree:%s' "$digest"
|
|
450
137
|
}
|
|
451
138
|
|
|
452
|
-
|
|
453
|
-
local
|
|
454
|
-
local
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
fi
|
|
459
|
-
|
|
460
|
-
hydrate_local_helper_in_worktree "$wt" "scripts/openspec/init-change-workspace.sh"
|
|
461
|
-
|
|
462
|
-
local openspec_script="${wt}/scripts/openspec/init-change-workspace.sh"
|
|
463
|
-
if [[ ! -f "$openspec_script" ]]; then
|
|
464
|
-
echo "[codex-agent] Missing OpenSpec change init script in sandbox: ${openspec_script}" >&2
|
|
465
|
-
echo "[codex-agent] Run 'gx setup --target ${repo_root}' and retry." >&2
|
|
466
|
-
return 1
|
|
467
|
-
fi
|
|
468
|
-
if [[ ! -x "$openspec_script" ]]; then
|
|
469
|
-
chmod +x "$openspec_script" 2>/dev/null || true
|
|
470
|
-
fi
|
|
471
|
-
|
|
472
|
-
local change_slug capability_slug init_output=""
|
|
473
|
-
change_slug="$(resolve_openspec_change_slug "$branch")"
|
|
474
|
-
capability_slug="$(resolve_openspec_capability_slug)"
|
|
475
|
-
if ! init_output="$(
|
|
476
|
-
cd "$wt"
|
|
477
|
-
bash "scripts/openspec/init-change-workspace.sh" "$change_slug" "$capability_slug" 2>&1
|
|
478
|
-
)"; then
|
|
479
|
-
printf '%s\n' "$init_output" >&2
|
|
480
|
-
echo "[codex-agent] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
|
|
481
|
-
return 1
|
|
482
|
-
fi
|
|
483
|
-
if [[ -n "$init_output" ]]; then
|
|
484
|
-
printf '%s\n' "$init_output"
|
|
485
|
-
fi
|
|
486
|
-
echo "[codex-agent] OpenSpec change workspace: ${wt}/openspec/changes/${change_slug}"
|
|
487
|
-
}
|
|
139
|
+
export_worktree_mem0_env() {
|
|
140
|
+
local worktree="$1"
|
|
141
|
+
local mem0_dir="${worktree}/.omx/mem0"
|
|
142
|
+
local notepad_path="${mem0_dir}/notepad.md"
|
|
143
|
+
local project_memory_path="${mem0_dir}/project-memory.json"
|
|
144
|
+
local scope_path="${mem0_dir}/worktree-scope.json"
|
|
488
145
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if
|
|
492
|
-
|
|
146
|
+
export OMX_MEM0_SCOPE="worktree"
|
|
147
|
+
export OMX_MEM0_DIR="$mem0_dir"
|
|
148
|
+
if [[ -f "$notepad_path" ]]; then
|
|
149
|
+
export OMX_NOTEPAD_PATH="$notepad_path"
|
|
493
150
|
fi
|
|
494
|
-
if
|
|
495
|
-
|
|
151
|
+
if [[ -f "$project_memory_path" ]]; then
|
|
152
|
+
export OMX_PROJECT_MEMORY_PATH="$project_memory_path"
|
|
496
153
|
fi
|
|
497
|
-
if [[ -
|
|
498
|
-
|
|
154
|
+
if [[ -f "$scope_path" ]]; then
|
|
155
|
+
export OMX_MEM0_SCOPE_PATH="$scope_path"
|
|
499
156
|
fi
|
|
500
|
-
return 1
|
|
501
|
-
}
|
|
502
157
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
local lock_script="${repo_root}/scripts/agent-file-locks.py"
|
|
507
|
-
|
|
508
|
-
if [[ ! -x "$lock_script" ]]; then
|
|
509
|
-
return 0
|
|
158
|
+
if [[ -z "${CODEX_AUTH_SESSION_KEY:-}" ]]; then
|
|
159
|
+
export CODEX_AUTH_SESSION_KEY="$(derive_worktree_session_key "$worktree")"
|
|
160
|
+
echo "[codex-agent] Scoped CODEX_AUTH_SESSION_KEY to ${CODEX_AUTH_SESSION_KEY}"
|
|
510
161
|
fi
|
|
511
162
|
|
|
512
|
-
|
|
513
|
-
changed_raw="$({
|
|
514
|
-
git -C "$wt" diff --name-only -- . ":(exclude).omx/state/agent-file-locks.json";
|
|
515
|
-
git -C "$wt" diff --cached --name-only -- . ":(exclude).omx/state/agent-file-locks.json";
|
|
516
|
-
git -C "$wt" ls-files --others --exclude-standard;
|
|
517
|
-
} | sed '/^$/d' | sort -u)"
|
|
518
|
-
|
|
519
|
-
if [[ -n "$changed_raw" ]]; then
|
|
520
|
-
mapfile -t changed_files < <(printf '%s\n' "$changed_raw")
|
|
521
|
-
python3 "$lock_script" claim --branch "$branch" "${changed_files[@]}" >/dev/null 2>&1 || true
|
|
522
|
-
fi
|
|
523
|
-
|
|
524
|
-
deleted_raw="$({
|
|
525
|
-
git -C "$wt" diff --name-only --diff-filter=D -- . ":(exclude).omx/state/agent-file-locks.json";
|
|
526
|
-
git -C "$wt" diff --cached --name-only --diff-filter=D -- . ":(exclude).omx/state/agent-file-locks.json";
|
|
527
|
-
} | sed '/^$/d' | sort -u)"
|
|
528
|
-
|
|
529
|
-
if [[ -n "$deleted_raw" ]]; then
|
|
530
|
-
mapfile -t deleted_files < <(printf '%s\n' "$deleted_raw")
|
|
531
|
-
python3 "$lock_script" allow-delete --branch "$branch" "${deleted_files[@]}" >/dev/null 2>&1 || true
|
|
532
|
-
fi
|
|
163
|
+
echo "[codex-agent] Worktree mem0 scope: $mem0_dir"
|
|
533
164
|
}
|
|
534
165
|
|
|
535
|
-
|
|
536
|
-
local
|
|
537
|
-
local
|
|
538
|
-
|
|
539
|
-
if ! worktree_has_changes "$wt"; then
|
|
540
|
-
return 0
|
|
541
|
-
fi
|
|
542
|
-
|
|
543
|
-
claim_changed_files "$wt" "$branch"
|
|
544
|
-
git -C "$wt" add -A
|
|
545
|
-
|
|
546
|
-
if git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json"; then
|
|
547
|
-
return 0
|
|
548
|
-
fi
|
|
549
|
-
|
|
550
|
-
local default_message="Auto-finish: ${TASK_NAME}"
|
|
551
|
-
local commit_message="${GUARDEX_CODEX_AUTO_COMMIT_MESSAGE:-$default_message}"
|
|
552
|
-
local commit_output=""
|
|
553
|
-
|
|
554
|
-
if commit_output="$(git -C "$wt" commit -m "$commit_message" 2>&1)"; then
|
|
555
|
-
echo "[codex-agent] Auto-committed sandbox changes on '${branch}'."
|
|
556
|
-
return 0
|
|
557
|
-
fi
|
|
166
|
+
resolve_finish_base_branch() {
|
|
167
|
+
local branch="$1"
|
|
168
|
+
local stored_base=""
|
|
558
169
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
echo "[codex-agent] Auto-committed sandbox changes on '${branch}' after sync retry."
|
|
564
|
-
return 0
|
|
565
|
-
fi
|
|
170
|
+
stored_base="$(git -C "$repo_root" config --get "branch.${branch}.guardexBase" || true)"
|
|
171
|
+
if [[ -n "$stored_base" ]]; then
|
|
172
|
+
printf '%s' "$stored_base"
|
|
173
|
+
return
|
|
566
174
|
fi
|
|
567
175
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
printf '%s\n' "$commit_output" >&2
|
|
176
|
+
if [[ -n "$BASE_BRANCH" ]]; then
|
|
177
|
+
printf '%s' "$BASE_BRANCH"
|
|
571
178
|
fi
|
|
572
|
-
return 1
|
|
573
179
|
}
|
|
574
180
|
|
|
575
|
-
|
|
576
|
-
local
|
|
577
|
-
local
|
|
578
|
-
|
|
579
|
-
if ! has_origin_remote; then
|
|
580
|
-
return 1
|
|
581
|
-
fi
|
|
582
|
-
|
|
583
|
-
local base_branch
|
|
584
|
-
base_branch="$(resolve_worktree_base_branch "$wt")"
|
|
585
|
-
if [[ -z "$base_branch" ]]; then
|
|
586
|
-
return 1
|
|
587
|
-
fi
|
|
588
|
-
|
|
589
|
-
if ! git -C "$wt" fetch origin "$base_branch" --quiet; then
|
|
590
|
-
return 1
|
|
591
|
-
fi
|
|
592
|
-
|
|
593
|
-
if ! git -C "$wt" show-ref --verify --quiet "refs/remotes/origin/${base_branch}"; then
|
|
594
|
-
return 1
|
|
595
|
-
fi
|
|
596
|
-
|
|
597
|
-
local behind_count
|
|
598
|
-
behind_count="$(git -C "$wt" rev-list --left-right --count "HEAD...origin/${base_branch}" 2>/dev/null | awk '{print $2}')"
|
|
599
|
-
behind_count="${behind_count:-0}"
|
|
600
|
-
if [[ "$behind_count" -le 0 ]]; then
|
|
601
|
-
return 1
|
|
602
|
-
fi
|
|
603
|
-
|
|
604
|
-
echo "[codex-agent] Auto-commit retry: '${branch}' is behind origin/${base_branch} by ${behind_count} commit(s). Syncing and retrying..."
|
|
181
|
+
render_finish_hint() {
|
|
182
|
+
local branch="$1"
|
|
183
|
+
local base="${2:-}"
|
|
184
|
+
local hint=""
|
|
605
185
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if ! stash_output="$(git -C "$wt" stash push --include-untracked -m "codex-agent-autocommit-sync-${branch}-$(date +%s)" 2>&1)"; then
|
|
610
|
-
return 1
|
|
611
|
-
fi
|
|
612
|
-
stash_ref="$(printf '%s\n' "$stash_output" | grep -o 'stash@{[0-9]\+}' | head -n 1 || true)"
|
|
186
|
+
hint="bash scripts/agent-branch-finish.sh --branch \"${branch}\""
|
|
187
|
+
if [[ -n "$base" ]]; then
|
|
188
|
+
hint="${hint} --base \"${base}\""
|
|
613
189
|
fi
|
|
614
|
-
|
|
615
|
-
if
|
|
616
|
-
|
|
617
|
-
if [[ -n "$stash_ref" ]]; then
|
|
618
|
-
git -C "$wt" stash pop "$stash_ref" >/dev/null 2>&1 || true
|
|
619
|
-
fi
|
|
620
|
-
return 1
|
|
190
|
+
hint="${hint} --via-pr --wait-for-merge --cleanup"
|
|
191
|
+
if [[ -n "$GH_PR_REF" ]]; then
|
|
192
|
+
hint="${hint} --pr \"${GH_PR_REF}\""
|
|
621
193
|
fi
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
if ! git -C "$wt" stash pop "$stash_ref" >/dev/null 2>&1; then
|
|
625
|
-
echo "[codex-agent] Auto-commit retry could not re-apply local changes after sync. Manual resolution required in: $wt" >&2
|
|
626
|
-
return 1
|
|
627
|
-
fi
|
|
194
|
+
if [[ -n "$GH_REPO_REF" ]]; then
|
|
195
|
+
hint="${hint} --repo \"${GH_REPO_REF}\""
|
|
628
196
|
fi
|
|
629
197
|
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
looks_like_conflict_failure() {
|
|
634
|
-
local output="$1"
|
|
635
|
-
if grep -qiE 'preflight conflict detected|merge conflict detected|auto-sync failed while rebasing|rebase --continue|rebase --abort' <<< "$output"; then
|
|
636
|
-
return 0
|
|
637
|
-
fi
|
|
638
|
-
return 1
|
|
198
|
+
printf '%s' "$hint"
|
|
639
199
|
}
|
|
640
200
|
|
|
641
|
-
|
|
642
|
-
local
|
|
643
|
-
local
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
if
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
local review_prompt
|
|
685
|
-
review_prompt="Resolve git conflicts for branch ${branch} against ${finish_base_branch:-dev}, then commit the resolution in this sandbox worktree and exit."
|
|
686
|
-
|
|
687
|
-
(
|
|
688
|
-
cd "$wt"
|
|
689
|
-
set +e
|
|
690
|
-
"$CODEX_BIN" "$review_prompt"
|
|
691
|
-
review_exit="$?"
|
|
692
|
-
set -e
|
|
693
|
-
if [[ "$review_exit" -ne 0 ]]; then
|
|
694
|
-
echo "[codex-agent] Conflict-review Codex pass exited with status ${review_exit}." >&2
|
|
695
|
-
fi
|
|
201
|
+
resolve_role_model() {
|
|
202
|
+
local role="$1"
|
|
203
|
+
local config_path="$2"
|
|
204
|
+
python3 - "$role" "$config_path" <<'PY'
|
|
205
|
+
import json
|
|
206
|
+
import os
|
|
207
|
+
import re
|
|
208
|
+
import sys
|
|
209
|
+
|
|
210
|
+
role = sys.argv[1]
|
|
211
|
+
config_path = sys.argv[2]
|
|
212
|
+
if not os.path.exists(config_path):
|
|
213
|
+
print(f"codex-agent: config file not found: {config_path}", file=sys.stderr)
|
|
214
|
+
sys.exit(2)
|
|
215
|
+
try:
|
|
216
|
+
with open(config_path, "r", encoding="utf-8") as fh:
|
|
217
|
+
cfg = json.load(fh)
|
|
218
|
+
except json.JSONDecodeError as exc:
|
|
219
|
+
print(f"codex-agent: config file is not valid JSON ({config_path}): {exc}", file=sys.stderr)
|
|
220
|
+
sys.exit(2)
|
|
221
|
+
if not isinstance(cfg, dict):
|
|
222
|
+
print(f"codex-agent: config file root is not a JSON object: {config_path}", file=sys.stderr)
|
|
223
|
+
sys.exit(2)
|
|
224
|
+
for key in ("version", "tiers", "roles"):
|
|
225
|
+
if key not in cfg:
|
|
226
|
+
print(
|
|
227
|
+
f"codex-agent: config file missing required top-level key '{key}': {config_path}",
|
|
228
|
+
file=sys.stderr,
|
|
229
|
+
)
|
|
230
|
+
sys.exit(2)
|
|
231
|
+
tiers = cfg.get("tiers") or {}
|
|
232
|
+
roles = cfg.get("roles") or {}
|
|
233
|
+
if not isinstance(tiers, dict) or not tiers:
|
|
234
|
+
print(f"codex-agent: config 'tiers' must be a non-empty object: {config_path}", file=sys.stderr)
|
|
235
|
+
sys.exit(2)
|
|
236
|
+
if not isinstance(roles, dict) or not roles:
|
|
237
|
+
print(f"codex-agent: config 'roles' must be a non-empty object: {config_path}", file=sys.stderr)
|
|
238
|
+
sys.exit(2)
|
|
239
|
+
if role not in roles:
|
|
240
|
+
valid = ", ".join(sorted(roles.keys()))
|
|
241
|
+
print(
|
|
242
|
+
f"codex-agent: role '{role}' is not defined in {config_path}. Valid roles: {valid}",
|
|
243
|
+
file=sys.stderr,
|
|
696
244
|
)
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
245
|
+
sys.exit(2)
|
|
246
|
+
role_entry = roles[role]
|
|
247
|
+
if not isinstance(role_entry, dict) or "tier" not in role_entry:
|
|
248
|
+
print(f"codex-agent: role '{role}' entry is missing 'tier' in {config_path}", file=sys.stderr)
|
|
249
|
+
sys.exit(2)
|
|
250
|
+
tier_name = role_entry["tier"]
|
|
251
|
+
if tier_name not in tiers:
|
|
252
|
+
print(
|
|
253
|
+
f"codex-agent: role '{role}' points to tier '{tier_name}' which is not defined in {config_path}",
|
|
254
|
+
file=sys.stderr,
|
|
255
|
+
)
|
|
256
|
+
sys.exit(2)
|
|
257
|
+
tier_entry = tiers[tier_name]
|
|
258
|
+
if not isinstance(tier_entry, dict) or not tier_entry.get("model"):
|
|
259
|
+
print(
|
|
260
|
+
f"codex-agent: tier '{tier_name}' is missing a non-empty 'model' field in {config_path}",
|
|
261
|
+
file=sys.stderr,
|
|
262
|
+
)
|
|
263
|
+
sys.exit(2)
|
|
264
|
+
model = tier_entry["model"]
|
|
265
|
+
if re.fullmatch(r"REPLACE_WITH_[A-Z_]+_MODEL_ID", model):
|
|
266
|
+
print(
|
|
267
|
+
f"codex-agent: tier '{tier_name}' still has a placeholder model ID. "
|
|
268
|
+
f"Edit {config_path} and replace {model}.",
|
|
269
|
+
file=sys.stderr,
|
|
270
|
+
)
|
|
271
|
+
sys.exit(2)
|
|
272
|
+
print(f"{tier_name}\t{model}")
|
|
273
|
+
PY
|
|
707
274
|
}
|
|
708
275
|
|
|
709
|
-
|
|
710
|
-
|
|
276
|
+
resolved_model=""
|
|
277
|
+
resolved_tier=""
|
|
278
|
+
resolved_source=""
|
|
279
|
+
role_config_path="${OMX_ROLE_MODEL_CONFIG:-${repo_root}/.agents/role-model-tiers.json}"
|
|
280
|
+
|
|
281
|
+
if [[ -n "${OMX_ROLE_MODEL_OVERRIDE:-}" ]]; then
|
|
282
|
+
resolved_model="$OMX_ROLE_MODEL_OVERRIDE"
|
|
283
|
+
resolved_tier="${ROLE:-n/a}"
|
|
284
|
+
resolved_source="env-override"
|
|
285
|
+
elif [[ -n "$ROLE" ]]; then
|
|
286
|
+
if ! lookup_output="$(resolve_role_model "$ROLE" "$role_config_path")"; then
|
|
287
|
+
exit 2
|
|
288
|
+
fi
|
|
289
|
+
resolved_tier="${lookup_output%%$'\t'*}"
|
|
290
|
+
resolved_model="${lookup_output##*$'\t'}"
|
|
291
|
+
resolved_source="config"
|
|
711
292
|
fi
|
|
712
293
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
exit 1
|
|
294
|
+
if [[ -n "$resolved_model" ]]; then
|
|
295
|
+
echo "[codex-agent] role=${ROLE:-n/a} tier=${resolved_tier} model=${resolved_model} source=${resolved_source}" >&2
|
|
296
|
+
CODEX_EXTRA_ARGS+=("-m" "$resolved_model")
|
|
717
297
|
fi
|
|
718
298
|
|
|
719
|
-
|
|
299
|
+
start_output="$(bash "${repo_root}/scripts/agent-branch-start.sh" "${start_args[@]}")"
|
|
300
|
+
printf '%s\n' "$start_output"
|
|
301
|
+
|
|
302
|
+
worktree_path="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | tail -n1)"
|
|
303
|
+
if [[ -z "$worktree_path" ]]; then
|
|
304
|
+
echo "[codex-agent] Could not determine sandbox worktree path from agent-branch-start output." >&2
|
|
720
305
|
exit 1
|
|
721
306
|
fi
|
|
722
307
|
|
|
723
|
-
if !
|
|
308
|
+
if [[ ! -d "$worktree_path" ]]; then
|
|
309
|
+
echo "[codex-agent] Reported worktree path does not exist: $worktree_path" >&2
|
|
724
310
|
exit 1
|
|
725
311
|
fi
|
|
726
312
|
|
|
313
|
+
export_worktree_mem0_env "$worktree_path"
|
|
314
|
+
|
|
727
315
|
echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
|
|
728
316
|
cd "$worktree_path"
|
|
729
317
|
set +e
|
|
730
|
-
"$CODEX_BIN" "$@"
|
|
318
|
+
"$CODEX_BIN" "${CODEX_EXTRA_ARGS[@]}" "$@"
|
|
731
319
|
codex_exit="$?"
|
|
732
320
|
set -e
|
|
733
321
|
|
|
734
322
|
cd "$repo_root"
|
|
323
|
+
|
|
735
324
|
final_exit="$codex_exit"
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
325
|
+
worktree_branch="$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
326
|
+
finish_base_branch=""
|
|
327
|
+
finish_hint=""
|
|
328
|
+
if [[ -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
|
|
329
|
+
finish_base_branch="$(resolve_finish_base_branch "$worktree_branch")"
|
|
330
|
+
finish_hint="$(render_finish_hint "$worktree_branch" "$finish_base_branch")"
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
if [[ "$codex_exit" -eq 0 ]]; then
|
|
334
|
+
if [[ -x "${repo_root}/scripts/agent-branch-finish.sh" ]]; then
|
|
335
|
+
if [[ -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
|
|
336
|
+
finish_args=(--branch "$worktree_branch")
|
|
337
|
+
if [[ -n "$finish_base_branch" ]]; then
|
|
338
|
+
finish_args+=(--base "$finish_base_branch")
|
|
339
|
+
fi
|
|
340
|
+
finish_args+=(--via-pr --wait-for-merge --cleanup)
|
|
341
|
+
if [[ -n "$GH_PR_REF" ]]; then
|
|
342
|
+
finish_args+=(--pr "$GH_PR_REF")
|
|
343
|
+
fi
|
|
344
|
+
if [[ -n "$GH_REPO_REF" ]]; then
|
|
345
|
+
finish_args+=(--repo "$GH_REPO_REF")
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
echo "[codex-agent] Codex finished successfully. Auto-finishing branch via PR merge + cleanup..."
|
|
349
|
+
if ! bash "${repo_root}/scripts/agent-branch-finish.sh" "${finish_args[@]}"; then
|
|
350
|
+
echo "[codex-agent] Auto-finish failed. Sandbox is kept for manual resolve/retry." >&2
|
|
351
|
+
if [[ -n "$finish_hint" ]]; then
|
|
352
|
+
echo "[codex-agent] Retry with: ${finish_hint}" >&2
|
|
760
353
|
fi
|
|
354
|
+
final_exit=1
|
|
761
355
|
fi
|
|
356
|
+
else
|
|
357
|
+
echo "[codex-agent] Could not determine sandbox branch name; skipping auto-finish." >&2
|
|
358
|
+
final_exit=1
|
|
762
359
|
fi
|
|
763
360
|
else
|
|
764
|
-
|
|
765
|
-
|
|
361
|
+
echo "[codex-agent] Missing scripts/agent-branch-finish.sh; skipping auto-finish." >&2
|
|
362
|
+
final_exit=1
|
|
363
|
+
fi
|
|
364
|
+
else
|
|
365
|
+
if [[ -x "${repo_root}/scripts/agent-branch-finish.sh" && -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
|
|
366
|
+
echo "[codex-agent] Codex exited with status ${codex_exit}. Attempting best-effort push + PR (no merge wait)..." >&2
|
|
367
|
+
fallback_args=(--branch "$worktree_branch" --via-pr --skip-tasks-gate)
|
|
368
|
+
if [[ -n "$finish_base_branch" ]]; then
|
|
369
|
+
fallback_args+=(--base "$finish_base_branch")
|
|
370
|
+
fi
|
|
371
|
+
if [[ -n "$GH_PR_REF" ]]; then
|
|
372
|
+
fallback_args+=(--pr "$GH_PR_REF")
|
|
766
373
|
fi
|
|
374
|
+
if [[ -n "$GH_REPO_REF" ]]; then
|
|
375
|
+
fallback_args+=(--repo "$GH_REPO_REF")
|
|
376
|
+
fi
|
|
377
|
+
if bash "${repo_root}/scripts/agent-branch-finish.sh" "${fallback_args[@]}"; then
|
|
378
|
+
echo "[codex-agent] Fallback push succeeded: branch pushed + PR created/updated for manual review." >&2
|
|
379
|
+
else
|
|
380
|
+
echo "[codex-agent] Fallback push failed; sandbox retained. Retry with: ${finish_hint}" >&2
|
|
381
|
+
fi
|
|
382
|
+
else
|
|
383
|
+
echo "[codex-agent] Skipping auto-finish because Codex exited with status ${codex_exit} and no sandbox branch was detected."
|
|
767
384
|
fi
|
|
768
385
|
fi
|
|
769
386
|
|
|
770
387
|
if [[ -x "${repo_root}/scripts/agent-worktree-prune.sh" ]]; then
|
|
771
|
-
echo "[codex-agent] Session ended (exit=${
|
|
388
|
+
echo "[codex-agent] Session ended (exit=${final_exit}). Running worktree cleanup..."
|
|
772
389
|
prune_args=()
|
|
773
390
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
|
|
774
391
|
prune_args+=(--base "$BASE_BRANCH")
|
|
775
392
|
fi
|
|
776
|
-
if [[ "$AUTO_CLEANUP" -eq 1 && "$auto_finish_completed" -eq 1 ]]; then
|
|
777
|
-
prune_args+=(--only-dirty-worktrees --delete-branches --delete-remote-branches)
|
|
778
|
-
fi
|
|
779
393
|
if ! bash "${repo_root}/scripts/agent-worktree-prune.sh" "${prune_args[@]}"; then
|
|
780
394
|
echo "[codex-agent] Warning: automatic worktree cleanup failed." >&2
|
|
781
395
|
fi
|
|
@@ -784,15 +398,13 @@ fi
|
|
|
784
398
|
if [[ ! -d "$worktree_path" ]]; then
|
|
785
399
|
echo "[codex-agent] Auto-cleaned sandbox worktree: $worktree_path"
|
|
786
400
|
else
|
|
787
|
-
worktree_branch="$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
788
401
|
echo "[codex-agent] Sandbox worktree kept: $worktree_path"
|
|
789
402
|
if [[ -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
|
|
790
|
-
if [[ "$
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
echo "[codex-agent] If finished, merge with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge"
|
|
794
|
-
echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
|
|
403
|
+
if [[ -z "$finish_hint" ]]; then
|
|
404
|
+
finish_base_branch="$(resolve_finish_base_branch "$worktree_branch")"
|
|
405
|
+
finish_hint="$(render_finish_hint "$worktree_branch" "$finish_base_branch")"
|
|
795
406
|
fi
|
|
407
|
+
echo "[codex-agent] If finished, merge + clean with: ${finish_hint}"
|
|
796
408
|
fi
|
|
797
409
|
fi
|
|
798
410
|
|