Skip to content

v1.0.620

Choose a tag to compare

@fa-release-bot fa-release-bot released this 21 Oct 20:57
· 170 commits to main since this release
5074157
  • fix(gh-1743): a structured compaction that hits its total-budget
    watchdog (the factory's 300s totalBudget) no longer aborts the
    live session. AutoCompactorFactory.run() now applies the
    mechanical local-trim fallback (keepRecentTokens, pairing-repaired,
    [context trimmed locally] marker — the same valve the classic
    pass ladder ends on) instead of failing fast; the honest
    both-roles-failed verdict is kept only when nothing is droppable.

  • fix(gh-1636): auto-compaction stops dumping the internal checkpoint
    summary into the console — the default report is ONE marker line
    (● auto-compacted · engine: N → M tokens (F freed · P%)); the records
    line + fenced summary moved behind the new tui.showCompactionDetails
    opt-in (default false), and the manual /compact still prints the full
    block. The headless log face no longer streams the summarizer deltas by
    default (the gh-1433 E4 live stream is part of the same opt-in; the
    gh-1430 … reasoning liveness line still covers the window). Nothing
    is lost — the summary stays in the session file, reachable via
    compact_expand.

  • fix(test): gh-1732 — the wrap_ansi_line_perf_test sustained-contention
    validation leg spikes 3 of the 5 doubled runs per attempt instead of 4
    (CI run 39546301132, core shard 2/4: the old shape left exactly ONE
    real-wall-clock-timed doubled sample per attempt, and real jitter on
    that single sample — the very shape being modeled — inflated all three
    attempts' min ratios to a 3.30x best against the 3.2 bound while the
    real 500k growth legs on the same runner stayed green). 3-of-5 is the
    documented live-red shape ("≥3 of the 5 timed runs"): median-of-5
    [c,c,s] is still a spiked value, so the leg still proves the
    pre-gh-1532 median harness fails, and the min harness now rides two
    clean doubled samples per attempt.

  • fix(#1705): CLI fresh-install boarding asks the provider choice FIRST —
    the same list the /add-provider picker shows (presets, Custom last) —
    and only Custom continues into the api-type wizard; a preset opens its
    own flow with the api type prefilled. Previously boarding opened the
    custom wizard directly, cold-asking "api type" before any provider was
    chosen.

  • feat(providers): gh-1704 — first-class Codex/ChatGPT credential export,
    CI refresh action, and Provider Queue integration (Copilot-export
    parity). fa provider export <name> [--check] resolves the saved
    ChatGPT login's OAuth blob from its secure-store slot, refreshes it when
    stale, and prints the shell lines (OPENAI_API_KEY + OPENAI_BASE_URL
    = the Codex backend — these tokens never serve api.openai.com); the
    refresh token never prints and --check reports credential health with
    the SSO caveat. /provider chatgpt import [path] adopts an existing
    codex login session (~/.codex/auth.json) into the same slot — no
    browser round-trip. .github/actions/chatgpt-codex-auth mints a fresh
    access token from a CHATGPT_REFRESH_TOKEN secret in CI (direct curl,
    no third-party action); invalid_grant fails fast and loudly.
    providersQueue: entries speak chatgpt-codex: an omitted apiKeyEnv
    resolves the Keychain slot automatically (in-memory refresh), a new
    refreshTokenEnv: field mints the initial access token on slot
    activation, and a raw Bearer token in apiKeyEnv is tolerated by
    chatgpt_codex.dart (no more FormatException — proactive/reactive
    refresh degrade, a 401 surfaces as the auth death the queue fails over
    on).

  • feat(bench): gh-1681 — leaderboard submission readiness. ATIF
    trajectories: new exportAtifJson whole-snapshot exporter
    (lib/src/trajectory/atif_export.dart, ATIF-v1.7 — the schema harbor
    0.23.0's flagship adapters emit) behind a headless fa trajectory export [--format atif] [--output path] verb; the harbor adapter runs
    it as a bounded best-effort exec after the agent phase on every
    terminal path, so <trial>/agent/trajectory.json reaches the hub with
    the other agent logs (the leaderboard static analysis requires one per
    rewarded trial) and result.json metadata carries an
    atif_trajectory present/missing marker. The golden fixture
    (bench/harbor_fa/fixtures/atif_sample.json) is pinned Dart-side and
    cross-validated against harbor's pydantic Trajectory +
    validate_trajectory in test_atif_contract.py. Workflow: a
    submission: true dispatch input turns the submission rules into hard
    setup preflights (full task coverage, ≥5 attempts/task, no
    agent-timeout override, pinned dataset) and makes the new per-shard
    ATIF presence gate fail hard; default runs gain only a warn-only scan
    (artifact flow unchanged). Runbook: docs/leaderboard-submission.md
    (pinned dataset + @latest content-hash recording, quota-window runner
    policy, lb CLI steps).

  • fix(headless): gh-1589 — the headless session becomes a SEALED artifact:
    no session-JSONL writes land after the run's agent phase ends. New
    Session.seal() (state in the writer, no file-format change): raised in
    runHeadless' finally and the REPL teardown as the LAST session action
    (close order: drain → usage fold → seal → exit, one implementation both
    paths — REG-2), it makes every writer (_persistMessages,
    shell_job_registry snapshots, compaction, any appendCustomEntry,
    leaf moves) drop late appends with exactly ONE stderr warning per
    writer class — never a throw into an unhandled zone; the exit code
    stays the turn's terminal outcome (issue #413 semantics untouched). The
    settle→reaction path is sealed with the file (I2): the drain wrap-up
    raises a fence (_reactionRunsSealed) so a background-shell settle or
    task completion arriving after the final answer's drain — including the
    registry's one-turn-deferred listener racing _finishHeadlessDrain's
    check-to-return window (the 65gGW7b 10:13:42–10:14:00 burst) — does
    board bookkeeping only: no _startRun, no model request, no record.
    An honest drain's records (final answer → seal) stay legal (E7); the
    seal is per-run state, so resume/replay/session_repair are unchanged.
    Adapter side (bench/harbor_fa, I3/AC4+AC7): run()/perform_task
    returns only after the fa process exited on EVERY path — the ladder
    kill became terminate-and-reap (SIGTERM → bounded exec await → SIGKILL
    escalation → pgrep confirm), and the external-cancel paths (harbor's
    own wait_for firing first, the no-knobs exec, external-cap-only mode)
    now fire the kill + bounded reap before propagating, with a loud
    agent-phase exit guarantee degraded stderr note when fa survives both
    signals (never a silent proceed, never an unbounded hang).

  • fix(bench): gh-1643 — a trial killed mid-stream no longer reports 0/None
    usage. The fold read only FINALIZED assistant messages, so a run killed
    before its first assistant message (the costliest rows) folded to silent
    zeros. Both folds now price a dangling model_request_summary at its
    KNOWN request-side volume (system prompt + outbound message chars at the
    shared chars/4 rate, estimated input; output unknown) and mark the usage
    incomplete instead: UsageChainScan.usageIncomplete → usage.json
    (usageIncomplete: true, written only when set so completed folds stay
    byte-identical), SessionUsage.usage_incomplete → the tb adapter's
    usage-guard.json row and harbor's agent_result.metadata, and the
    post-mortem stamps usage_incomplete on every results.json row it
    patches from a partial fold. Harbor also exports the run outcome class
    (agent_result.metadata.failure_mode: agent_timeout self-kill /
    external_kill / completed / crashed / never_started) so downstream
    classification stops relying on exception.txt archaeology; a summary
    superseded by another summary is flushed instead of silently dropped on
    both sides. Never-started trials keep the gh-1638/gh-1576 audit-only
    precedence (no fake usage).

  • fix(stuck-calls): gh-1632 — a stuck tool call in an interactive session now
    escalates like everywhere else. effectiveStuckTool() defaulted every
    non-headless host to StuckFollowUpMode.advisory when no explicit
    agent.stuckTool: was configured, so the fa 1.0.580 incident session (a
    dead git fetch in the REPL) fired its tool_stuck flag at the 300 s
    threshold and then hung for 27 more minutes — advisory mode never cancels,
    retries, or converts; only the owner's manual steering broke the deadlock.
    The autonomous default (cancel → retry once → background-convert, then a
    session-visible escalation) now applies in EVERY mode; advisory remains
    an explicit opt-out (agent.stuckTool.followUp: advisory), never an
    implicit default.

  • fix(bindings): gh-1624 — the gh-1570 binding preflight no longer kills
    translating-proxy factories. _validateModelEndpoint's vendor-prefix
    verdict was fatal on ANY endpoint, so FA_PROVIDER_TYPE=anthropic +
    bifrost's /anthropic path + zai/glm-5.3 exit-64'd every boot on
    1.0.576 (the prefix is a routing key on a translating proxy, not a
    dialect declaration). The vendor-prefix check now fires ONLY on the
    endpoints that carry a dialect verdict — the three family-exclusive
    canonical vendor endpoints (api.anthropic.com, api.openai.com,
    generativelanguage), mirroring the family verdict's scope: those keep
    the gh-1570 fail-fast, ROUTERS (OpenRouter/Copilot/DIAL/kimi — the
    prefix is a routing key there too; OpenRouter's canonical ids are
    anthropic/claude-*) and unknown/private endpoints carry the mismatch
    as a ProviderModelBinding.warnings entry, never exit 64. The warning
    is rendered on EVERY resolve path (gh-1624 review): the FA_PROVIDER_*
    boot notice, the provider queue (per entry at boot, on queue reload,
    and in /provider queue test), and the --model/--base-url flag
    revalidation — a rider never boots silently.
    Breaking-change note for 1.0.576 upgraders behind proxy
    factories: the migration is the wire: FA_PROVIDER_CONFIG key
    —
    documented now (README env-preconfig section,
    docs/agents/providers.md §Translating proxies, --help) with its
    caveat: the override engages only when the declared wire DIFFERS from
    the provider record's own kind (record anthropic + wire: anthropic
    still runs the checks). REG matrix (AC1/AC2/AC3) pinned in
    test/model_roles/provider_model_binding_test.dart; AC4 IT drives a
    bifrost-shaped loopback proxy through the production anthropic adapter
    (test/model_roles/bifrost_proxy_binding_test.dart).

  • gh-1596: the first-run wizard (site/setup.sh) no longer crashes or
    corrupts input under curl | sh on dash (Ubuntu sh) or busybox ash
    (Alpine): scripts/gen_installers.dart now renders the provider/mode/
    approval menus from the live provider catalog into plain POSIX sh — the
    bashisms that killed dash (printf -v, read -p) and silently corrupted
    busybox ash are gone; the hidden API-key prompt uses stty -echo + a
    by-name read (dash has no read -s) and the key never rides printf
    argv; EOF/Ctrl-C mid-wizard aborts non-zero with no half-written
    ~/.fah, and non-TTY runs print guidance and exit 1. The wizard's temp
    config is mktemp-created (no predictable, symlink-followable fixed
    path). A new path-triggered installer-portability CI leg proves the
    script under dash + busybox ash (ubuntu) and bash (macOS) on every
    site/** or generator change.

  • fix(cli): gh-1572 — fa boot no longer rewrites ~/.fah/config.yaml
    from a lossy config. persistConfig (bin/fah_runapp.dart) built a
    fresh CliConfig carrying only some fields and persistConfig() ran
    unconditionally at boot, so every launch silently dropped the user's
    spills:, subagents:, jobs:, headless:, agent:, quota:,
    tui:, trajectory:, waiting:, power.hold and auto_update
    settings (first reported on 1.0.547). Three cooperating fixes: the
    boot save is now saved.copyWith(<live overlays>) so every parsed
    section rides along by construction; CliConfig.copyWith replaces the
    drifted hand-rolled withCustomProviders field list (itself dropping
    spills/subagents/waiting/jobs/agentMode/misuseBreaker/streamThinking
    inside saveCliConfig's merge step); the _diskPreservedSections
    disk backstop covers every known top-level key. spills: renders in
    toYaml (new SpillsConfig.toYaml) and agent.misuseBreaker: false
    persists too; an unchanged save no longer rewrites the file at all.
    Rework round: the boot snapshot re-syncs from disk after every
    sanctioned mid-session settings write (onUserConfigFileChanged — a
    cleared context cap, an auto_update key drop or a global compaction
    pick is no longer resurrected/overwritten by the next save in the
    same session); validateAgentSection (the strict fa config check /
    config set / settings-hub mirror) accepts the emitter's
    agent.misuseBreaker and agent.stuckTool keys so fa config check
    never false-fails on fa's own output; clearing the context cap drops
    only the cap key instead of the whole agent: block (the sibling
    mode:/misuseBreaker: keys survive); and the providersQueue:
    block (no typed renderer) joins the disk backstop so a save can never
    silently delete the user's queue. Rework round 2 (merge + guards):
    validateAgentSection's per-key switch became the _agentKeyValidators
    table (the two gh-1572 keys pushed the switch form to complexity 13 —
    over the repo's CRAP ratchet; the table keeps the mirror complete at
    complexity 4), and the last two direct USER-config writers —
    _writeTtsrSection and _writeMcpSection — fire the re-sync hook, so
    a mid-session ttsr rule toggle or mcp server edit survives the next
    persist too (the writer roster in settings_flow_config_sync.dart is
    now exhaustive).

  • gh-1606: the copilot provider accepts fine-grained PATs
    (github_pat_…) directly — PAT mode skips the copilot_internal/v2/token
    exchange entirely (GitHub 404s every PAT exchange) and sends the raw PAT
    as the Bearer against the configured Copilot base URL with the official
    Copilot CLI's first-party identity headers
    (copilot-integration-id: copilot-developer-cli,
    user-agent: copilot/<fa-version> (<os>) term/unknown, installed by the
    host at startup). OAuth device-flow tokens keep the exchange +
    proactive-refresh flow unchanged; a PAT 401/403 surfaces the error
    instead of a doomed refresh retry. Headless COPILOT_GITHUB_TOKEN now
    works with a PAT. Connect flow: the paste hint explains PAT mode, the
    dead-PAT case surfaces at the model fetch, and /provider export copies
    the saved Copilot account's full token to the clipboard (account picker
    on several entries; the token never prints to the transcript).

  • gh-1575: macOS clipboard paste fixed for TIFF-only pasteboards + the
    Ctrl+V text fallback. _readMacos probes «class TIFF» after PNGf/JPEG
    miss and converts via sips -s format png (a Preview/browser copy used
    to dead-end on clipboard holds no image; a failed conversion keeps the
    unavailable fall-through and every scratch file is cleaned up); new
    readPasteboardText (pbpaste / xclip+wl-paste / PowerShell
    Get-Clipboard) behind the same injectable ProcessRunner/platform:
    seam, and Ctrl+V with a TEXT clipboard now inserts the clipboard text
    into the composer instead of printing the image probe's dead-end reason
    — bracketed-paste routing, gesture pairing and silent-failure semantics
    unchanged. Review pass: the Windows Get-Clipboard read pins UTF-8
    console output and normalizes embedded \r\n (OEM-code-page mojibake),
    the Linux text backends pin text/plain;charset=utf-8 (an image-only
    clipboard can no longer leak bytes as "text"), the library doc no longer
    overclaims the FA_FAKE_PASTEBOARD seam, and FaTuiCallbacks moved to
    a fa_tui_callbacks.dart part file keeping fa_tui.dart under the
    2800-line gate.

  • gh-1555 (W1, lazy/windowed-everywhere reads): the legacy full-open call
    sites migrate to windowed opens, so a session ledger past the gh-1073
    2 GiB full-open backstop stays openable on every user-facing read path.
    The TUI session picker, /resume, and the boot name-resolution scan
    open windowed and resolve the display name through paged reads; the
    subagent transcript preview opens the child windowed (tail only); the
    _loadSession marathon fallback no longer degenerates to a refused
    full open when the boundary walk exhausts its page cap (it proceeds
    with the tail-anchored windowed session; the boot cap owns over-window
    residue); and the headless fa trajectory resolver opens windowed,
    reading the whole ledger for the cost/inspect views through the new
    SessionTree.ensureWholeLedgerResident — a sequential forward scan in
    bounded strips (WindowedSessionStorage.loadAllOlder), not the
    boundary-walk's doubling strips (whose per-strip record cap measured
    ~13x read/parse amplification over the whole file). The same change
    block-prepends paged chunks in _indexChunk (one insertAll per
    list), removing an O(n²) the suspended-eviction walk paid per record.
    Trajectory export streaming (AC3a), split-and-retain rotation
    activation (W2), and the opt-in write-side caps (W3) remain follow-up
    work.

  • gh-1605: the FinalizeGate gains a "grader parity" named check. A
    produced entry point (script/CLI the verifier will run itself) is only
    verified when it survives the grader's own invocation: the checklist
    must re-run each produced entry point from a different working
    directory in a fresh subprocess with a clean env, capture stdout/
    stderr/exit code, and diff against the earlier in-session run — any
    divergence (different output, relative-path artifacts, locale/
    encoding-dependent output) is a fail ledger item, never a pass.
    Closes the modernize-scientific-stack__nwbgy7a near-miss (every
    in-session checklist item passed, the verifier's clean-cwd subprocess
    collapsed Station 102 to Station 101's mean, reward 0.0). REG coverage:
    the parity procedure catches cwd- and env-dependent entry points and
    does not false-positive cwd-independent ones
    (test/agent/finalize_gate_grader_parity_test.dart), and an IT replays
    the evidence shape end-to-end — divergence detected and persisted as a
    fail ledger item before finalize.

  • gh-1603: unattended runs can no longer end as a clean success on empty
    model responses. Loop: from the second empty-hiccup retry on, the request
    carries a continuation nudge user message ("your last reply was
    empty/truncated — continue the task"; exact-text constant
    emptyContinuationNudge, emitted with MessageStart/End so the transcript
    stays truthful) — a cap-truncated reasoning model re-streamed verbatim
    repeats the empty outcome, the nudge breaks the symmetry; the classifier
    treats that exact message as transparent, so the turn's partial-content
    window (gh-1577) survives a nudged retry. Headless: the empty-reply nudge
    budget rises to AgentCliConfig.headlessEmptyReplyNudges (default 2 — the
    session ends only after 3 consecutive unrecoverable empties; interactive
    keeps the #1085 M2b one-nudge semantics), and a run that still settles
    empty prints the explicit __FA_INCOMPLETE marker and exits 1 — a
    distinguishable failure for the bench adapter, never a silent exit 0.

  • gh-1577: an empty assistant completion after partial content (assistant
    text, tool activity, or a truncation signal earlier in the turn) is a
    bounded-retry provider hiccup — up to emptyHiccupRetries (default 3)
    re-streams with exponential backoff (emptyHiccupBaseDelay,
    emptyHiccupMaxBackoff, both threaded through the Agent facade,
    AgentWiringSpec, and the CLI config), and exhaustion ends the run with
    a loud StopReason.error (headless exit 1, interactive error + prompt)
    instead of the silent exit-0 settle that killed the gh-1577 trials.
    Clean empties keep the #1085 M2b one-nudge semantics. The abort always
    wins: a cancel racing the final retry settles StopReason.aborted for
    any empty shape (the exit-contract race is pinned by tests), and the
    backoff sleep is cancellation-aware.

  • gh-1574: SQLite reads work on installed binaries — bundle libs land where
    the binary looks. The AOT binary's native-assets resolver reads
    <exe_dir>/../lib/<name> (the dart build cli bundle layout), but every
    POSIX installer copied the bundle libs beside the binary
    (<install_dir>/lib), so read path=x.db died with a raw FFI dump
    (Couldn't resolve native function 'sqlite3_initialize' … Failed to load dynamic library '~/.local/lib/libsqlite3.dylib'); it only worked on machines
    with a manual symlink in ~/.local/lib. Fixed the layout in all three
    install paths — site/install.sh (incl. the quarantine-xattr pass and the
    source-build advice), install_local.sh, and fa update
    (bin/self_manage.dart copyBundleLibs, now public and layout-pinned by
    tests; also aligns Windows fa update with install.ps1, which was
    already correct) — plus clean degradation in the FFI engine:
    Sqlite3Engine.openReadOnly classifies the dart:ffi resolver's
    library-load failure (isSqliteDynamicLibraryLoadFailure) and throws a
    clean SQLite is unavailable — reinstall or run fa update note instead of
    the raw resolver dump; a Dart-level system-library fallback is impossible
    with package:sqlite3 native assets (build-time asset mapping, no loader
    override). Installs already broken by the old layout self-heal: on the
    bundled library's first failed load the binary moves the stale
    <exe_dir>/libsqlite3.* into <exe_dir>/../lib/ and retries the load
    (repairStaleSqliteBundleLayout) — so one fa update suffices (the
    successor binary repairs on its first SQLite read; a pre-fix binary cannot
    repair itself), and the stale <bin>/lib leftovers disappear as they
    migrate. The POSIX installers also report a failed lib copy instead of
    swallowing it (site/install.sh warns naming the lib dir; same in
    install_local.sh), and site/install.sh.template carries the same
    layout so regenerating the installers cannot resurrect the old one.

  • gh-1556: bench pipeline stops bleeding score + fa deadline awareness.
    Bench: the harbor ladder kill uses the [f]a bracket trick and is
    wrapped so a kill failure can never replace the TimeoutError (the
    pre-fix self-matching pkill classified TB 2.0 ladder kills as agent
    crashes — a SOLVED task scored 0); progress-watch (stall-gap 360s per
    the gh-1430 watchdog invariant, flat abs ceiling 3600s) is the
    bench-harbor.yml default on every shard, with the harbor adapter
    picking the gap-aware ProgressWatch for parity with the
    terminal-bench adapter; the aggregate counts completeness by
    result.json presence (errored trials report as their own breakdown
    line; exit 1 only on genuinely lost trials) and renders the full
    report — table, errored breakdown, ledger row — to
    $GITHUB_STEP_SUMMARY (TB1 parity). Both bench adapters export
    FA_DEADLINE_EPOCH_MS (effective cap − 120s wrap-up margin) into the
    agent session and audit the exact value. fa core: deadline awareness —
    FA_DEADLINE_EPOCH_MS / --timer/--deadline / /timer write one
    session clock, the per-turn context carries a time-left: line while
    armed, and one-shot system notices steer wrap-up at ~50%, T-3min
    (drain background jobs), and T-30s; absent deadline = byte-for-byte
    today's behavior.

  • gh-1522: tag-sourced versions — the git tag is the single source of
    truth. The release-bot file bumps are retired: cutting a release = a
    tag + GitHub Release (zero commits to main, zero file mutations).
    Both pubspecs carry the fixed 0.0.0-dev placeholder (CI guards
    hand-bumps), the publish job stamps the staged pubspec + generated
    changelog section from the tag (scripts/stamp_staged_release.sh,
    staged version == tag guarded pre-upload), store builds stamp
    --build-name/--build-number from the derived tag version
    (pubspec.lock stops churning across releases), and
    install_local.sh stamps version.txt from git describe.

  • feat(usage): gh-1460 — the fa-tokens: segment-close line carries the
    session's model id, so every token row the factory publishes is
    attributable to a model and therefore priceable. The fold now captures
    each segment's LAST-SEEN model id (the model that served the final
    request, the same string settings/UI display — mid-session fallback
    take-overs attribute the segment to the model that actually finished it;
    zero-filled fallbacks never clobber a seen model) and the pinned line
    shape grows an OPTIONAL "model":"<id>" key after "segment" — omitted
    when no model was observed, so legacy lines parse unchanged and the
    pinned regex (usageTokensLogPattern, gh-1241 AC7) keeps matching both
    shapes. usage.json's schema is untouched (the model is rebuildable from
    the chain, I6); old consumers using tolerant JSON ignore the new key,
    and the repo's single strict consumer (the pinned constant) is updated
    in the same change.

  • fix(test): gh-1460 rework — the wrap_ansi_line_perf_test growth-shape
    legs measure the doubling ratio with the MINIMUM over 9 runs instead of
    the median of 5 (CI run 38112524699, core shard 0/4: a scheduling pause
    inflated the doubled-size median — 12.09ms against a true ~7ms — and
    tripped the 3.2 bound at 3.65x on a genuinely linear wrap). Runner
    jitter only ever inflates a wall-clock sample, so the min converges on
    the true compute time; genuine superlinearity still trips the ratio
    (the gh-1496 quadratic candidates measure ~4x by min). The absolute-
    budget legs keep the median — their gh-1357 headroom already prices in
    jitter. A synthetic regression test pins the pause-proof property.