Skip to content

v1.0.643

Choose a tag to compare

@github-actions github-actions released this 25 Oct 13:16
· 97 commits to main since this release
eea0462
  • feat(gh-1844): media slots never fall back to the main connection
    implicitly — SDK/CLI, app store, and the JS bridge resolve a slot
    without an override as UNCONFIGURED with an actionable
    "configure the slot" error; the main connection serves a slot only via
    the explicit per-slot useMainConnection: true opt-in (YAML
    models.slots.<slot>.useMainConnection / app's "Same as main
    connection" pick, preserved through media_models.json). Image and
    video generation accept multimodal input (text + reference images:
    sandbox paths, http(s) URLs, base64) on capability-declaring
    OpenRouter endpoints (input_references + declared/probed
    input_modalities gate; unsupported endpoints fail before any network
    call); the OpenRouter video job API is a first-class video dialect
    (submit → poll → mp4). New decide tool: structured decisions via
    OpenRouter's Decisions API (/v1/systemone) — bool/choice/score
    questions with calibrated probabilities, structured error results, no
    streaming.

  • feat(gh-1834): the FinalizeGate contract counts a parity/conformance
    claim as verified only with negative + combination self-checks — new
    numbered step 2.6 in prompts/cli/finalize_gate.md (refused
    incompatible/mutually exclusive options, empty/corrupt inputs, boundary
    values, must-NOT-work cases, alongside happy paths; a blanket
    "everything matches" is not verified). Universality: abstract example
    only, no benchmark/task/flag names in the prompt; the step is
    conditional, so tasks with no conformance claim see no gate change.
    Ledger/verdict wire formats unchanged; validated end-to-end by the
    scripted IT-4 replay (real CLI + real bash) whose persisted
    task_ledger carries the rejection rows.

  • fix(gh-1832): release v1.0.635 published without fa-extension.zip — the
    landing-page download 404'd. The extension's only producer (the daily
    build-macos.yml build-extension leg) derives its tag as "latest+1 at its
    own dispatch moment"; on 2026-10-24 the v1.0.635 tag was cut by
    auto_release before those legs ran, so the extension (and the DMGs)
    landed on an orphaned v1.0.636 draft while the tag train published
    v1.0.635 — every leg green, nothing asserted the published asset set.
    Now scripts/release_required_assets.txt is the single source of truth,
    scripts/assert_release_assets.sh gates the release-provenance publish
    flip (a missing asset leaves the release a draft and fails the run
    loudly) and a new release-asset-smoke ci.yml job post-publish probes
    every landing-page release link + the full required set (red on non-200).
    Remediation: the exact-tag fa-extension.zip (built from the v1.0.635
    tree by run 40064876732) was uploaded to release v1.0.635 — the landing
    link serves 200 again.

  • fix(gh-1820): the arm64 DMG leg's stale size floor is reseeded onto the
    shipped v1.0.634 shape — run 4004758753 (post-#1815 SPM-lane adoption)
    passed the min-platform gate and the full build, then failed the
    size_manifest.py +5% budget on App.framework/App +5.1%
    (25,020,112 → 26,284,144, the accumulated Dart AOT of #1793/#1794/
    #1796/#1805/#1806); the committed scripts/size_budget/fa-macos-arm64.tsv
    was still the pre-#1096-diet floor (TOTAL 210.5 MB vs the artifact's
    116.4 MB), so the documented intentional-growth remediation applies:
    the floor is now the exact --reseed output for the failing run's
    artifact manifest (26 rows ≥ 64 KiB + TOTAL 116,386,369, from the run
    log's printed table — the zip never uploaded past the gate), locking in
    Fa 23.4→18.6 MB, quickjs_bridge 950→844 KB; new
    test/ci_1820_macos_arm64_size_floor_test.dart gates the shape green
    through the REAL size_manifest.py check and pins the committed floor
    byte-identical to a --reseed round-trip.

  • fix(gh-1814): the desktop DMG lane ADOPTS the SwiftPM lane — Flutter
    3.47.7 removed the FLUTTER_SWIFT_PACKAGE_MANAGER disable knob
    (flutter/flutter#193978), so the gh-180 pure-CocoaPods pin became a no-op
    and the min-platform gate (which forbade any SwiftPM state) failed both
    DMG legs on every pinned-3.47.7 run (39991663029, 40022900373); the
    #1813 escape (pin 3.47.4) is unreachable on the ~60kB/s runner link.
    scripts/assert_macos_min_platform.sh now ASSERTS the SPM lane instead:
    SwiftPM state expected after flutter build macos --config-only,
    generated package floor ≥ the app floor (the silent bump must not skip —
    the original gh-180 killer), no SwiftPM product above the app minimum,
    vendored inappwebview manifest floored at 14.0 if it ever reappears.
    Floors were audited and need no raising: Runner pbxproj 3×14.0, Podfile
    :osx, '14.0', generated floor 14.0 on 3.47.7, vendored podspec 14.0
    (its Package.swift stays deleted — the plugin rides the CocoaPods half of
    the hybrid lane via Podfile.lock). The dead env pin left the build-macos
    job env; the SDK pin reverts #1813 back to 3.47.7. The ci.yml macOS debug
    leg gets the same treatment (gh-1814 review): its
    FLUTTER_SWIFT_PACKAGE_MANAGER=0 pin — a silent no-op on the floating
    3.47.x stable it resolves past the knob's removal — is removed and the
    lane comment corrected; the leg rides SPM enabled as it already has been
    (run 399572012 green).

  • fix(gh-1805): modifier chords no longer type their base letter into the
    composer while the slash menu is open — the vendored dart_tui decoder
    puts the base letter into the keystroke's text for control bytes
    (Ctrl+S → ctrl+s with text s), and the slash-menu edit cluster was
    the one insert surface missing the _isCommandKeystroke guard, so a
    chord pressed mid-menu poisoned the typed prefix (/memory landed as
    /smemory). Plain letters and Shift+letters still type and filter.

  • feat(1515): ONE model-limits settings surface — the settings hub's
    standalone "Model parameters" row is retired and bare /model-edit
    (TUI) aliases into /settings → Model capabilities, pre-focused on the
    active model. The caps flow gains the model-edit preset ladders +
    Custom… free input for context window / max output (a preset pick
    writes models.overrides AND rebuilds the active model on the spot —
    the old flow's live-mutation semantics, now durable), every field row
    shows the current EFFECTIVE value with its source layer (pinned /
    role override / catalog), the thinking row is an omp-style selector
    (only levels the model supports, per-rung budget metadata, current
    level preselected), and the hub row describes the effective summary
    (200K ctx · 16K out · thinking off) instead of a pin count. The
    scripted /model-edit <field> <n> form and the line-mode limits print
    are unchanged.

  • feat(gh-1746): opt-in session disk retention — sessions.retention in
    ~/.fah/config.yaml caps the sessions root and sweeps whole session
    units (transcript + sidecars) oldest-last-used-first at boot, exempting
    live, named, young, and the current session; nothing is EVER
    auto-deleted without the section (byte-identical legacy boot). Adds
    fa session clean [--dry-run], the /sessions + fa session list
    usage footer under a cap, and header-cwd/path-escape guards so a
    planted session header can never steer a delete outside any root.

  • fix(gh-1763): a bash tool command running broad process kills can no
    longer signal its own shell or the harness. Scripts are delivered from a
    per-exec private temp file (sh -c '. "$1"' sh <script>) so the wrapper
    argv never carries the script body (pkill -f <pattern> cannot
    self-match its own sh -c; $0/$#/exit codes/set -e/stdin stay
    identical), a PATH shim for pkill/killall plus a kill() prelude
    guard exempt the harness pid chain + process group (exported as
    FA_PROTECTED_PIDS) and the invoking shell from the child's own kills
    while legitimate victims (stuck test runners, background servers) are
    still terminated, and headless SIGTERM aborts now log
    fa: SIGTERM received … so a child-issued kill never masquerades as a
    user Ctrl+C (exit code contract unchanged: 130 graceful, 143 escalate).

  • fix(gh-1778): the release gate no longer reds a fully-green shard when
    GitHub's cache service degrades. v1.0.621's Core tests (shard 2/4) was
    killed by its own timeout-minutes: 12 with 2535/2761 tests green — both
    cache steps had stalled ~133 s on keys that existed (degraded service,
    not eviction), cold pub get + native-asset hook builds delayed the
    first test to ~5 min, and the shard needed ~14 min against a budget
    sized for the warm ~8.8 min wall. The job budget is now 20 min (sibling
    test-leg band), and both test-core cache steps are best-effort
    (timeout-minutes: 2 + continue-on-error: true) — a failed restore is
    slower setup, never a red shard. test/ci_core_shard_timeout_guard_test.dart
    pins the budget and the best-effort cache contract (RED-first on the old
    YAML).

  • feat(#1761): the 32KB project-context budget never cuts silently again.
    A file truncated to its head excerpt now carries an extended
    <!-- From: path — N bytes, only the first M shown --> marker plus an
    explicit MUST-read-the-full-file instruction (offset/limit/ranges,
    prompt copy in prompts/context/project_context_read_full.md); a file
    dropped entirely leaves a one-line tombstone naming its path instead of
    vanishing; and the user gets a dim boot line per truncated/dropped file
    (CLI line mode prints directly, TUI re-prints after the banner, the app
    logs through its AppLog boot-notice seam). New projectContext: config
    section: maxBytes (default 32768) — 0 disables the section entirely
    and reads no files (fast boot); unknown keys are tolerated with a note.
    Leaf-first allocation is unchanged.

  • fix(gh-1757): the Android build no longer re-enters the sdkmanager
    download roulette for CMake. v1.0.619's build-android leg died at
    :jni:configureCMakeDebug[arm64-v8a] — the :jni module (transitive
    jni package via path_provider_android) wires externalNativeBuild.cmake
    with no version, so AGP auto-installed its default cmake;3.22.1
    mid-configuration and one corrupt archive ("Archive is not a ZIP
    archive") red'd the leg, the aggregate Quality gate, and the
    Advance-vpinned-marker job. The gh-1655 pre-install action now also
    pre-installs cmake;3.22.1 with bounded retry + idempotent skip (same
    hardening the NDK path already carries); the guard test pins the new
    AC6 assertions and the AGP-version coupling (AC7).

  • feat(#1750): per-model image caps — the outbound request never carries
    more unique images than the target model accepts. Model gains an
    optional maxImages (catalog-seeded: gemini-4-argon: 10, measured);
    images.perModel.<modelId> config overrides it (strict images:
    section, user truth wins); the registry resolves
    perModel > catalog > images.maxPerRequest (first present wins, the
    issue-#1554 shape). A Google-shaped too many images … maximum allowed for model <id> is <n> 400 is classified (image_overflow.dart, the
    overflow.ts twin), latches the learned cap for the (provider, model)
    pair for the run, and re-sends ONCE through the registry at the
    smaller cap — current-first-then-newest drops with the existing
    never-silent notices naming the learned cap; a second refusal
    surfaces exactly as today. JSONL is never rewritten;
    images.registry: false keeps the byte-identical legacy shape
    including the recovery-off behavior.

  • feat(gh-1760): oversized user-message intake spills to a file instead of
    blowing the context window. Any incoming user message — headless
    -p/positional, TUI first/mid-session, steering/follow-up drain,
    external inbox — whose text passes the new relative threshold (a
    spills.userFraction of the live window budget minus the compaction
    reserve, floored by spills.userThreshold bytes) is redacted and
    written verbatim to .fah/spills/<sessionId>/user-<n>.md (the #678
    layout) from one choke point (Agent.userMessageGuard); the model
    receives a bounded head+tail preview plus a read-in-parts instruction
    riding the replacement USER message (copy in
    prompts/spill/user_read_instruction.md). Images stay inline, a
    refused spill write degrades to today's inline behavior, and
    spills.enabled: false / userThreshold: 0 / the new
    FAH_USER_SPILL_THRESHOLD env knob (bytes) keep the legacy shapes.

  • 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.