Commit Graph
4803 Commits
Author SHA1 Message Date
Glen StampoultzisandGitHub b9b3a24d58 fix(desktop): restore notification sounds (#2582)
Restore desktop notification sounds by default, add a persisted Play sound setting, and group notification permission and testing in a dedicated Notifications section.
2026-08-08 22:28:37 +02:00
b19db9537c fix(server): summarise Claude AskUserQuestion permission notifications (#2925)
* fix(server): summarise Claude AskUserQuestion permission notifications

Claude's handlePermissionRequest never set title or description on the
AgentPermissionRequest it emits, so buildPermissionDetails fell through to
serialising request.input. For AskUserQuestion that input is the whole question
object, and the push and in-app notifications both showed truncated JSON.

Summarise the first question the way the OMP and Pi providers already do for
their ask_user permissions: title is the question text, description is the
option labels. request.input is untouched, so the permission UI keeps rendering
the full payload.

Closes #2612

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWY2Xdowxr562GXFdxPhjZ

* test(server): assert question permissions through the emitted request

Greptile flagged that the suite imported a production helper that was only
exported for the test, which leaves an unnecessary public export behind and
couples the tests to implementation structure.

The helper is module-private again, and every case now asserts on the
permission request the session emits and the notification body built from it.
Added a case pinning request.input so the permission UI payload stays covered,
and one for a non-question tool.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWY2Xdowxr562GXFdxPhjZ

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 21:28:21 +02:00
1a117652fd Fix Kimi usage refresh, 5 hour session limit, and reasoning levels (#2743)
* Refresh expired Kimi usage tokens

* Test Kimi usage token refresh

* Harden Kimi usage token refresh

Follow-up review of the refresh path added in f6ce001a.

Credential reads no longer depend on the shape of fields this provider
only writes back: declaring expires_at and friends as required numbers
meant a null or stringified value threw away the access token sitting
next to them, turning a working Kimi quota bar into "unavailable" with
no way back. Those fields are nullish and coerced now.

Refreshed tokens are merged into a re-read of the credential file rather
than the copy the request started with, so a write by Kimi Code during
the refresh round-trip survives, and a file deleted in that window stays
deleted instead of being resurrected with fresh credentials. A failed
write logs at warn: Kimi rotates the refresh token, so losing that write
silently logs the user out of Kimi Code.

Only 401 triggers a refresh. A 403 means authenticated but not entitled,
which no refresh fixes, and refreshing anyway rotated the user's refresh
token on every poll.

Also: floor expires_at to whole seconds, indent the file the way Kimi
Code does, derive the temp file name from the real basename, and split
out fetchUsageResponse, which is what puts fetchUsage back under the
complexity limit — the original passed neither lint nor the formatter.

* Keep CLI-rotated Kimi credentials during refresh

The Kimi CLI shares the credential file the quota fetcher writes. A refresh
read the file, merged its new tokens, and renamed over it, so a rotation that
landed while the refresh was in flight was replaced with stale tokens and the
CLI's session broke until credentials were recreated.

Thread the refresh token that was actually spent into the persistence step and
skip the write when the file no longer carries it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(kimi): normalize additional usage windows

* fix(kimi): preserve custom auth while parsing limit windows

* test(kimi): cover additional usage windows

* test(kimi): move usage-window tests into service.test.ts

Every other quota provider is tested in service.test.ts; kimi.test.ts
was the only provider with a standalone collocated test file.

* fix(kimi): resolve reasoning-effort options per model, not per catalog probe

deriveModelDefinitionsFromACP derived thinkingOptions once from whichever
model happened to be active in the one-off catalog probe session, then
copied that same array onto every model. Kimi's ACP adapter only reports
thinking levels for the currently selected model, so K2.7/K2.7-coding-fast
(boolean on/off) and K3 (off/low/medium/high) ended up with identical,
often-wrong options depending on which model the probe defaulted to.

fetchCatalog now reuses the single probe session to switch through each
candidate model and read back its real thought_level options, falling
back to the default options if an individual model's probe fails. Skips
entirely when there's one model or no thinking picker, so providers
without per-model effort levels pay no extra round trips.

* refactor(kimi): merge kimi-usage.ts back into kimi.ts

kimi.ts was the only quota provider split across two files; every
other provider (including claude.ts at 503 lines, larger than the
merged Kimi file) stays in one. Also fixes a nested-ternary lint
error surfaced by the merge.

* fix(kimi): re-validate credentials immediately before the rename

Greptile flagged that saveRefreshedCredentials read the credential file,
checked its refresh token against a stale snapshot, then did async
writeFile work before an unconditional rename — leaving a window where
the Kimi CLI could rotate the file after the check and have its newer
tokens clobbered by our stale merge. The refresh-token-only comparison
also missed a CLI access-token rotation that kept the same refresh token.

Re-read the file immediately before the rename and compare both tokens
against the snapshot the merge was built from, discarding the merge if
anything changed. Narrows the check-then-write gap to the smallest
window possible without cross-process file locking.

* refactor(acp): make per-model catalog probing an injectable resolver

Catalog discovery in ACPAgentClient.fetchCatalog ran sequential
setSessionConfigOption model switches for every multi-model ACP with a
thought_level picker, so a slow or nonconforming agent could stall any
provider's catalog probe under the shared 60s timeout.

Replace the built-in resolvePerModelThinkingOptions with an optional
ACPCatalogModelResolver injected through ACPAgentClientOptions (threaded
through GenericACPAgentClient like the existing transformer/parser deps).
The base client ships no resolver, so catalog discovery derives models
from the initial session response and never switches models. Export the
config-option helpers a resolver needs.

The base regression test pins that a plain ACPAgentClient with multiple
models and a thinking picker never calls setSessionConfigOption.

* feat(kimi): resolve per-model thinking options via KimiACPAgentClient

Kimi exposes different thinking-effort levels per model but only reports
the currently selected model's levels, so the catalog probe must switch
through each model to read them back. Move that logic off the shared ACP
adapter and onto a Kimi-specific client.

Add resolveKimiCatalogModels (the sequential setSessionConfigOption probe,
fallback, and warning) and KimiACPAgentClient, which injects it via the
new catalogModelResolver dependency. Select it for providerId "kimi" in
the registry alongside cursor/kiro/traecli, so only Kimi pays the extra
round trips; every other ACP provider skips model switching entirely.

Tests cover the four probe behaviors on KimiACPAgentClient and the
registry dispatch to it.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 21:09:26 +02:00
Mohamed BoudraandGitHub 638693a5d7 fix(app): keep the crash screen readable and its retry reachable (#3043)
The root error fallback was one unbounded scroller, so on a phone the title
rendered under the status bar and a long stack trace pushed Retry off the
bottom of the screen — the one action that recovers the app was unreachable
on exactly the errors that need it most.

The screen is now a fixed shell: a safe-area message block, an independently
scrolling details region, and a Retry that stays put. `SafeAreaProvider` moved
above the boundary in the root tree because the fallback renders when the app
tree below it has already failed, and insets have to be available there.

The hand-rolled Pressable and bordered message box are replaced by the shared
`<Button>` and `<ScrollableCodeSurface>`, which gives the trace a monospace
face and horizontal scrolling instead of wrapping mid-token. On desktop the
trace is capped and the action sits with the message; a button pinned to the
bottom of a tall window reads as detached from the content it belongs to.

Dropped the "Something went wrong" kicker — it restated the title one line
above it.
2026-08-08 20:59:01 +02:00
2740c8206f fix(server): resolve exact file paths that git ignores (#3042)
Clicking a file path an agent mentioned failed with "No file found"
whenever git ignored the file, which covers anything written under a
scratch directory such as .dev/.

Backticked paths do not open directly. They resolve through the
directory-suggestions RPC, which the daemon runs with gitignore
filtering on for every workspace search. findExactEntry resolved the
path, confirmed it stayed inside the workspace root, and then discarded
it on the ignore check.

Ignore filtering belongs to discovery. It keeps build output out of the
composer's @-mention picker and out of the directory chooser, and both
of those keep it. It does not belong to retrieval of a path the caller
named exactly, where containment inside the root is the only question
left. That check is now dropped from the exact-path branch alone.

This does not widen what is readable. The file-read path applies no
ignore filtering at all; resolveScopedPath checks only root containment,
before and after resolving symlinks. Every file this surfaces was
already readable through the file explorer.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 20:47:28 +02:00
0906d1d7bd fix(codex): omit disabled skills from slash commands (#2759)
* fix(codex): omit disabled skills from slash commands

Codex skills/list returns disabled skills with enabled:false. Filter
them out in loadSkills so Paseo matches Codex CLI/TUI and no longer
surfaces stale slash entries for skills the user disabled.

Fixes #2617

* fix(codex): preserve empty skill discovery

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-08-08 20:22:48 +02:00
Mohamed BoudraandGitHub 3d420720c5 docs(hub): document workflow security boundaries (#3032)
* docs(hub): add security guidance for workflows

* docs: register Hub security guide
2026-08-08 17:22:09 +02:00
Mohamed Boudra 375d1d6fb8 Merge branch 'main' of github.com:getpaseo/paseo 2026-08-08 16:59:56 +02:00
Mohamed BoudraandGitHub f0eb7cea4b feat(providers): add native options and exact MCP grants (#3025)
* feat(providers): add native options and exact MCP grants

Keep provider policy in provider-owned schemas so unattended Hub runs can grant only requested tools from injected MCP servers. Preserve the native resolved sandbox policy when a workflow override is present, including configured writable roots.

* test(hub): verify real provider tool policy

* test(app): avoid pinning transient restart status
2026-08-08 16:49:29 +02:00
Mohamed BoudraandGitHub b669dad221 Add durable Paseo Hub CLI login (#3022)
* feat(cli): add durable Hub login

Keep organization-scoped human credentials separate from daemon relationship authority. Connect now exchanges explicit or stored CLI authority for a one-time enrollment token, while logout removes human access without implicitly disconnecting automation.

* fix(cli): require TLS for remote Hub credentials

Durable Hub credentials are bearer secrets, so remote origins must not receive them over cleartext HTTP. Keep literal loopback HTTP available for local Hub development.

* fix(cli): make Hub destinations explicit

Resolve optional Hub origins through one shared precedence policy so active logins remain the default context and credentials stay origin-bound. Report the selected destination before human-facing work, keep structured output auditable, and preserve login authority until requested daemon disconnection succeeds.
2026-08-08 15:27:43 +02:00
Mohamed Boudra 7ac4303d8f docs(release): streamline release workflow
Make the release document the single source of truth. ACP drift is explicit opt-in, release preparation uses one remote batch, and a heartbeat owns completion through published artifacts and store submissions. Preserve one changelog entry per beta and collapse the series on promotion.
2026-08-08 13:49:22 +02:00
paseo-ai[bot] e36842b234 fix: update lockfile signatures and Nix hash [skip ci] 2026-08-08 09:04:58 +00:00
Mohamed Boudra 7392e1b767 chore(release): cut 0.3.0 2026-08-08 10:56:30 +02:00
Mohamed Boudra 3196385526 docs: collapse 0.3.0 beta entries into the stable changelog 2026-08-08 10:53:19 +02:00
Mohamed BoudraandGitHub b167ee02e1 feat(app): search history by workspace, agent, and branch (#2995)
History was a date-grouped list you scrolled. Finding a session from last
week meant eyeballing every row. This adds a search field to the existing
filter rail, next to the host picker.

Search runs on the daemon, not the client. The daemon already holds every
persisted agent in memory, so it ranks the whole history and the client
never has to warn that it only looked at the pages it had loaded.

A searched response is one ranked page with no cursor. The global top K is
always contained in the union of each host's own top K, so merging per-host
pages and slicing back to the limit is the exact global answer — no k-way
merge, and no offset into a ranking that is recomputed per request, which
would duplicate and skip rows as history changes underneath it. Past result
200 of a relevance order the answer is a narrower query, so the footer says
that instead of offering a "Load more" that cannot deliver.

Matching is the app's existing picker matcher, moved to the protocol package
so app and daemon share one implementation instead of drifting into two
"why didn't this show up?" rule sets. Typo tolerance is opt-in, so the
comboboxes keep exact narrowing; the budget is per token, and four-character
tokens get transpositions only, because at that length a free substitution
turns "main" into "mail", "maid" and "rain".

Ranked rows carry the spans they matched on, so results can mark them. The
subsequence and typo tiers match characters the eye would never find
unaided, and an unexplained row reads as a bug. The ranker computes the
spans anyway; sending them keeps the client from deriving a second opinion
that could disagree with the ranking it is explaining.

Hosts that fail or are offline are now named above the list. Without that,
"No sessions match" is a claim the app has no basis for.

Gated on server_info.features.agentHistorySearch, checked once against the
connected hosts. Reachability is reported by the banner instead, so one
offline host in the registry no longer hides the field.
2026-08-08 10:24:51 +02:00
paseo-ai[bot] cc5ce1038d fix: update lockfile signatures and Nix hash [skip ci] 2026-08-07 22:26:31 +00:00
Mohamed Boudra 8d3881f640 docs: clarify Pi 0.84 beta fix 2026-08-08 00:06:24 +02:00
Mohamed Boudra a7c7a6e482 chore(release): cut 0.3.0-beta.4 2026-08-07 23:59:18 +02:00
Mohamed Boudra 082ddb2303 docs: add 0.3.0-beta.4 changelog 2026-08-07 23:51:40 +02:00
Mohamed Boudra dc19b45f23 fix(desktop): keep browser surfaces interactive 2026-08-07 22:34:59 +02:00
98d3281cad fix(pi): stream assistant deltas when pi omits the cumulative message (#2978)
* fix(pi): stream assistant deltas when pi omits the cumulative message

pi 0.84.0 removed the cumulative `message` snapshot from JSON/RPC
`message_update` events, so `handleMessageUpdate` dereferenced
`undefined` and took down the whole daemon on the first assistant delta.
Every prompt to a pi agent crash-looped the worker.

`message_update` carries an `assistantMessageEvent`, which pi only emits
for assistant messages, so a missing snapshot is treated as an assistant
update. `message_start` still carries the message, so the response id
keeps flowing through `activeAssistantMessageId`.

Fixes #2940
Fixes #2946
Fixes #2950

* fix(pi): give the message_update COMPAT tag a version and removal date

Follows docs/protocol-compatibility.md: every shim tag needs a name, a
version, and a removal condition or date so `rg "COMPAT\("` stays a
schedulable cleanup backlog.

---------

Co-authored-by: dundunHa <dundunhade@gmail.com>
2026-08-07 21:26:08 +02:00
Mohamed Boudra 5e197ad0cf style(app): align help menu versions on a right rail
The app and host version lines ran as one sentence, so the versions ragged
with the length of each name and were hard to compare. Menu hints now take an
optional trailing cell and lay out as key/value across the row.

Renaming the string to a plain name drops its interpolation, so every locale
carries the same untranslated product name it already had.

MenuHint renders in one place today (this menu), so the row layout in the
shared component changes nothing else.
2026-08-07 20:28:27 +02:00
Mohamed Boudra 9b5f6f33f6 style(app): indent the sidebar new workspace row
The row only renders under its project header, so stepping it in reads as
part of that project rather than as a peer of it. Extra padding rather than
margin keeps the hover and pressed fills on the same box as every other
sidebar row.
2026-08-07 20:24:46 +02:00
paseo-ai[bot] 3ddebe7221 fix: update lockfile signatures and Nix hash [skip ci] 2026-08-07 16:18:18 +00:00
Mohamed Boudra 86ff0a18c4 chore(release): cut 0.3.0-beta.3 2026-08-07 18:09:43 +02:00
Mohamed Boudra bf38bd728c docs: add 0.3.0-beta.3 changelog 2026-08-07 18:05:52 +02:00
paseo-ai[bot] fcc54621ca fix: update lockfile signatures and Nix hash [skip ci] 2026-08-07 15:39:19 +00:00
Mohamed Boudra f842537142 fix(server): handle deferred Windows PTY failures 2026-08-07 17:32:03 +02:00
Mohamed BoudraandGitHub c9a9d8fdce Make workspace archive self-healing during setup (#2987)
* fix(server): make workspace archive self-healing

Background setup could keep writing after its workspace record was archived, causing disk cleanup to fail and the initiating client to restore a local ghost. Track setup per daemon so archive can stop it, and let repeated archive requests finish residual cleanup after the record has committed.

* fix(server): await setup process termination

Archive must not proceed from setup cancellation until the process-tree terminator has completed. Join that promise to the streamed command's existing finish path so child close cannot release workspace cleanup early.
2026-08-07 16:47:33 +02:00
Mohamed Boudra 6abfa242d8 fix(app): keep launch selector with mobile controls
Hidden optional rows reserved space in the middle of the compact form. Move those spacers below the visible controls so worktree options push the launch selector down without separating it from the other selectors.
2026-08-07 15:30:57 +02:00
Mohamed BoudraandGitHub a06e4d60e9 Keep workspace Git refreshes responsive at scale (#2979)
* fix(server): prevent Git watcher fanout stalls

Repository metadata events were refreshing every observed worktree, multiplying Git process submissions and starving workspace create and archive requests behind observation work. Route metadata to the workspaces that depend on it, preserve conservative shared-ref refreshes, and admit interactive lifecycle operations ahead of queued background reads.

* fix(server): preserve Git metadata dependencies
2026-08-07 18:50:43 +08:00
Mohamed BoudraandGitHub a5184fd08a fix(app): keep browser relay WebSockets protocol-neutral (#2976)
Use the standard browser WebSocket constructor without the Node/React Native options object, while retaining the options-capable factory for supported runtimes and the desktop bridge.
2026-08-07 17:30:43 +08:00
Mohamed Boudra 75a5065bb3 fix(app): apply clipboard syntax at selection boundaries
Selections contained inside formatting copy only their visible content. Preserve Markdown syntax when the range includes or crosses the semantic element, while retaining list and table structure needed to serialize child boundaries.
2026-08-07 10:43:44 +02:00
Mohamed Boudra 84b735fbea fix(app): preserve nested list clipboard lines
Flattened child list lines were reparented inside the parent item line, so rich paste targets split markers from their content. Keep every flattened line as a sibling and cover cross-structure selection plus inline-code double-click copying.
2026-08-07 10:25:43 +02:00
Mohamed BoudraandGitHub 64058722e1 Deploy Hub configurations from the CLI (#2955)
* feat(cli): add Hub configuration deploy

Deploy configuration bytes directly to an explicitly targeted Hub project using only caller-supplied origin and organization API key authority.

* refactor(cli): name Hub deploy contracts

* docs(hub): complete deploy CLI guidance

* docs(hub): link deployed API references

* feat(cli): bundle referenced Hub partials

Send exact referenced prompt-partial content alongside YAML so Hub can resolve local includes while inline-only configurations keep the existing request shape.

* fix(cli): match Hub YAML size semantics

Apply the finalized Hub request limit to decoded YAML length and cover multibyte configuration payloads without changing the wire body.
2026-08-07 09:40:36 +02:00
Mohamed Boudra 3a6d6db9a5 fix(app): anchor image growth during timeline reload
Intrinsic image dimensions can arrive after the timeline has painted, outside the React segment-change path. Let the browser preserve the visible content anchor during that resize instead of exposing a frame before the deferred bottom correction.
2026-08-07 08:26:39 +02:00
Mohamed BoudraandGitHub c352632b74 Launch a terminal instead of a chat from New workspace (#2941)
* feat(app): launch a terminal from the New workspace screen

New workspace only ever started a chat agent. Empty workspaces existed but
were reachable only by submitting an empty composer, which nothing advertised,
and terminals could not be started from this screen at all. People concluded
Paseo did not support terminals.

Adds a launch chip to the meta row, shaped from the same ComboboxTrigger as
its neighbours, that chooses between Chat and a terminal. Terminal profiles
mark where a typed prompt goes with a {{{prompt}}} sentinel in their command
or args, substituted client-side before create_terminal_request, so the daemon
learns nothing new and the protocol is untouched. A profile without the
sentinel takes no input: the composer goes read-only and shows the exact
command, so there is nothing to type wrong.

Prompt forms come from each CLI's own --help. claude, codex, and pi take a
trailing positional; opencode's positional is the project directory, so it
uses --prompt=. Profiles predating the sentinel adopt the right form on read,
keyed on the command's base name rather than the profile id, because profiles
created in settings get generated ids.

* fix(app): stop existing launchers passing the prompt sentinel literally

Adoption made every caller of resolveTerminalProfiles see sentinel-bearing
args, but only New workspace substituted them. Pinned targets and the
workspace terminal menu forwarded profile.args straight through, so launching
Claude from an existing workspace ran `claude "{{{prompt}}}"` with that text as
the prompt.

Every launcher now goes through resolveTerminalProfileLaunch. Surfaces with
nowhere to type pass an empty prompt, which drops the prompt-only arg and
gives back the bare command they had before.

Also from the same review:

- Adoption skipped when args lead with a subcommand. `opencode run` takes a
  positional message rather than --prompt, and `codex login` takes no prompt at
  all. Only the first arg is checked, so `pi --thinking high` still adopts.
- Settings edits the persisted list again, not the adopted view. It writes the
  whole list back on any change, so resolving first baked read-time adoption
  into the user's config the moment they reordered a row.
- A hidden draft no longer names a workspace. Text typed for a prompt-taking
  profile stayed in state after switching to a read-only one and still seeded
  the generated title and branch.
- Windows .cmd escaping now has a round-trip test: a real cmd.exe runs a real
  .cmd shim that echoes its argv, covering metacharacters, an injection
  attempt, and newlines. The existing tests only asserted the command line we
  generate.
- Fixed an ENOTEMPTY flake in the terminal suite. kill() only signals, so the
  shell was still writing into $ZDOTDIR while its directory was being removed.
  Cleanup waits for exit instead.

* fix(app): keep the host and isolation accessible names stable

Wiring the new tooltips also replaced these two accessible names with the
tooltip copy, which broke `sidebar-project-grouping` at the host picker: its
selector is `getByRole("button", { name: "Host", exact: true })` and the button
had become "Choose the host". It timed out on both attempts in CI.

An accessible name identifies a control; a tooltip explains it. Conflating them
renamed a control that other surfaces address by name. The names go back to
what they were and the tooltips keep the "Choose the ..." copy.

* test(server): stop temp-dir teardown failing unrelated terminal tests

The ENOTEMPTY flake survived both earlier attempts. Waiting for the session to
exit shrank the window; retrying the remove did not close it either. The writes
come from processes zsh forks on the way out, which outlive the pty and answer
to nothing the test holds.

Teardown is now best-effort and logs what it left behind. A temp directory we
cannot remove is not a defect in the terminal code under test, and failing on it
reports the race against whichever assertion ran last, which is exactly how this
surfaced: as a flake in an unrelated zsh title test. 10 consecutive runs green.

Also replaces a cast in the Windows .cmd helper with a real narrow, so a change
to ResolvedTerminalCommand fails loudly there. Test files are excluded from
typecheck (tsconfig.server.json), so a cast in one is never checked at all.

* fix(app): keep terminal launch policy in its owning workflows

Profile adoption now parses known global option values before deciding whether argv already contains a positional. Workspace naming is derived inside the terminal workflow so hidden launch-only drafts cannot affect branch or title generation.

* fix(server): preserve cmd shim arguments through both parses

npm command shims forward %*, so metacharacters lose one escape layer before the target process receives argv. Exercise the production node-pty string command line against a forwarding shim, and only tolerate the known ENOTEMPTY teardown race.

* fix(server): stabilize Windows subprocess tests

* test(server): isolate Windows argv recorder output

* test(server): allow native watcher integration to settle
2026-08-07 14:08:22 +08:00
292a9c4c8c docs: add Korean README translation (#2947)
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-08-07 01:18:25 +02:00
b4357139bb fix(claude): show the Fast toggle on Opus 5 (#2939)
The Claude model manifest declares fast-mode support per model, and the
`claude-opus-5` entry was added without `supportsFastMode`. Claude Code
supports fast mode on Opus 5, so the composer and Command Center hid a
toggle the model actually has.

Set the flag. Everything downstream already keys off it:
`claudeManifestModelSupportsFastMode` gates `buildClaudeFeatures`, which
feeds `listFeatures`, the session `features` getter, `setFeature`, and
the `setModel` auto-clear.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 07:14:21 +08:00
Mohamed BoudraandGitHub ef6d7b3b9a docs(hub): document required output contracts (#2948)
* docs(hub): document required output contracts

* docs(hub): link faq to output contract

* docs(hub): document materializable required outputs

Explain that required declarations resolve through registered capabilities, failed deliveries can be retried, and command-line repository comments use the scoped credential path.
2026-08-06 22:44:25 +02:00
Mohamed BoudraandGitHub 1e03e2d120 docs(hub): clarify daemon slug references (#2952)
Hub configuration resolves a normalized friendly slug to an immutable daemon ID. Document that model consistently so enrollment terminology matches hub.yml.
2026-08-06 22:00:06 +02:00
Mohamed BoudraandGitHub 42bfd4e04c Stop terminal activity after interrupted turns (#2942)
* fix(terminal): clear activity when turns are interrupted

Some terminal agents do not emit a stop lifecycle hook after a user interrupt. Treat exact interrupt key frames as cancellation at the terminal input boundary so running state cannot stick, and clear rather than finish to avoid completion attention.

* test(terminal): use platform shell in interruption tests

Let the terminal test fixture select the platform default shell so the interruption cases reach their assertions on Windows.
2026-08-06 16:30:44 +02:00
Mohamed Boudra a9034ec642 fix(app): preserve running tab accessibility
Keep the blue running dot while retaining the progressbar semantics used by assistive technology and browser-level running-state checks.
2026-08-06 16:22:21 +02:00
Mohamed BoudraandGitHub deb5e5c729 docs(hub): document durable workflow routing (#2943)
* docs(hub): document durable workflow routing

* docs(hub): make workflow guide progressive
2026-08-06 16:07:41 +02:00
Mohamed Boudra bb55e5bc0d fix(app): align needs-input alert sizes
Keep the filled needs-input alert at one shared size across sidebar badges, workspace rows, and tab descriptors.
2026-08-06 15:40:57 +02:00
Mohamed Boudra 9c05de6f12 fix(app): emphasize needs-input status indicators
Use a filled amber alert for needs-input while keeping running and review dots visually consistent between workspace rows and tabs.
2026-08-06 15:36:21 +02:00
Mohamed BoudraandGitHub f4e8522050 Show workflows in the subagent track (#2933)
* feat(server): show provider workflows in subagent track

Normalize workflow tasks into the provider-subagent contract so shared clients need no workflow awareness. Persisted nonterminal runs replay as failed because their original runtime can no longer deliver a terminal update.

* fix(server): preserve workflow replay and tool cards

* fix(server): suppress workflow notification cards

* fix(server): replay workflow child timelines

* fix(server): fail tasks when Claude exits

* fix(server): preserve workflow replay parity

* fix(server): fail tasks on Claude restart

* fix(server): hide workflow child prompts on replay

* fix: terminalize workflows on session reload

* fix(server): own workflow notification routing

* feat(server): show workflow results in detail pane

* fix(server): preserve unrestored task notifications

* fix(server): normalize replay task notifications
2026-08-06 21:01:26 +08:00
Mohamed BoudraandGitHub c1f7ed3198 Improve reconnect status feedback (#2931)
* fix(app): debounce reconnect status toast

Keep reconnect transport immediate while delaying the visible status toast so brief outages stay quiet. Scope the toast to the active retained panel, keep its copy and status dot consistent across locales, and add a manual real relay deployment journey that verifies streaming recovery without user action.

* fix(app): isolate manual relay deployment check

Keep the deployment journey out of the provider-wide real test project so it runs only when its local relay prerequisites are explicitly requested. Also keep the relay teardown active when packaged daemon startup or later cleanup fails.

* fix(app): preserve reconnect toast across tabs

Keep reconnect presentation timing at the host level so switching between retained panels does not restart the debounce or flash the toast. Add a browser regression covering a visible outage across retained tab switches.

* fix(app): anchor reconnect debounce to host status

Record host connection status transitions independently of mounted agent panels and preserve the outage start across reconnecting states. Use that host-level timestamp when a visible agent panel appears, with runtime coverage for the transition lifecycle.

* fix(app): reset reconnect toast state per outage

* test(app): build relay journey prerequisites
2026-08-06 13:45:34 +02:00
e99eb7de3d fix(app): keep line breaks and indentation in copied code (#2935)
* fix(app): keep line breaks and indentation in copied code

#2930 strips the pre/code tags from a partly selected code block, which
is right for the Markdown syntax but leaves the code itself going
through Turndown as prose. Turndown collapses whitespace, so two
selected lines arrive as one, the indentation is gone, and
Markdown-significant characters come back escaped.

Divert a partial code selection before that: copy the selected text
verbatim. Fully selected regions are untouched and still produce a
fence, which is what hasSelectedAllContents was already deciding.

The html half needs pre for anything multi-line -- HTML collapses
newlines too, so a rich paste target would lose the lines the same way.
A single line stays undecorated, matching the rule that a partial
selection does not carry its container's formatting.

Two trailing-newline fixes ride along, since both make a paste run code
nobody asked to run. Newlines between rendered lines are their own text
nodes, so a selection that overshoots the end of a line carries one;
strip it. The block's Copy button was handed the raw fence body, which
ends in a newline; hand it the rendered text instead.

* fix(app): strip every trailing blank line from copied code

The Copy button stripped one trailing newline, so a fence whose author
left a blank line before the closing delimiter still put an executable
newline on the clipboard. Pasting that into a terminal runs the last
line, which is the thing the strip existed to prevent.

Both copy paths now share one regex in markup.ts and remove every
trailing break, plus any indentation that followed the last one.

Reported by Greptile on #2935.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): infer code copy format from selection boundary

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-08-06 11:27:38 +00:00
Mohamed BoudraandGitHub 2298e9efdf Fix composer toolbar collapse flicker on tab switch (#2937)
Retained tabs stay mounted under display:none while inactive, which
reports a zero-width onLayout. ControlledAgentControls fed that
straight into its density resolver, so a hidden tab's toolbar
collapsed to its tightest density and painted one frame collapsed
before re-expanding when the tab became visible again.

Extract the zero-width guard that useContainerWidthBelow already had
into a shared readMeasuredWidth helper, and use it in the composer
toolbar's own layout handler so a display:none measurement can never
drive its density.

Also bump the chat outline rail's MIN_PANEL_WIDTH breakpoint (818 ->
918) so the rail disappears earlier under a narrower panel, and widen
the chat-outline e2e spec's viewport so its panel keeps healthy margin
over the new threshold.
2026-08-06 18:37:38 +08:00