|
|
@@ -40,13 +40,14 @@ EOF
|
|
|
|
|
|
err() { echo "ff-collect: $*" >&2; }
|
|
|
|
|
|
-RUN="" ID="" REPO="" SCHEMA=0 CHECK_CLEAN=0
|
|
|
+RUN="" ID="" REPO="" SCHEMA=0 CHECK_CLEAN=0 REPAIR=0 JQERR=""
|
|
|
while [ $# -gt 0 ]; do
|
|
|
case "$1" in
|
|
|
--run) RUN="${2:-}"; shift 2 ;;
|
|
|
--id) ID="${2:-}"; shift 2 ;;
|
|
|
--repo) REPO="${2:-}"; shift 2 ;;
|
|
|
--schema) SCHEMA=1; shift ;;
|
|
|
+ --repair) REPAIR=1; shift ;;
|
|
|
--check-main-clean) CHECK_CLEAN=1; shift ;;
|
|
|
-h|--help) usage; exit 0 ;;
|
|
|
*) err "unknown argument: $1"; usage >&2; exit 2 ;;
|
|
|
@@ -57,6 +58,45 @@ command -v jq >/dev/null || { err "jq required"; exit 2; }
|
|
|
[ -n "$REPO" ] || REPO="$(git rev-parse --show-toplevel 2>/dev/null)" || true
|
|
|
[ -n "$REPO" ] && [ -d "$REPO" ] || { err "not in a git repo (or --repo invalid)"; exit 2; }
|
|
|
|
|
|
+HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
+SPAWN="$HERE/ff-spawn.sh"
|
|
|
+COLLECT="$HERE/ff-collect.sh"
|
|
|
+
|
|
|
+# strip markdown code fences (```json ... ```) so schema gating tolerates fenced replies
|
|
|
+strip_fences() { sed -E '/^[[:space:]]*```[[:alnum:]]*[[:space:]]*$/d'; }
|
|
|
+
|
|
|
+# stdin = text; returns 0 if valid JSON else 1; sets global JQERR to the parse error
|
|
|
+json_ok() {
|
|
|
+ local t; t="$(cat)"
|
|
|
+ JQERR="$(printf '%s' "$t" | jq empty 2>&1 1>/dev/null | head -1)"
|
|
|
+ printf '%s' "$t" | jq empty >/dev/null 2>&1
|
|
|
+}
|
|
|
+
|
|
|
+# do_repair <bad_text> <jq_error>: save bad output, respawn a <id>-repair lane
|
|
|
+# (same brain, --max-turns 3, no worktree), gate it, print its text on success.
|
|
|
+# One attempt only. Exits 0 (repaired) or 10 (repair also failed). FLEETFLOW_REPAIR_DRYRUN
|
|
|
+# forces the respawn to --dry-run (test/offline seam).
|
|
|
+do_repair() {
|
|
|
+ local bad="$1" jqerr="$2" rid="${ID}-repair" pf spawn_rc=0 rout rc2
|
|
|
+ pf="$RUNDIR/$rid.prompt-src.txt"
|
|
|
+ printf '%s' "$bad" > "$RUNDIR/$ID.invalid.txt"
|
|
|
+ err "schema validation failed (--repair): saved $RUNDIR/$ID.invalid.txt; respawning lane $rid"
|
|
|
+ printf 'Your previous FINAL REPLY failed JSON validation. Error: %s. Previous output: %s. Reply with ONLY the corrected JSON object, nothing else.' \
|
|
|
+ "$jqerr" "$bad" > "$pf"
|
|
|
+ if [ -n "${FLEETFLOW_REPAIR_DRYRUN:-}" ]; then
|
|
|
+ bash "$SPAWN" --run "$RUN" --id "$rid" --brain "$BRAIN" --max-turns 3 \
|
|
|
+ --repo "$REPO" --prompt-file "$pf" --dry-run >/dev/null 2>"$RUNDIR/$rid.spawn.err" || spawn_rc=$?
|
|
|
+ else
|
|
|
+ bash "$SPAWN" --run "$RUN" --id "$rid" --brain "$BRAIN" --max-turns 3 \
|
|
|
+ --repo "$REPO" --prompt-file "$pf" >/dev/null 2>"$RUNDIR/$rid.spawn.err" || spawn_rc=$?
|
|
|
+ fi
|
|
|
+ case "$spawn_rc" in 0|3) ;; *) err "repair spawn failed (rc=$spawn_rc; see $RUNDIR/$rid.spawn.err)"; exit 10 ;; esac
|
|
|
+ # gate the repair lane WITHOUT --repair (one attempt); its stdout is the repaired text
|
|
|
+ rout="$(bash "$COLLECT" --run "$RUN" --id "$rid" --repo "$REPO" --schema 2>"$RUNDIR/$rid.collect.err")"; rc2=$?
|
|
|
+ if [ "$rc2" = 0 ]; then printf '%s\n' "$rout"; exit 0; fi
|
|
|
+ err "repair lane also failed validation (see $RUNDIR/$rid.collect.err)"; exit 10
|
|
|
+}
|
|
|
+
|
|
|
# --- escape guard -------------------------------------------------------------
|
|
|
if [ "$CHECK_CLEAN" = 1 ]; then
|
|
|
BASELINE=""
|
|
|
@@ -92,7 +132,13 @@ if [ "$BRAIN" = "codex" ] || { [ -z "$BRAIN" ] && [ -f "$RUNDIR/$ID.last.txt" ];
|
|
|
ART="$RUNDIR/$ID.last.txt"
|
|
|
[ -s "$ART" ] || { err "codex last-message missing/empty: $ART"; exit 3; }
|
|
|
if [ "$SCHEMA" = 1 ]; then
|
|
|
- jq empty < "$ART" 2>/dev/null || { err "final message is not valid JSON (schema lane)"; exit 10; }
|
|
|
+ CLEAN="$(strip_fences < "$ART")"
|
|
|
+ if ! printf '%s' "$CLEAN" | json_ok; then
|
|
|
+ [ "$REPAIR" = 1 ] && do_repair "$CLEAN" "$JQERR"
|
|
|
+ err "final message is not valid JSON (schema lane): $JQERR"; exit 10
|
|
|
+ fi
|
|
|
+ printf '%s\n' "$CLEAN"
|
|
|
+ exit 0
|
|
|
fi
|
|
|
cat "$ART"
|
|
|
exit 0
|
|
|
@@ -110,7 +156,13 @@ if [ "$IS_ERR" != "false" ]; then
|
|
|
fi
|
|
|
TEXT="$(jq -r '.result // empty' "$ART")"
|
|
|
if [ "$SCHEMA" = 1 ]; then
|
|
|
- printf '%s' "$TEXT" | jq empty 2>/dev/null || { err "final text is not valid JSON (schema lane)"; exit 10; }
|
|
|
+ CLEAN="$(printf '%s' "$TEXT" | strip_fences)"
|
|
|
+ if ! printf '%s' "$CLEAN" | json_ok; then
|
|
|
+ [ "$REPAIR" = 1 ] && do_repair "$CLEAN" "$JQERR"
|
|
|
+ err "final text is not valid JSON (schema lane): $JQERR"; exit 10
|
|
|
+ fi
|
|
|
+ printf '%s\n' "$CLEAN"
|
|
|
+ exit 0
|
|
|
fi
|
|
|
printf '%s\n' "$TEXT"
|
|
|
exit 0
|