Commit graph

354 commits

Author SHA1 Message Date
Bryan Helmkamp
cc590f6f97
Merge remote-tracking branch 'origin/main' into fix/modal-provider-catalog
Resolve the model catalog table conflict in docs/public/core-concepts/models.mdx
by keeping both changes: this branch's `kimi` -> `moonshot` provider rename for
the Kimi rows, and main's new DeepSeek V4 rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:32:19 -04:00
Bryan Helmkamp
20d3aa5cdd
Merge pull request #705 from fabro-sh/feat/download-all-artifacts
Download latest artifacts as a ZIP
2026-08-01 09:28:16 -04:00
Bryan Helmkamp
24cd22d793
Merge pull request #703 from fabro-sh/fix/mcp-restart-after-upgrade-697
Fix stale MCP servers after upgrades
2026-08-01 09:26:26 -04:00
Bryan Helmkamp
a78750fa97
Update attach snapshot and soften context_values wording
The preamble removal changed the `stage.completed` payload, so the
`attach --json` inline snapshot no longer matched. Drop the stale
`current.preamble` line.

Reword the `context_values` doc row. `stage_context_values` only strips
runtime-only keys; it does not normalize artifact pointers to blob refs
the way `artifact::durable_context_snapshot` does, so calling it a
durable snapshot overstated it. Point readers at `checkpoint.completed`
for the durable projection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:26:13 -04:00
Bryan Helmkamp
8b767c658b
fix: make the artifact tie-break match the artifacts page
Two artifacts can share a filename, a retry, and an absent stage, in
which case the winner was whichever the object store listed first. Break
the tie on the serialized stage ID, which is the third key the artifacts
page sorts on. Compare the `node@visit` string rather than StageId's own
ordering: the page compares the string, so "unknown@2" beats
"unknown@10" there and now here too.

The spec said captures from the `start` and `exit` nodes are excluded,
but the exclusion is by handler type, so a node named `start` that does
real work keeps its artifacts. Say that instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:20:40 -04:00
Bryan Helmkamp
2adb44707d
Address Copilot review comments
Raise the replacement test's deadline to 20s. The server takes up to 1s to
notice the replacement and then bounds its own shutdown at 5s, so the old
5s deadline sat below the worst case and could fail a healthy server on a
loaded runner. A passing run still exits in about a second.

Reword the SHUTDOWN_TIMEOUT comment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:18:57 -04:00
Bryan Helmkamp
b834e0bda3
perf: batch archive writes and tolerate a vanished artifact
Deflate flushes its output in 8 KiB blocks, and each write became its own
allocation, channel send, and HTTP body frame. A 64 KiB BufWriter in
front of the sink cuts all three by eight.

An artifact deleted between the listing and its read no longer aborts the
whole archive. That race is a run being pruned mid-download; leaving the
file out beats handing back a truncated ZIP missing everything after it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:12:27 -04:00
Bryan Helmkamp
14cf4f3e94
Simplify MCP executable monitoring
Exit the process from `main` for every command instead of returning. The
`mcp start` command parks Tokio's stdin reader on a read only the MCP host
can end, so dropping the runtime waits forever. Exiting in `main` also keeps
the CLI telemetry event, which the previous exit inside the MCP command
skipped.

That removes the reason for the `McpServerExit` enum, whose only job was to
carry an implementation detail out to the CLI so it could exit.

Watch the executable through its device and inode on Unix. That is a
complete file identity, so the length and modification time no longer add
anything. Drop the PATH scan: `current_exe` reports the symlink itself on
macOS, so it detects a Homebrew relink without it. This also drops the
`fabro-static` dependency and a clippy suppression.

Bound the shutdown wait after an upgrade is detected. The transport closes
by writing to a stdout the host may already have stopped reading, which
could hang the exit the change is supposed to trigger.

Log a warning when upgrade detection cannot start, rather than disabling it
silently.

Share one spawn helper between the two raw stdio tests, and link the test
executable instead of copying 200 MB of binary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:10:28 -04:00
Bryan Helmkamp
daaca3f479
refactor: simplify artifact ZIP download
Path safety now lives in one place. The NUL-byte and drive-letter rules
move from a server-only helper into the store's own filename validation,
so uploads reject those paths at write time instead of only the ZIP read
path catching them. The download still re-checks, because artifacts
stored before the rule existed can still carry an unsafe path, but it
now skips a bad path rather than failing the whole archive.

Promote is_boundary_stage to RunProjection and drop the three identical
private copies. The ZIP download used a node-name match instead, which
would have dropped artifacts from a working node that happened to be
named "start".

Compress the archive. Entries were Stored while the response was also
excluded from transfer compression, so text artifacts moved at full
size. async_zip gains the deflate feature; async-compression and flate2
were already in the lock file.

Log archive failures unconditionally. The send-succeeded guard meant a
client that had already disconnected left no record at all, which is the
case where the log is the only evidence.

Also: collapse the duplicate 500 arms, drop the dead stage-ID tiebreaker
and the cached order in the selection map, name the accessible label
after the visible one, share the run URL prefix between the two download
href builders, and document the mid-stream truncation behavior in the
OpenAPI description.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:09:54 -04:00
Scott Werner
42dcb410f2 Remove obsolete manifest Docker image argument 2026-08-01 09:03:10 -04:00
Bryan Helmkamp
bf913ec58e
Merge pull request #685 from fabro-sh/fabro/run/01KYQMV1VW6139EGNHEM1RGF2G
Reject invalid run events before write; clarify append commit status
2026-08-01 08:21:59 -04:00
Bryan Helmkamp
9ef8742d5d
Merge pull request #687 from fabro-sh/fabro/run/01KYQN78K19NY7PNSCDYP6CG9G
Extract a source-neutral run-compiler boundary in fabro-server
2026-07-31 17:11:33 -04:00
Fabro
8282581ac7 fabro(01KYQN78K19NY7PNSCDYP6CG9G): verify (succeeded)
Fabro-Run: 01KYQN78K19NY7PNSCDYP6CG9G
Fabro-Completed: 10

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-31 14:14:25 -04:00
Fabro
bca63e75c6 fabro(01KYQN78K19NY7PNSCDYP6CG9G): simplify_sol (succeeded)
Fabro-Run: 01KYQN78K19NY7PNSCDYP6CG9G
Fabro-Completed: 7

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-31 14:09:32 -04:00
Fabro
76f61f163b fabro(01KYQN78K19NY7PNSCDYP6CG9G): simplify_fable (succeeded)
Fabro-Run: 01KYQN78K19NY7PNSCDYP6CG9G
Fabro-Completed: 6
Fabro-Checkpoint: 1254f4ca92f7b1f822cdfa7ca549ef8595a50fed

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-31 14:09:32 -04:00
Fabro
ac6e3ced6a fabro(01KYQN78K19NY7PNSCDYP6CG9G): implement (succeeded)
Fabro-Run: 01KYQN78K19NY7PNSCDYP6CG9G
Fabro-Completed: 5
Fabro-Checkpoint: 879008d9d5

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-31 14:09:32 -04:00
Bryan Helmkamp
980aabc543
feat: download latest artifacts as zip 2026-07-31 13:23:35 -04:00
Bryan Helmkamp
f868734f27
Fix stale MCP servers after upgrades 2026-07-31 13:18:12 -04:00
Release Repro
f613821bfb
feat(llm): add direct DeepSeek provider 2026-07-31 13:10:08 -04:00
Bryan Helmkamp
fd55ff03ad
fix(model): prefer MOONSHOT_API_KEY
Keep KIMI_API_KEY as a legacy fallback for env and vault credentials.
2026-07-31 09:14:58 -04:00
Bryan Helmkamp
24f9ac6bd1
refactor(model): rename Kimi provider to Moonshot 2026-07-31 09:00:52 -04:00
Bryan Helmkamp
8b5106902c
feat: reuse completed subagent sessions 2026-07-31 08:19:10 -04:00
Bryan Helmkamp
e6eb36f852
refactor: simplify model-keyed fallback internals
Consolidation pass over the fallback feature, no intended behavior
changes beyond noted validation and event-shape cleanups:

- Unify the two parallel notice types: FallbackPlanNotice is gone;
  ModelFallbackNotice now owns the runtime NoNearbyReasoningLevel case
  and the shared ChainEmpty wording. Notices emit through a new
  Emitter::notice_scoped with their own level, and each distinct notice
  is emitted once per run instead of on every LLM call.
- Move canonical_model_id onto Catalog so chain keys are written and
  read through one function; reject provider-qualified fallback keys,
  which could never match at dispatch and were silently dead config.
- Type FallbackTarget as ProviderId/ModelId, removing repeated
  ProviderId::new re-wrapping at every use site.
- Derive FallbackPlan's current route from a position index instead of
  storing current/requested_controls copies; advance() no longer has
  unreachable None branches.
- Bundle the agent invocation's live state (session, bridge, lease,
  forwarder, accounting) into LiveAgentInvocation; failover_agent_session
  drops from 21 parameters to 7 and the six copies of the
  abort/discard/classify teardown collapse into two methods.
- Share one route_request builder between one_shot and its failover
  loop; complete_one_shot_request takes the request by value instead of
  deep-cloning the message payload per call.
- Event::Failover carries FailoverProps directly; the props' original
  route and attempt fields are now required, and reasoning efforts are
  typed ReasoningEffort instead of strings.
- Reuse RunModelSettings/RunModelControls in fabro-api via
  with_replacement, add the missing controls property to the OpenAPI
  schema, regenerate the TS client, and add the type-identity/JSON
  parity test.
- Smaller cleanups: ReasoningEffort::closest_supported uses enum
  discriminants; ModelFallbackPolicy gains len(); resolve_model_fallbacks
  takes a provider slice; duplicate-target filtering lives only in the
  resolver.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 12:51:36 -04:00
Bryan Helmkamp
ba82656656
feat: add model-keyed fallback policies 2026-07-30 12:02:41 -04:00
Fabro
5f46acdf3f Merge remote-tracking branch 'origin/main' into fabro/run/01KYQMV1VW6139EGNHEM1RGF2G 2026-07-30 00:22:09 +00:00
Fabro
a94233407b fabro(01KYQMV1VW6139EGNHEM1RGF2G): simplify_sol (succeeded)
Fabro-Run: 01KYQMV1VW6139EGNHEM1RGF2G
Fabro-Completed: 7
Fabro-Checkpoint: edcf7467e9

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-30 00:22:06 +00:00
Fabro
6ce5ea76a2 fabro(01KYQMV1VW6139EGNHEM1RGF2G): simplify_fable (succeeded)
Fabro-Run: 01KYQMV1VW6139EGNHEM1RGF2G
Fabro-Completed: 6
Fabro-Checkpoint: b9c14c247e

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-29 22:31:26 +00:00
Bryan Helmkamp
0f2b66565a
Address artifact review feedback 2026-07-29 16:37:59 -04:00
Bryan Helmkamp
5f040d3107
Fix artifacts from repeated stage visits 2026-07-29 14:27:37 -04:00
Scott Werner
1575cc174f Stop requesting workflow write permission 2026-07-29 13:19:55 -04:00
Bryan Helmkamp
cb24f47b59
Merge remote-tracking branch 'origin/main' into feat/infer-command-node-from-script
# Conflicts:
#	docs/public/workflows/stages-and-nodes.mdx
#	lib/foundation/fabro-types/src/graph.rs
2026-07-29 10:40:26 -04:00
Bryan Helmkamp
de7bb61ef5
Remove nonfunctional run agent permissions setting 2026-07-29 10:23:00 -04:00
Bryan Helmkamp
a369ea7fc4
Merge remote-tracking branch 'origin/main' into feat/for-each-item-injection
# Conflicts:
#	apps/fabro-web/app/components/stage-renderers/parallel-children.tsx
2026-07-28 20:03:25 -04:00
Bryan Helmkamp
17b3223558
Neutralize runtime for_each labels before display
Addresses three Copilot review comments on #653.

`item_label` comes from a model or a workflow author, and it reaches the
terminal through the CLI progress display. A label could carry ANSI
escapes, newlines, or bidi overrides and rewrite what the operator sees.
It could also be whitespace-only, giving a branch a blank identity.

Add `text::sanitize_display_label`: strip ANSI sequences, drop control
and bidi-reordering characters, trim, and elide past 80 characters.
Return an empty string when nothing printable survives so callers fall
back to an identity they control.

Apply it where the label is created, so events, the store, and the web
UI all get a clean value instead of each consumer having to remember.
`parallel_branch_display` sanitizes again, because a run recorded before
this commit still has raw labels in its event log.

`emit_branch_retrying` now sets `stage.retrying`'s `index` from the
branch stage's execution ordinal, matching the envelope `stage_id` and
the meaning every other emitter gives that field. The branch's position
in the fan-out is already on `parallel.branch.started`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 19:52:59 -04:00
Bryan Helmkamp
f4c09867e5
Merge origin/main into refactor/remove-env-interpolation 2026-07-28 18:31:16 -04:00
Bryan Helmkamp
fa85bc42a2
Merge pull request #643 from fabro-sh/feat/claude-5-profile
feat(agent): add Claude 5 profile
2026-07-28 18:25:21 -04:00
Bryan Helmkamp
977628000b
Merge pull request #668 from fabro-sh/feat/provider-qualified-fallbacks
Support provider-qualified model fallback selectors
2026-07-28 18:13:43 -04:00
Bryan Helmkamp
6c516ec31d
Merge pull request #660 from fabro-sh/feat/parallel-branch-live-status
Show live status for parallel branches
2026-07-28 18:13:21 -04:00
Bryan Helmkamp
00228383dd
Merge remote-tracking branch 'origin/main' into refactor/remove-env-interpolation
# Conflicts:
#	lib/foundation/fabro-types/src/settings/interp.rs
2026-07-28 17:54:35 -04:00
Bryan Helmkamp
503e21fbc1
Merge origin/main into feat/parallel-branch-live-status
Conflicts were between this branch's parallel-branch identity work and
main's stage billing, review targets, and live stage timing.

- Stage fixtures: main added `billing` to each per-file `makeStage`; this
  branch had hoisted one builder into `lib/test-utils`. Kept the hoisted
  builder and gave it `billing: makeBilledTokenCounts()`, so both intents
  hold and the field list stays in one place. `stage-sidebar.test.ts` also
  builds raw `RunStage` wire payloads, so it keeps importing
  `makeBilledTokenCounts` directly.
- Import lists (`run_projection.rs`, `fabro-api/src/lib.rs`,
  `run_state.rs`, `stage_projection_round_trip.rs`): unioned both sides —
  `ParallelBranchId` alongside `timing`, `ReviewTarget`,
  `ReviewTargetKind`, `AttrValue`, `Node`, and
  `StageToolBatchProjection`.
- `fabro-server` tests: git interleaved two unrelated new tests into one
  body. Split them back into
  `list_run_stages_exposes_parallel_branch_identity` and
  `run_billing_includes_live_stage_timing_in_rows_and_totals`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 17:50:36 -04:00
Bryan Helmkamp
9d828a8688
Merge remote-tracking branch 'origin/main' into refactor/remove-env-interpolation
# Conflicts:
#	lib/components/fabro-workflow/src/pipeline/pull_request.rs
2026-07-28 17:44:03 -04:00
Bryan Helmkamp
b19dda6aa3
Merge pull request #661 from fabro-sh/fix/small-default-skips-providers-without-small-model
Fix run title generation picking a flagship model, and sharpen the prompt
2026-07-28 17:38:27 -04:00
Bryan Helmkamp
6226c8c517
fix: address env interpolation review findings
Restore the documented SDK env credential facade without reintroducing run fallback behavior. Fail closed on GitHub permission resolution, require worker storage at the CLI boundary, and align interpolation names and generated docs.
2026-07-28 17:30:50 -04:00
Bryan Helmkamp
690ddd2a96
Fix live parallel branch refresh and simplify branch rendering
Branches bypass the engine's stage.started/stage.completed lifecycle, so
no SWR key invalidated the stages list while a fork ran. The new live
branch rows stayed frozen at their first observed state until an
incidental refetch. Map parallel.* events to the stages list, run events,
and graph keys.

Also:
- Label branch rows with formatStageLabel so a re-entered branch renders
  as `review_glm@2`, matching the sidebar and waterfall.
- Build branch rows in one pass and count live outcomes in one loop.
- Name ParallelBranchId in the OpenAPI spec and reuse fabro_types::
  ParallelBranchId, replacing two copies of an inline string format.
- Hoist makeStage and textContent into lib/test-utils so widening Stage
  cannot leave per-file fixtures stale (tests are excluded from
  typecheck, so the two component-test copies had already gone stale).
- Query stat tiles by data-stat instead of an exact Tailwind class.
- Reuse append_scoped_stage_event's body via append_event_with_scope and
  add test_branch_event instead of poking envelope fields.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 17:26:05 -04:00
Bryan Helmkamp
8f9b36c0b8
fix(test): propagate storage setup errors 2026-07-28 17:23:31 -04:00
Bryan Helmkamp
e91343bbeb
refactor: address run title review findings 2026-07-28 17:14:03 -04:00
Bryan Helmkamp
df0bd58819
Merge remote-tracking branch 'origin/main' into fix/small-default-skips-providers-without-small-model
# Conflicts:
#	lib/apps/fabro-server/src/test_support.rs
2026-07-28 17:06:20 -04:00
Bryan Helmkamp
1aa7a153b0
Merge pull request #652 from fabro-sh/fix/publish-failures
Make publish failures terminal
2026-07-28 17:03:18 -04:00
Bryan Helmkamp
2103e3fbde
Qualify colon model references against the provider registry
Splitting on the first colon in FromStr broke bare model IDs that
legitimately contain one. A reference like "llama3:8b" parsed as
provider "llama3" selector "8b", and since "llama3" is not a provider
the lookup failed instead of passing the ID through to the pinned
provider. Verified against origin/main: canonical_session_model with
"future-model:latest" pinned to openrouter returned the passthrough
before and a 400 after.

This is not fixable by choosing a different separator. Bedrock
inference-profile ARNs contain both colons and slashes, and
docs/public/integrations/bedrock.mdx tells users to put arbitrary
inference-profile IDs in api_id. Only the registry can tell a provider
prefix from a model ID that happens to contain the separator.

FromStr now leaves colon-bearing tokens bare, and ModelRef::qualify
promotes only those whose prefix names a known provider. resolve()
applies it, so the fallback path is covered; sessions.rs applies it
before its own match so it keeps its tailored ambiguity messages.

ModelRegistry is now implemented for Catalog in fabro-types, replacing
the CatalogModelRegistry wrapper that existed only in start.rs, so both
call sites share one registry view.

Covered by regression tests at both surfaces, plus qualify unit tests
for ollama tags and Bedrock ARNs. The pre-existing passthrough test
canonical_session_model_preserves_unknown_passthrough_on_selected_provider
passes again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 17:02:16 -04:00
Bryan Helmkamp
e6cf174578
Merge origin/main into feat/human-review-target
Conflict in apps/fabro-web/app/components/interview-dock.tsx. Main moved
the dock onto the shared collapsible `RunDockShell` and replaced the
local button constants with shared ones.

Kept main's structure whole and re-applied the review target rendering
onto it: the question paragraph in the shell's `body` becomes the linked
`ReviewTargetQuestion` when the target passes `safeReviewTarget`, and
plain text otherwise. Both now share main's paragraph classes through
`QUESTION_TEXT`, so the two renderings stay visually identical.

`peek` keeps using `question.text`, which is the correct plain-text
collapsed summary for a review target question.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 16:48:02 -04:00