Commit graph

4560 commits

Author SHA1 Message Date
Bryan Helmkamp
6760172879
Merge pull request #722 from fabro-sh/codex/move-input-scalar-coercion
Move input scalar coercion to shared types
2026-08-03 21:59:16 -04:00
Scott Werner
5305dca6c2 Regenerate TypeScript client without push outcome models
Regenerates the Axios client from the reduced OpenAPI spec and removes
the six stale pre-run-push-outcome model files the generator leaves
behind, along with their barrel and generator-manifest entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 19:27:33 -04:00
Scott Werner
3d77d371c6 Remove the recorded pre-run push outcome, keep the push
The manifest builder's best-effort pre-run push converted every result
into a PreRunPushOutcome that was serialized into GitContext, expanded
into five OpenAPI union arms, and generated into API clients — but no
production path ever read it; every field read was a test.

Delete the concept while preserving the behavior:

- Drop the PreRunPushOutcome enum and GitContext.push_outcome from
  fabro-types; GitContext keeps origin_url, branch, optional sha, and
  dirty, which remain real execution inputs and provenance.
- Rename the manifest outcome builder to push_manifest_branch_best_effort,
  a side-effect-only helper with the same decision rules: skip without an
  origin, skip on configured-repository mismatch, skip when the branch is
  already synced, otherwise push noninteractively and discard the result
  without failing manifest creation or logging raw Git stderr.
- Prove the push through repository state instead of the deleted enum: a
  branch ahead of a local bare origin is pushed during manifest build, a
  mismatched configured repository is not, and a failing remote helper
  still cannot fail manifest creation.
- Remove push_outcome from GitContext in OpenAPI, delete the five-arm
  union schemas, and drop the fabro-api type replacement and re-export.
- Keep one regression proving historical run.created events with a nested
  push_outcome still deserialize through ordinary unknown-field tolerance
  and reserialize to the reduced shape. No migration or event rewrite.

Old JSON carrying the removed field stays readable. Newly generated
clients omit a field older servers required, so new-client-to-old-server
compatibility is intentionally not promised for this pre-1.0 contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 19:27:33 -04:00
Scott Werner
e38f372eeb Move input scalar coercion to shared types 2026-08-03 19:17:03 -04:00
Bryan Helmkamp
b6fdd9df72
Keep task reminders transactional across interrupts 2026-08-03 16:56:54 -04:00
Bryan Helmkamp
c673ef4fde
Merge pull request #719 from fabro-sh/remove-manifest-display-metadata
Some checks failed
Rust / Format (push) Has been cancelled
Rust / Clippy (push) Has been cancelled
Rust / Generated Docs (push) Has been cancelled
Rust / Test (Linux) (push) Has been cancelled
Rust / Test (macOS) (push) Has been cancelled
TypeScript / Typecheck (push) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
Remove unused run-manifest display and provenance metadata
2026-08-03 16:45:32 -04:00
Bryan Helmkamp
53d96e27f9
Merge pull request #718 from fabro-sh/github-coordinate-shared-types
Move GitHub coordinate validation to shared types
2026-08-03 16:45:23 -04:00
Bryan Helmkamp
c87f005b5f
Merge pull request #717 from fabro-sh/codex/trim-run-created
Trim unused run creation event fields
2026-08-03 16:45:07 -04:00
Scott Werner
cc4bb520b7 Remove field-specific server event assertions 2026-08-03 16:36:34 -04:00
Scott Werner
2ecb8e0366 Test unknown run event properties generically 2026-08-03 16:23:00 -04:00
Scott Werner
6bfcbcf138 Remove redundant run event serde test 2026-08-03 16:16:02 -04:00
Bryan Helmkamp
69bb95c0cc
feat(models): add Qwen3.8 Max to OpenRouter 2026-08-03 16:01:19 -04:00
Scott Werner
4898cb7aa1 Align the playground manifest builder with the trimmed target
The playground's local manifest projection and builder now emit only
the resolved target path; workflow-name fallback, titles, and the
workflows-map key are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 15:42:18 -04:00
Scott Werner
d728ad5508 Remove unused run-manifest display and provenance metadata
Drop ManifestTarget.identifier (the raw token the user typed) and
ManifestGoal.path (the original goal-file path) from the OpenAPI
manifest schema, the Rust manifest builder, the regenerated Rust and
TypeScript client types, and every canonical test fixture. Neither
field had a production reader: the server selects the workflow by
target.path and consumes only the resolved goal type and text.

Target path, goal type/text, manifest versioning, and submitted-byte
persistence are unchanged. Old request bodies that still carry the
removed properties remain accepted through unknown-field tolerance,
pinned by a dedicated public-route regression test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 15:42:18 -04:00
Scott Werner
85dfcd36f9 Scope synchronous fixture I/O allowance to tests 2026-08-03 15:32:36 -04:00
Scott Werner
4194ff4a26 Update run creation event fixtures and docs 2026-08-03 15:09:32 -04:00
Scott Werner
cff0a2461a Trim unused run creation event fields 2026-08-03 15:09:25 -04:00
Scott Werner
cdc88b3158 Move GitHub coordinate validation to shared types
GitHub repository slug and git ref selector syntax now has one owner:
fabro-types::repository defines GitHubRepositorySlug with a try_new
constructor and the is_valid_github_ref_selector predicate.
fabro-automation keeps its public type path as a re-export of the same
type and delegates its existing parser and ref validation to the shared
grammar, preserving its exact error variants and messages. Server
checkout and materialization code imports the type from its canonical
owner. No wire, API, or behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 14:53:46 -04:00
Bryan Helmkamp
e7fc25f62b
Merge pull request #714 from fabro-sh/codex/create-only-run-store
Remove legacy run-creation recovery paths
2026-08-03 13:04:39 -04:00
Scott Werner
5a4a25ccc7 Remove the run-creation reopen fallback 2026-08-03 12:34:37 -04:00
Scott Werner
7104570049 Remove duplicate-run handling from store creation 2026-08-03 12:34:32 -04:00
Bryan Helmkamp
60cf4fdd8f
Merge pull request #712 from fabro-sh/remove-client-run-id
Make the server the only allocator of run IDs
2026-08-03 11:17:47 -04:00
fabro-releases[bot]
77e7704e81 Bump version to 0.313.0-nightly.0
Some checks are pending
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
2026-08-02 09:55:44 +00:00
Bryan Helmkamp
30ce02f2a8
Make pull request creation durable and asynchronous 2026-08-01 19:28:52 -04:00
Scott Werner
fa796f7f24 Fix labeled run lookup in CLI test 2026-08-01 13:55:58 -04:00
Bryan Helmkamp
116970ccad
Merge pull request #713 from fabro-sh/fix/require-unvisited-goal-gates
Some checks failed
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
Fail runs that bypass goal gates
2026-08-01 12:27:49 -04:00
Bryan Helmkamp
a966de25b3
Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Bryan Helmkamp <19+brynary@users.noreply.github.com>
2026-08-01 12:17:16 -04:00
Bryan Helmkamp
885bc92122
Fail runs that bypass goal gates 2026-08-01 12:10:12 -04:00
Scott Werner
44eadca29c Keep removed flag coverage server-free 2026-08-01 11:47:11 -04:00
Scott Werner
96bfeb156f Make the server allocate all run IDs 2026-08-01 11:47:11 -04:00
Scott Werner
be91a5ef89 Remove run IDs from create manifest producers 2026-08-01 11:47:11 -04:00
Scott Werner
3a558b225e Remove client-selected run IDs from the CLI 2026-08-01 11:47:11 -04:00
fabro-releases[bot]
d16cd75c56 Bump version to 0.312.0-nightly.1 2026-08-01 14:42:21 +00:00
Bryan Helmkamp
f212594875
Merge pull request #694 from fabro-sh/feat/reusable-subagent-sessions
Reuse completed subagent sessions
2026-08-01 10:21:41 -04:00
Bryan Helmkamp
8e4129dcc1
Merge pull request #675 from fabro-sh/fix/cross-process-refresh-lock
Fix cross-process CLI token refresh races
2026-08-01 10:21:30 -04:00
Bryan Helmkamp
4b732287e5
Merge pull request #676 from fabro-sh/remove-run-agent-permissions
Remove nonfunctional run agent permissions setting
2026-08-01 10:18:45 -04:00
Bryan Helmkamp
a617eaa5eb
docs: record why a resumed turn's send cannot hang an agent
Review asked twice whether `permit.send` can leave an agent Running with
no turn on its way. It cannot, and the reasoning is not local to the call,
so state it there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:17:10 -04:00
Bryan Helmkamp
5f72bfe3ac
fix(client): name the refresh lock in its wait error
LockError::Task is only constructed while waiting for the refresh
sidecar lock, so "auth store lock" pointed at the wrong file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:17:00 -04:00
Bryan Helmkamp
41cd1aac49
Merge pull request #693 from fabro-sh/fix/human-input-timeout-accounting
Pause workflow timeouts during human input
2026-08-01 10:13:49 -04:00
Bryan Helmkamp
8c19b1e94a
fix: keep forwarding a reused child's events after a broadcast lag
The subagent event forwarder left its loop on any `recv` error, including
`Lagged`. A lagged broadcast receiver stays usable, so one transient lag
silenced the child for the rest of its life while the task completed
normally and shutdown joined it without noticing. Session reuse widens
that window from a single turn to the whole parent session.

Also borrow each result's output when rendering a parent notification
instead of cloning it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:13:27 -04:00
Bryan Helmkamp
143e6600eb
Merge pull request #696 from fabro-sh/fix/expose-agent-output-schema
fix: expose output schemas to agents
2026-08-01 10:11:43 -04:00
Bryan Helmkamp
ef9606e6ec
Reuse PermissionLevel and fix stale run spec snapshots
AgentPermissions duplicated fabro_types::PermissionLevel: same variants,
same kebab-case wire form, same crate. PermissionLevel is strictly richer
(Hash, strum, clap::ValueEnum) and is already the with_replacement target
for the OpenAPI PermissionLevel schema, whose values are identical to the
AgentPermissions schema this branch deletes.

Delete AgentPermissions and type the [cli.exec.agent] permissions setting
as PermissionLevel. This drops the adapter match in `fabro exec` and the
`as AgentPermissionLevel` alias that existed only to tell the two names
apart. The TOML wire form is unchanged.

Removing run.agent.permissions also changed the serialized run spec, but
two fabro-cli inline snapshots still carried "permissions": null. They
failed on this branch and passed on main. Accept the updated snapshots.

Also tighten the removed-setting test to assert the exact unknown-field
message, rename its module to run_agent now that it covers more than
fabro_tools, and drop three doc references to the removed setting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:09:44 -04:00
Bryan Helmkamp
1bb70adafd
refactor: simplify subagent session reuse
Review pass over the reuse change. No intended behavior changes.

- share one definition of the initial generation from fabro-types instead
  of three copies across fabro-types, fabro-agent, and the supervisor
- give each child one SubAgentHandle instead of threading the supervisor's
  state, callback, and notification sender through five functions, and
  collapse the repeated signal-then-drain pairs into publish()
- move `reusable` inside SubAgentStatus::Finished so a closed agent can no
  longer be marked reusable
- clear the lifecycle draining flag with an RAII guard, so one panicking
  callback cannot silence every later lifecycle event
- tear down a session that failed to initialize right away rather than
  holding it and its sandbox until the parent closes the agent
- look agents up through SupervisorState::agent/agent_mut instead of five
  copies of the same not-found error
- drop the unreachable cleanup_started branch and the test-only emit_event
  whose only caller was its own test
- render subagent starts from one ProgressEvent and one display method,
  deriving the spawn/turn distinction from the generation
- set projected subagent status through one helper instead of four
  identical reducer arms
- drive the generation-pinned wait test through spawn/send_input rather
  than hand-writing private supervisor state

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:09:07 -04:00
Release Repro
fae39d9fd6
refactor(workflow): tidy stall watchdog wiring and interview naming
Second pass, from the remaining review findings.

- Wrap the stall watchdog in a `StallWatchdog` type. The call site kept
  two parallel `Option`s derived from the same condition and threaded out
  an `Option<(CancellationToken, JoinHandle<()>)>`. `monitor_for_stall`
  also took two same-typed `CancellationToken` params pointing opposite
  directions, where swapping them compiles and yields a run that silently
  never stalls.
- Rename `WorkflowAgentQuestionRuntime::stage_id` and
  `PendingAgentQuestionBatch::stage_id` to `node_id`. They hold
  `node.id`, and the previous commit put them two lines from
  `stage_scope.stage_id()`, which returns a real `StageId`.
- Widen the two real-time interview tests. `node_timeout_excludes_
  human_input_wait` allowed 20ms of active work against a 50ms budget,
  which is tight enough to flake under parallel nextest load. The blocked
  wait still outruns the timeout, so both still fail if the pause
  regresses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:08:03 -04:00
Bryan Helmkamp
58c8df593a
fix(client): only adopt a peer-rotated token while it is usable
The staleness check treated "different from the token that failed" as
"usable". A long-lived process could read an entry a sibling rotated an
hour earlier, whose access token had since expired, install it, and
return Ok. The caller retries once and does not refresh again, so that
surfaced a 401. Require the stored token to be unexpired; an expired one
now falls through and rotates with the refresh token just read.

Also:

- Give the non-Unix `acquire_refresh_lock` a no-op passthrough, matching
  the other lock helpers off Unix. Returning an error there broke
  re-installing a stored dev token, which needs no lock because it never
  writes.
- Rename `Client::refresh_lock` to `local_refresh_lock`. Two different
  locks were sharing one word four lines apart.
- Gate `LockError::Task` on Unix, where its only construction site is.
- Give the concurrency test a no-proxy transport connector. Building
  clients without one goes through `connect_target_transport`, which
  does not disable proxy discovery, against localhost.
- Assert the rotated refresh token reaches the store, which is the
  invariant behind single-use rotation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:06:11 -04:00
Bryan Helmkamp
69976fc308
refactor(client): dedupe auth store lock acquisition
The cross-process refresh lock added a third copy of the open-file,
try-lock, then block-on-contention sequence. Collapse all three into
one `open_locked_file` helper parameterized by `LockMode`, which
removes `open_lock_file` and `lock_error`.

Lock calls are now qualified as `FileExt` calls throughout, since
`std::fs::File` has inherent locking methods with different return
types that take precedence over trait methods.

Also give `acquire_refresh_lock` one signature on all platforms by
defining `RefreshLockGuard` for non-Unix targets too, instead of
returning `Result<(), _>` there and `Result<RefreshLockGuard, _>` on
Unix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:59:17 -04:00
Bryan Helmkamp
ee8f2201a5
Merge remote-tracking branch 'origin/main' into fix/expose-agent-output-schema 2026-08-01 09:58:37 -04:00
Bryan Helmkamp
c59b4e9fb4
Merge origin/main into fix/expose-agent-output-schema
One conflict, in StructuredOutputError::repair_message. main (#709)
added a `previous_error` parameter and richer validation-error
rendering; this branch had replaced the inline expectation match with
OutputSchemaKind::expectation().

Resolved by keeping both: main's new signature and section assembly,
calling schema.expectation() for the expectation text. The method
already supersedes main's inline match and carries this branch's intent
of embedding the resolved JSON Schema instead of naming it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:58:18 -04:00
Release Repro
3fa48c38aa
refactor(workflow): simplify interview block state and stall watchdog
Follow-up cleanup on the human-input timeout work.

- Drop the `unresolved_interviews` counter from `InterviewBlockState`. It
  duplicated `blocked_stages`, which is non-empty exactly when the run is
  blocked.
- Publish block state before emitting `run.blocked` / `run.unblocked` in
  both directions, so a listener reading `subscribe()` from an event
  callback never sees state that disagrees with the event. The watchdog
  still gets a full fresh deadline because it restarts on the unblock
  transition.
- Stop panicking in `InterviewBlockState::resolve`. It runs from `Drop`,
  where a panic during unwind aborts the process.
- Replace the emitter's `activity_revision` watch channel with a
  monotonic timestamp. `record_activity` runs on every agent stream
  delta, and the channel woke the watchdog task and re-armed its timer
  per event. The watchdog now samples `last_activity()` when its deadline
  fires and re-arms only if the run was active, so the hot path is one
  clock read and one relaxed store.
- Remove the now-unused `last_event_at()` and `epoch_millis()`.
- Collapse the duplicated blocked/unblocked `select!` arms in
  `monitor_for_stall` and `timeout_excluding_interview_wait` into one
  loop each, using a branch precondition to park the timer while blocked.
- Handle a dropped block-state sender in
  `timeout_excluding_interview_wait` by falling back to a plain deadline
  instead of panicking, which also removes a potential busy loop.
- Only compute `stage_id` when the node actually has a timeout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:53:07 -04:00
Bryan Helmkamp
66b282d966
Merge pull request #691 from fabro-sh/fix/modal-reasoning-token-usage
fix(llm): decode Modal reasoning token usage
2026-08-01 09:48:36 -04:00