Default-model chats retain their effective model in saved runtime info, and the persistence handle retains the session ID needed for credential attribution. Pass both through the provider client so closed chats resolve usage after restart.
Stored agents have no loaded session after a daemon restart, so session-owned reference lookup returned null. Resolve through provider clients using stored config, and request the report when a chat opens even during a turn.
OpenCode v2's edit tool takes `path` instead of `filePath`, so the
OpenCode edit schema missed it and the shared fallback dropped the
camelCase oldString/newString. Completed edits reached the timeline as
{type: "edit", filePath} with nothing to diff.
Closes#5607
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Resolve retained variants against the destination model before applying a model change. Publish the effective thinking choice and refresh the provider resume handle immediately so a reload cannot restore the old selection.
Claude Code 2.1.284 introduces Sonnet 5.5 with a native 1M context
window, medium default effort, and adaptive thinking that cannot be
turned off, so it takes the same shape as Opus 5.5 in the manifest.
Sonnet 5.5 rejects `thinking: {"type": "disabled"}` with a 400, so its
entry omits supportsThinkingDisabled and its thinking options have no
off. It also takes the Sonnet family tagline, which moves Sonnet 5 to
Previous release.
The catalog gates the entry on Claude Code 2.1.284, the version that
recognizes the model, so older installs keep their current catalog.
The existing major-minor runtime matching already resolves dashed
gateway IDs such as us.anthropic.claude-sonnet-5-5-20260928-v1:0 to
the new entry without collapsing them onto Sonnet 5.
Removing a plugin provider closed its runtime and published a runtime
failure for every open session, so an idle session whose turn had already
completed gained a "Provider connection closed" error. Only a turn that
is still in flight is interrupted now; an idle session goes stale and its
next prompt reopens it from persistence.
Closes#5535
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
A provider that extends Codex runs through the registry's client wrapper,
which forwarded session listing and import but not native archive or
unarchive. Archiving such an agent left its Codex thread unarchived, so
the session stayed in Import session and could be imported again. The
wrapper now forwards both, the same as stock Codex.
Closes#5454
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Show a host's new version after its daemon restarts
The app wrote a host's server info into the session store only when a
client was first mounted, and a reconnect reuses the same client, so
after a daemon upgrade or restart the host page and version checks kept
the old version until the app restarted.
The host runtime now projects the client's current server info into the
session store on every connection change, keeping the last known value
while disconnected. The React mount effect that duplicated this write is
removed.
Closes#5236
Co-authored-by: Leonid <127580858+bashrusakh@users.noreply.github.com>
* Keep a caller-owned test daemon home across startup retries
A failed start removed the home even with cleanup disabled, so a retry on a
taken port came back with a new serverId. The host restart spec now drives a
restartable host daemon helper that owns its port, home, and teardown.
* Remove only the test daemon dirs a failed start created
A failed attempt now removes the temp home and static dirs it created and
leaves caller-supplied ones, so cleanup: false no longer leaks per retry.
* Use function declarations in the test daemon helpers
* test(app): cover host versions across daemon disconnect and reconnect
The host settings badge retains its last known version during a transport failure; the help menu should show the replacement version after reconnect without changing another host.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Leonid <127580858+bashrusakh@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* Report a background send_agent_prompt's started turn as running
The background branch read the target's status right after dispatch,
before the provider acknowledged the turn, so an idle target read as
idle. Wait for the run to start first, as create_agent and the app's
send path already do.
* Check the caller still gets the finish notification after a background send
* Acknowledge the held turn start explicitly in the send_agent_prompt test
* Cover immediate completion after background turn start
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
A provider that extends codex and sets CODEX_HOME in its env runs Codex
against that home, but the daemon listed, expanded, and fell back to
skills from its own CODEX_HOME or ~/.codex. The / menu offered another
home's prompts and never the provider's own, and invoking one of the
provider's prompts sent the raw slash text to Codex.
Resolve the Codex home from the same environment the app-server is
launched with, and give it to the session.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(server): run internal plugins in-process through the plugin runtime
* fix(server): harden internal plugin startup and boundary checks
* feat(usage): add plugin usage sources and Codex discovery
* fix(usage): share SDK helpers and bound source cache
* feat(usage): migrate usage fetchers to plugin sources
Serve the legacy usage RPC from discovered source reports so older apps retain the same host usage list while new clients use the source registry. Keep credential reads and normalization inside each source.
* refactor(usage): shape sources as fetch functions
Keep the plugin usage API limited to stable report and presentation helpers. Source-local parsers and timeout handling preserve credential and response parity while the cases move alongside each source.
* feat(usage): resolve reports from active session accounts
Resolve source inputs at fetch time so model and credential changes select the account each session spends. Keep the legacy discovered-account RPC for existing clients. Sync OpenCode model events outside foreground turns so usage follows model switches.
* fix usage source discovery and session report parity
* test adapter usage references through daemon RPC
* name usage reference contract across server
* refactor(usage): own usage RPCs and validate session auth
* feat(omp): resolve session usage from active OAuth credential
* fix(omp): match usage source input and valid credential fallback
* test(omp): use upstream credential database schema
* test(omp): verify live session usage references
* fix(usage): declare YAML parser and consolidate credential validation
* feat(app): show usage sources in the composer and a Usage screen
Replace the provider-matched usage in the context ring tooltip with a
composer pill driven by agent.get_usage_report, add a sidebar Usage
screen over usage.list_reports grouped by host, and move the host
settings Usage page to usage.list_reports. Hosts without
features.usageSources show an update message. Source icons come from
the report's catalog SVG. The app no longer calls provider.usage.list.
* fix(usage): declare the Paseo requirement on internal usage-source plugins
A manifest without requirements.paseo targets Paseo before 0.8, so the daemon
refused to start eight of the nine usage sources.
* fix(app): keep host connection statuses live under the React Compiler
The hook read a store version only as a memo trigger (void version); the
compiler dropped it, so pages that mounted before a host came online kept
showing it as connecting.
* fix(app): place the context ring slot before the usage pill
The ring reserves its slot before it has data. With the pill first, the empty
slot sat between the pill and the mic; now it falls in the flexible space.
* refactor(app): extract the settings page layout into PageLayout
Desktop puts the title in the page above a centered column; compact moves it
into a back header. Settings renders the same pixels through it.
* feat(app): lay out the Usage screen like a settings page
The title moves from the header bar into the page on desktop; compact gets the
settings back header, which returns home when the screen was opened by URL.
* refactor(app): drop the unused actions slot from PageLayout
* test(plugin): classify the usage source SDK entry
* fix(usage): preserve platform test contracts
* fix(settings): preserve page title test contract
* feat(usage): identify and cache reports by account ID
Keep report identity stable across input routes and token rotation, and make selective refresh operate on known account reports.
* feat(app): read usage reports through report IDs
Share report data between agent pills and host views while preserving the existing popover interaction for the next UI phase.
* fix(usage): keep account identity aligned with credentials
Read labels from local token claims and use one credential lookup for usage fetches and account identification. Bound cached profile identities across token rotation.
* fix(usage): isolate failure IDs and prune expired reports
Failure reports live outside the account-key namespace, and cache writes discard entries past the five-minute TTL.
* refactor(app): share the compact time-ago prose formatter
Move the tab tooltip's "3m" -> "3m ago" wording into utils/time so the
usage card can reuse it.
* feat(usage): show when each report was fetched
The usage card reads fetchedAt as "Updated 3m ago": a tooltip on the
card header where hover exists (web, desktop), printed in the card
footer on native and compact. Shared by the pill popover and the Usage
screen.
* feat(usage): open the pill popover from cache and refresh on demand
Opening the popover no longer forces a vendor fetch; it shows the cached
report. A Refresh control force-refreshes only this report
(usage.list_reports with its single ID and forceRefresh), shows
Refreshing... while it runs, keeps the previous report visible, and says
"Unable to refresh usage" if the request fails.
* feat(app): let ToolbarButton show a tooltip other than its label
An icon action can now say a status in its tooltip ("Updated 3m ago")
while keeping its label for accessibility.
* feat(usage): refresh a report from its card header
Refresh is an icon button at the right edge of every usage card header,
in the pill popover and on the Usage screen. It force-refreshes only that
report, writes the result to the report's cache and its host list, shows
a spinner and disables itself while pending, and prints "Unable to
refresh usage" in the card on failure. Its tooltip carries the
freshness; without hover the freshness stays in the card footer. The
status indicator sits just left of the button, and the popover's
separate refresh row is gone.
* fix(server): include internal plugin assets in built output
* feat(server): built-in plugins in /plugins (#5544)
* feat(server): load compiled built-in Codex plugin
* test(server): guard built-in plugin loading and installs
* test(server): verify built-in plugins from dist in CI
* refactor(server): migrate remaining usage sources to built-in plugins
* test(server): guard all packaged usage source registrations
* docs: describe usage validation across plugin hosts
* test(ci): include built-in plugins in routing contract
* test(server): import moved Codex usage source
* fix(server): retain ACP updates received with session creation
Rewound Codex forks receive the same provider and runtime MCP config as thread start and resume. Covers paginated and legacy rewind paths.\n\nCloses #4542\nCloses #3205\n\nCo-authored-by: zbaibg <zbai29@wisc.edu>
* fix(opencode): reconnect v2 sessions after helper exits
A helper exit aborted the session lifetime, so every later prompt failed even though OpenCode can serve the same native session from another helper. Keep the session open, fail the active turn, and acquire and configure the current helper on the next OpenCode operation.
* fix(opencode): forget exited v2 helper generation
A dead shared helper could remain selected as the current generation. Clear that selection at exit so the first lazy reconnect acquires a live helper.
* feat(auth): authenticate daemon clients through hello
Give local CLI and desktop clients a per-run credential while remote clients send the host password inside the protocol. Keep relay passwordless admission for this mobile release so existing saved hosts keep working until the follow-up enforcement release.
Move password recovery for saved hosts into host settings and keep pairing credentials in the add flow. Include isolated web QA evidence for both flows.
* fix(auth): preserve Hub bootstrap and local credential expectations
Hub-owned sockets already carry admission; keep their hello handling synchronous through session bootstrap while resolving credentials asynchronously for ordinary sockets. Keep the resolver out of lightweight configuration imports.
Update CLI expectations for same-home credentials, let a rejected connection yield to another usable connection, and clear pairing secrets when add-flow targets change.
* fix(pairing): retain passwords across edits to the same target
Track the last valid pairing host while a URI is edited. This preserves an entered password for whitespace and partial edits but clears it when a different host becomes the target.
* test(auth): align local credential checks across platforms
Expect the CLI to authenticate through the credential in its selected home, and verify POSIX file modes only where Node exposes them.
* feat(app): move host password into a settings row with a modal
The host page rendered an always-visible password form above Name on every
host. Replace it with a Password row under a Security section below
Appearance; tapping it opens a modal with Save and, when a password is saved,
Clear. The connection-error callout keeps its guidance and gains a Set
password button that opens the same modal. Drop the unused
pairing.hostPassword body/reopen/saveError strings.
* chore: remove committed QA screenshots
QA evidence stays out of the repository; the PR body describes it in words.
* feat(app): keep the host password out of the host page
The saved password is connection data, entered only when a host is added. A
Password setting on the host page reads as the daemon's own password, so the
row, modal, and callout button are gone. A rejected host shows the daemon's
reason with guidance to remove the host and add it again with the password.
useHostMutations no longer exposes setHostPassword; relay imports still set it
inside the runtime.
* feat(app): show host connection errors in an error alert
HostConnectionError renders the existing Alert with the connection error as
its title. Auth rejections add the re-add guidance as the description; other
errors show the title only. The Overview and Connections pages share it.
* style(app): soften alert borders to half-opacity accent
* feat(app): size alerts on the button scale
Alert takes a size prop (xs/sm/md/lg, default md) whose geometry lives next to
the button's in control-geometry: one font size for title and description, the
button icon size centered on the first text line, button padding and radius,
and a one-line alert as tall as the button of the same size. Consumers pick
sm among settings rows, cards, panels, and modals, and md when the alert is
the whole page or sheet state.
* style(app): roomier, rounder alerts with a tight title block
Alert sm padding returns to the original 12/16 and every size is at least
borderRadius.xl. Title and description sit a small gap apart with no line
height override. The icon slot carries a zero-width text strut in the alert's
font so the icon centers on the first line for titled, title-only, and
description-only alerts.
* refactor(app): align the alert icon with a centered lead row
Drop the zero-width strut. The icon and the alert's first text (title, else
description) share one row centered on each other; the description and actions
sit below, indented by the icon slot width plus gap so they start at the lead
text's left edge.
* refactor(app): drop the unused Alert icon override
* refactor(server): give Pi extension handling a registry
* feat(server): map Pi task extensions to Paseo task lists
* feat(server): map Pi subagent extensions to the Subagents track
* refactor(server): hydrate Pi child sessions asynchronously through the extension host
Child session JSONL reads blocked the daemon during live events and replay. The extension host now prepares immediate upserts and bounded async timeline hydration; replay awaits it, and closed sessions discard it. Preserve claimed notification text so completion details remain visible in the parent conversation.
* feat(server): present Pi question extensions as one question form
Keep the extension questionnaire in one Paseo permission while Pi walks its RPC dialogs. The host defers an early dialog until the form is answered, then replies to each dialog using the collected choices.
* fix(server): satisfy Pi extension start hook contract
The server build checks the session interface more strictly than the fast typecheck. Return the optional permission explicitly so the adapter satisfies the published extension contract.
* docs: document Pi extension adapters
* chore(server): format Pi subagent captures
These real RPC fixtures were added before the formatter ran over the captured JSON. Normalize whitespace so repository format checks pass without changing the captured data.
* test(server): resolve Pi child fixtures as local paths on Windows
File URL pathnames retain a leading slash before a Windows drive letter. Convert fixture URLs to filesystem paths and escape those paths when inserting them into captured JSON, so child timeline assertions exercise the real file on every CI platform.
* refactor(server): remove Oh My Pi handling from the Pi provider
* fix(server): keep Pi extension adapter failures out of core session handling
* fix(server): bound Pi child-session hydration during history replay
* fix(opencode): support v2 while preserving v1 provider behavior
* fix(opencode): target v2.0.7, keeping v1 support
OpenCode 2.0.4 removed the /api/health endpoint and the plugin
await-activation endpoint, and moved form reply/cancel under the session
namespace. The v2 adapter was pinned to the 2.0.3 SDK, so any 2.0.4+
binary failed at helper-server startup with an opaque HTTP 404.
- Pin @opencode/client to 2.0.7 and require v2 >= 2.0.4 at runtime
selection, with a clear error for older binaries.
- Use server.info for helper-server readiness.
- Move form reply/cancel/list to session.form and send the permission
decision field instead of reply.
- Replace plugin.awaitActivation with a plugin.list readiness wait. A cold
location registers its config-derived commands, skills, and providers
asynchronously; an empty inventory means it is still warming.
- Send session.command.name, which replaced command, and drop the removed
SkillInfo.slash field.
- Update the v2 test harness, unit tests, dependency lockfile, CI matrix,
and provider docs to 2.0.7.
* fix(opencode): target 2.0.10 and address v2 review findings
* test(opencode): colocate v2 suites with their modules
Greptile's review flagged that the v2 tests lived in top-level
opencode-agent.*.test.ts files and imported the readiness helper, v2
client, harness, and API types from a distance. Move each v2 suite beside
the module it describes and leave the top-level files to the v1 public
path:
- opencode/v2/agent.test.ts: session lifecycle, structured output,
resume, reconciliation, interrupt/replacement, helper isolation.
- opencode/v2/readiness.test.ts: location readiness deadline behavior.
- opencode/v2/permissions.test.ts: question and approval routing.
- opencode/v2/configuration.test.ts: permissionRules, feature flags, and
helper-isolation decisions, exercised directly through the module.
- opencode/v2/mapping.test.ts: model and mode normalization.
- opencode/runtime-client.test.ts: runtime version selection.
The v1 recovery, permission-action, and catalog-deadline suites stay in
place. Test counts are preserved, with deeper per-module coverage for
configuration.
* test(opencode): split v2 local E2E by behavior and add harness helpers
The re-review flagged the v2 scenarios in opencode-bridge.local.e2e.test.ts
for inline event filtering, permission-kind branching, and polling in test
bodies. Move stream, permission, and subagent coordination behind helpers
in opencode/test-utils/v2-local-e2e-helpers.ts, and split the session
scenarios into opencode-v2.local.e2e.test.ts so each file covers one
behavior:
- opencode-bridge.local.e2e.test.ts: process sharing, shell.env scoping,
the native tool bridge, and runtime version selection.
- opencode-v2.local.e2e.test.ts: session lifecycle, structured output,
streaming, approvals, MCP preapproval, stop/replacement, rewind and
subagents, commands, and skills/attachments.
Each helper waits on or answers the provider directly, so the tests state
domain intent and assert the collected result instead of polling or
branching inline. The CI-selected "versioned runtime" case stays in the
bridge file, and the live approvals and stop/replacement scenarios were
re-run green through the new helpers.
* fix(ci): preserve required server checks across OpenCode versions
Run v1 and v2 integration coverage sequentially so branch protection keeps its existing static check names.
* fix(agents): load startup history before registering sessions
A failed history stream while adding a runtime notice must close the unregistered session instead of leaving an initializing agent behind.
* fix(agents): limit startup history before buffering it
Apply the existing timeline content limits to each incoming history event so raw cumulative tool output is not retained until registration completes.
* fix(opencode): resolve Windows executables from platform packages
Recent v1 npm releases place opencode.exe in the architecture dependency rather than the CLI bin directory. Resolve that dependency from the CLI package while retaining support for the older layout.
* refactor(opencode): name public runtime option types
* test(opencode): let the spec own both runtime versions
Install each version in an isolated temporary directory and select it through provider configuration. Restore the CI workflow unchanged so version coverage is owned by the spec rather than workflow orchestration.
* test(opencode): launch native Windows binaries in isolated specs
Avoid npm command wrappers retaining locked executable files after helper shutdown during temporary installation cleanup.
* fix(opencode): preserve the legacy path when adding v2 support
Fall back to the existing client when version probing is inconclusive. Load v2 code and its bridge plugin only after positive v2 selection, keep v1 sessions undecorated, and restore original v1 discovery errors.
* fix(opencode): retry inconclusive version selection
A failed or unrecognized version probe previously cached the legacy fallback, pinning an upgraded binary to the v1 API until client recreation. Retry detection on the next operation while keeping the legacy path available.
* fix(opencode): release legacy runtime after inconclusive probe
Retryable version detection can clear the selected client after falling back to v1. Track whether the legacy client was selected so shutdown still releases its server without touching an unused v1 manager during a v2-only run.
* test(opencode): assert full runtime notice history
* fix(opencode): restore notice and retry behavior after rebase
---------
Co-authored-by: Jonathan karras <6307688+karrots@users.noreply.github.com>
Co-authored-by: Dhruv Singal <dhruvsingalabc@gmail.com>
A provider that extends codex with its own OPENAI_BASE_URL starts its
threads under its own Codex model provider id. Codex's thread/list returns
only the model provider its config selects unless the request names
others, and the import scan named none, so those sessions were missing
from the import list. After switching the default provider in the Codex
config, stock Codex listed them instead and imported them onto itself.
Ask Codex for the custom provider's own threads when listing its
importable sessions. Stock Codex keeps its current listing.
Closes#5381
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Opening an archived ACP agent whose worktree was removed spawned the
provider in the missing directory. The session spawn had no listener for
the child's error event, so Node's ENOENT became an uncaught exception
and the daemon restarted, then crashed again when the client retried.
The session spawn now rejects initialization on a spawn error, the same
way the provider probe already did, and both share one helper.
Closes#5435
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: qinkangdeid <qinkangdeidd@gmail.com>
A provider that extends claude and sets CLAUDE_CONFIG_DIR in its env
writes transcripts and settings.json under that directory, but the
daemon looked them up under its own CLAUDE_CONFIG_DIR or ~/.claude.
Agents on that provider reopened with an empty timeline after a daemon
restart, their sessions were missing from the import list, and models
from its settings.json were missing from the catalog.
Resolve the config directory from the same environment the provider is
launched with, and drop the test-only configDir client option.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
OpenCode adds text to user messages on its own behalf (continue prompts after
compaction or a subtask, MCP resource contents, plugin prompts) and flags that
text `synthetic: true`. Its own UI hides it. Paseo built user bubbles from every
text part, so reopening an OpenCode chat showed those messages as if the user
had typed them, and appended synthetic text to the user's own message.
Closes#5425
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Pi's navigateTree without a summary only moves the session's leaf in
memory and appends nothing to the session file. Pi reopens a session at
the file's last entry, so after a daemon restart the resumed chat showed
the messages the rewind had removed.
After navigating, the Paseo extension now appends a paseo_rewind custom
entry on the new branch. Custom entries carry no model context, and the
file's last entry now sits on the rewound branch, so Pi reopens there.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* List a generic ACP agent's slash commands in a new agent's / menu
Generic ACP agents advertise slash commands with available_commands_update
after session/new. The new-agent / menu opens a short-lived session and asks
for its commands right after session/new, so it always got an empty list,
which the app keeps for the draft. Wait for the first batch, bounded by the
existing initial-commands timeout, as the Cursor, Kiro, and TRAE clients do.
Closes#4759
* Keep the generic ACP command test independent of scheduling
* Wait for agent state writes before removing MCP test temp dirs
The blocking send_agent_prompt timeout test removed its temp dir while
AgentManager background tasks and AgentStorage writes were still in
flight, which intermittently failed cleanup with ENOTEMPTY on Linux CI.
Flush both before removing the dir in the tests that use real storage.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Notify a caller once when it prompts a child that is still running
create_agent and a background send_agent_prompt each armed their own
finish notification for the same child and caller. When the second
prompt replaces the child's running turn, the child never goes idle in
between, so its one finish fired both and the caller got the same
notification twice. Arming again for a child and caller now replaces
the earlier notification.
Closes#5397
* Declare the notification test helper as a function
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Rewind a Pi chat to the message you pick after an earlier rewind
After a rewind, Pi's session file keeps the abandoned branch. Paseo
captured user entry ids from the whole file but replayed history from
the current branch, pairing them by position. Each abandoned user
message shifted later rows onto an earlier entry id, so rewinding a
replayed row went back further than the selected message.
Capture user entries from the same compaction-aware branch Pi replays,
so the nth capture is the nth replayed user message.
* Assert Pi entry captures as data with named test helpers
* Name the fields of Pi user entry test fixtures
* Rewind a Pi row whose entry compaction summarized away
The capture fed both replay pairing and the rewind target lookup. Narrowing
it to the context branch broke rewinding a live row from before /compact,
because that row's entry is off the branch. The extension now reports the
session's user entries for the lookup apart from the branch entries replay
pairs with.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(omp): preserve described select options
* test(omp): assert exact option arrays instead of toMatchObject
toMatchObject ignores extra/missing keys, so the described-select and
malformed-metadata tests passed even with option-index misalignment or
an unfiltered whitespace-only description. Switched to toStrictEqual
on the options array so both are actually caught.
* fix(server): notify the caller when a blocking send_agent_prompt outlasts its wait
An agent-scoped send_agent_prompt with background: false only armed the
finish notification for background sends. When the 30s wait timed out,
the tool returned "still running" and nothing was armed, so the child's
result never reached the caller. The wait now reports whether it timed
out, and a timed-out wait on a still-running agent arms the same
notification a background send gets and says so in guidance. A blocking
send that finishes inside the wait still returns its result without a
notification.
* refactor(server): declare the finish-notification helper as a function
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
A Pi agent created without a model took the first entry of Pi's model
list, which ignores defaultProvider/defaultModel in Pi's settings. The Pi
catalog now marks the model a fresh Pi session reports in get_state as
the default, so the daemon's default-model fallback picks it.
Closes#4918
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Reconnect OpenCode agents to the new server after the old one exits
When the OpenCode server process exits, Paseo starts the next one on a
new port, but existing sessions kept their client bound to the old port
and failed every turn with ECONNREFUSED until the agent was reloaded.
A session now marks its connection dead when the server exits and
connects to the current server before its next turn, rewind, history
read, mode or command lookup.
Closes#5307
* Settle the exited OpenCode server's events before reconnecting
A queued exit from the old server could otherwise fail a turn started on
the new one.
* Re-add a reconnected OpenCode session's MCP servers on the new server
reconnect() reset mcpConfigured but left mcpSetupPromise holding the
first server's resolved setup, so the next turn awaited it and never
called mcp.add on the new server. Track MCP setup with one field that
reconnect clears.
* Re-run CI after an unrelated macOS file-observation flake
* Re-run CI after an unrelated website WebKit timeout
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
readPersistedConfig, used by paseo daemon config, status, pair, and
set-password, parsed and validated config.json without the error
wrapping loadPersistedConfig applies, so an invalid file surfaced as a
bare SyntaxError or ZodError. Both readers now share one parse step
that names the file and, for schema errors, the field.
Closes#5328
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(daemon): report the local daemon's server id when a password is set
Since #4575, `paseo daemon status` only learned the server id from an
authenticated connection. The desktop app runs that command without the
daemon password, so a password-protected desktop daemon reported no server
id, the app treated it as remote, and hid Open in editor.
The worker now announces its server id with readiness and the supervisor
publishes it in the instance record next to the listen address, so status
reports the running daemon's identity from local state.
Closes#5300
* fix(cli): omit serverId from status until a daemon publishes one
* chore: rerun CI after macOS file observation flake
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(server): discover ANTHROPIC_DEFAULT_FABLE_MODEL from Claude settings
* Retrigger CI after website dev server startup timeout
* test(server): assert the built-in Fable entry through the fetched catalog
---------
Co-authored-by: Noah <noahguerin20@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Reject missing and non-directory paths for local create_workspace
The create_workspace MCP tool with isolation local persisted a workspace
and project for a path that does not exist or is a regular file. The
app's workspace create request already rejects those paths with
"Directory not found"; the tool now applies the same check before
provisioning.
* Make the local create_workspace e2e assertions path-format independent
* Rethrow filesystem errors other than a missing path in local create_workspace
* Express the local create_workspace e2e test through daemon and workspace helpers
* Clean up the offline MCP daemon when its startup fails
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(server): keep original file names for chat uploads
Uploads replaced every character outside ASCII letters, digits, dot,
dash, and space with an underscore, so CJK, Korean, accented, and
parenthesized names reached disk and the agent as underscores.
Replace only characters no file system accepts in a file name, and
shorten names past 255 bytes while keeping the extension.
* test(server): make upload separator test independent of the daemon OS
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Load config.json saved with a UTF-8 byte order mark
Windows Notepad saves UTF-8 with a BOM. Both config readers passed the
text straight to JSON.parse, so the daemon refused to start and CLI
config commands failed with "Unexpected token".
Closes#4683
* Strip the config BOM with an anchored replace
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
A supervisor killed between creating paseo.pid and writing its contents
leaves an empty file. Every later start, status, and stop then failed
with "Cannot read daemon state", so the daemon stayed down until someone
deleted the file by hand.
An empty lock names no owner, so after the existing read retries it now
reads as no lock, and acquiring the lock removes it before creating a new
one. A lock with unparseable contents still refuses, since it may belong
to a supervisor this version cannot read.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Start the daemon when the schedules directory holds a file that is not a schedule
One unreadable .json file in the schedules directory rejected
ScheduleStore.list(), which ScheduleService.start() awaits during daemon
bootstrap, so the daemon never started. The store now skips a file it
cannot read as a schedule and logs its path once, the way agent storage
treats an invalid agent record.
Closes#4745
* Skip only schedule files with invalid content
A schedule file that cannot be read now fails the listing as before,
instead of being skipped as if it were not a schedule. An invalid file
is reported again when it reappears after being removed or fixed.
* Retrigger CI after an unrelated macOS observation-test flake
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* test(server): wait for repository observation before asserting ref refresh
The first checkout facts read can begin before the metadata watcher registers. The test sent an event through an optional watcher lookup at that point and silently dropped it, so a slow setup left only the initial snapshot. Hold the first read to reproduce the ordering and wait for observation setup and the initial refresh before emitting the ref event.
* test(server): isolate ref refresh from background fetch
An asynchronous background fetch could also schedule a structural refresh or populate the known-ref set. Give this test an empty fetch result and wait for that fetch to complete so the second facts read proves the injected watcher event caused it.
* test(server): require the registered repository watcher in the assertion
* Keep the plugin IPC channel open until provider closes report
closeProviderConnection removed the connection from the registry before
awaiting the provider's close, so shutdown's sweep could not see a close
already in flight. With a provider whose close does real work, the
subprocess finished shutting down and called process.disconnect() while
that close was still running, and the trailing provider.closed send hit a
closed channel and took the subprocess down with an unhandled
ERR_IPC_CHANNEL_CLOSED.
Keep the connection registered until its close has reported, and share
one close promise per connection, so shutdown waits for an in-flight
close instead of disconnecting underneath it. The close now also owns
reporting its own outcome, so it never rejects into teardown.
* Harden the reload teardown test for Windows and clean shutdown
Normalize line endings before patching the example provider, so the
fixture also builds from a CRLF checkout. Wait for the in-flight turn and
archive the agent before stopping the daemon, so teardown no longer
rejects a pending provider request into an unhandled rejection.
* Verify plugin reload teardown and reject sends during close
* Synchronize plugin reload teardown test with close lifecycle
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* fix(server): let Hub title created workspaces
Agent creation can provision a workspace under hub.execute, but a separate title request required workspace.manage. Permit the same execution authority for title changes and their responses, and lock the Hub request contract with socket and permission tests.
* test(server): keep Hub title repro free of conditional flow
* fix(server): stop re-deriving path state for every scanned directory
Directory suggestion scans were CPU-bound in path bookkeeping rather than
in reading the tree. A 20,000-entry scan of a 197,000-directory home took
6.9s, of which readdir accounted for 50ms; the rest went to repeated path
normalization.
Three redundant passes are removed:
- Discovery re-derived Git-ignore state by walking every ancestor of every
entry. Traversal only descends through entries that already passed that
check, so only the entry itself can be newly ignored.
- Discovery re-derived root containment for every child. Only a symlink can
resolve outside the tree being walked; every other child is its parent's
path plus a name, and the parent was already proved inside.
- The directory listing cache swept itself for expired keys on every miss
once it reached its cap, an O(cache size) pass per directory read. Reads
already reject stale entries by expiry and by directory metadata.
The same scan now takes 1.1s. The new test expresses the budget against a
plain readdir walk of the same tree, so it means the same thing on CI as on
a developer machine: the ratio was 12x and is now under 3x.
Refs #4879
* test(server): skip the scan-cost budget on Windows
The tree it needs is deeper than MAX_PATH allows, and the directory
listing cache it measures is disabled on Windows because directory
metadata cannot validate it there.
* test(server): count containment derivations instead of timing the scan
The previous budget compared wall-clock scan time against a readdir walk
of the same tree. packages/server/AGENTS.md requires tests to be
deterministic, and a ratio between an I/O-bound walk and a CPU-bound scan
moves with filesystem caching and runner load.
isPathInsideRoot now counts its calls behind start/stopPathContainmentMetrics,
matching the existing git command metrics. Scanning a symlink-free tree of
320 entries derived containment 1,760 times before this branch and derives
it none now, which is the defect stated exactly rather than inferred from a
stopwatch.
A second test covers the other direction: a symlink that leaves the root is
still checked, and still excluded from results.
* fix(server): keep the Git-ignore ancestor walk for symlinked entries
A symlink resolves to a path whose ancestors were never checked during
traversal, so a link into an ignored directory was discovered. Named
children keep the single-entry check.
* test(server): declare the scan helper as a function
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(server): back off degraded git polling on an unchanged workspace
When the recursive watcher declines a repository (over 250,000 entries on
macOS/Windows, or any watcher setup failure), WorkspaceGit falls back to
polling. That fallback rescheduled itself every 5 seconds for as long as the
workspace existed, with no client or agent involved. On a repository large
enough to defeat the watcher each tick is an expensive `git status`, so the
daemon held about a core indefinitely.
Double the gap after every tick whose workspace snapshot came back identical,
up to a 60-second ceiling, and reset to the 5-second base on the first change.
Both degraded loops — working tree and repository metadata — share the rule.
Explicit reads still force a refresh, so the ceiling only bounds how long an
edit made outside Paseo stays undiscovered.
Fixes#5102
* test(server): cover repository-metadata degraded backoff, document failed-tick semantics
* test(server): drive the recovery-refresh assertion off the fake clock, not waitFor
* test(server): declare metadata poll counter as a function
Custom messages may precede the prompt user message during an active OMP turn. Treating them as completion terminalized the turn while the provider continued working.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Expose Claude Agent SDK's documented extraArgs map through providerOptions so Claude-specific flags such as --chrome reach the CLI.
Document boolean and valued flags, link the upstream contract, and validate through schema tests, the public session interface, and a real Claude launch.
* fix(server): create new branches without an upstream
A new branch Paseo creates in place ran `git checkout -b <new> <base>`,
which lets git decide the upstream. Git sets `origin/<base>` whenever the
base is a remote-tracking ref (what the default-branch resolver returns
when the local default branch is absent) or when `branch.autoSetupMerge`
is `inherit`. Every later push reads that upstream, so the work lands on
the default branch: `git push -u origin <branch>` under
`push.default=tracking` pushes to `main`, and Paseo's own push resolves
`branch.<name>.merge` and pushes `HEAD:refs/heads/main`.
Pass `--no-track`, the same promise the worktree path already makes with
`git worktree add -b <branch> --no-track <base>`.
* test(server): read the upstream with for-each-ref so a git failure cannot read as absent
* test(server): use a function declaration for the clone fixture git runner
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(opencode): set permission rules on the OpenCode session
OpenCode stores permission rules on the session and its prompt_async
request schema has no permission field, so rules built from provider
options and Hub tool grants were dropped on every turn: an agent
configured with external_directory: allow still asked before reading
outside its worktree.
Send the rules where OpenCode reads them — on session.create, and on
session.update when resuming a session Paseo did not just create — and
stop attaching them to prompts. A resume that cannot apply its rules now
fails instead of running under a more permissive policy than configured.
The real-server test is taken from Gurvan Campion's #5295, which reached
the same diagnosis independently.
Co-authored-by: Gurvan Campion <32047986+gurvancampion@users.noreply.github.com>
* test(opencode): use function declarations for the permission test helpers
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Gurvan Campion <32047986+gurvancampion@users.noreply.github.com>
Observe provider response rejection before transport acceptance completes, while preserving the original promise and errors returned to callers. Cover early failure, send rejection, closure, cancellation, and successful retries through the public provider interface.
Closes#4781.
Refresh Git exclusions when ignored directories appear after subscription and bound Linux and native classification work. Preserve tracked-file notifications, ignore-update barriers, and watcher recovery.
Fixes#4558. Supersedes #4808 by @Marcus172; this change incorporates six commits originally authored by Marek Rom and adds verified follow-up fixes. The independent session-context leak is fixed by #5208. Combined isolated daemon verification passed.
Co-authored-by: Marek Rom <marek.marcus.rom@gmail.com>
Task subagent frames stream on the same query as the conversation, and
rememberTranscriptProgress recorded any assistant frame as the current turn's
rewind anchor. When a turn's last assistant frame came from a subagent, the
anchor held a uuid that lives on the subagent's sidechain rather than on the
session's own chain, so forkSession rejected it with "Message <uuid> not found
in session" and every later rewind in that conversation failed.
Skip frames that carry a parent_tool_use_id when recording anchors. The
persisted history path already skips sidechain entries; the live stream now
applies the same rule.
Closes#5284
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>