22 Commits
Author SHA1 Message Date
b12daabe35 perf(app): row-grained replica cache persistence (#3755)
* feat(hub): guide starter setup (#3651)

* feat(hub): guide setup with provider IDs

* fix(hub): preserve login setup state

* feat(hub): select starter agent runtime

Use the connected daemon's provider snapshot so generated Hub bundles only
contain complete provider, model, and mode combinations.

* fix(hub): stage starter runtime choices

Keep providers with valid modes selectable when the daemon has no default mode, so users explicitly choose the complete runtime.

* fix(hub): scope runtime choices to workspace

Provider capabilities can vary by repository, so request the snapshot for the bundle directory that guided setup will deploy.

* fix(server): stop short start gates from undercutting provider startup budgets (#3621)

* fix(server): stop short start gates from undercutting provider startup budgets

Agent creation still failed on contended cold starts after #3578, because two
shorter gates on the same path fire before the event-stream readiness budget can
apply. A traced creation reached session.create twelve seconds in, past the 10s
session.create gate, and the agent was then killed by a bare "timeout" at fifteen
seconds from AGENT_RUN_START_TIMEOUT_MS while the SSE connect was still running.

A run-start bound of 15s wraps a path that is deliberately allowed 30s, so the
outer gate always wins and the inner budget is unreachable.

Give session.create the shared OPENCODE_SERVER_STARTUP_TIMEOUT_MS budget, since
creating the first session for a directory is part of OpenCode coming up, and
derive its error message from the constant instead of hardcoding "10s". Raise
AGENT_RUN_START_TIMEOUT_MS to 60s with a comment recording that it must stay
above the slowest provider startup budget; it stays a literal because that module
is provider-agnostic.

Related to #3571.

* test(server): drive the run start budget through a real AgentManager

The run start boundary tests replaced AgentManager.waitForAgentRunStart with a
bare timer, so they never exercised the production run-state transition or the
agent_state subscription the real wait depends on. That path could regress while
the tests stayed green.

Build the scenario on a real AgentManager with a provider session that holds its
turn start open for a configurable span, so pendingRun.started, the lifecycle
change to "running", and the agent_state notification are what the wait actually
observes. Budgets stay independent literals and the settled-before/after
assertions are unchanged.

* fix(server): describe agent run start timeouts

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>

* fix(relay): validate handshake keys (#3669)

* fix(files): keep large source previews responsive (#3665)

Large read-only files bypassed the editor's virtualization and eagerly mounted every highlighted token, which could stall the renderer for seconds and consume gigabytes of memory. Route source previews through bounded platform renderers and reject reads above their display budgets before accumulation.

* fix(hub): make guided setup choices actionable (#3677)

Show a concise login success state, scope starter choices to available Hub connections, wait for daemon capabilities, and avoid partial starter bundles when setup cannot proceed.

* Keep released agent SDK versions reproducible (#3678)

Pin the validated provider and protocol SDK dependency pair so fresh installs cannot silently float after a Paseo release. Refresh the Nix npm dependency hash for the resulting lockfile.

* feat(app): refine agent profile creation (#3680)

Keep profiles as the richer primitive while restoring the familiar star cue and clearer guidance for agent selection. Account for borders in shared field geometry so controls preserve their declared heights across form consumers.

* fix(app): restore native composer auto-growth (#3681)

Native multiline inputs were always scrollable, which kept iOS content-size measurement pinned to one line. Keep native sizing intrinsic below the cap and enable internal scrolling only after the composer reaches it.

* docs(hub): document zero-friction onboarding (#3657)

Lead with the browser-to-CLI onboarding journey, document the generated starter bundle, and align guided setup with available Hub connections and daemon-reported runtime defaults.

* Surface workspace changes from agent panes (#3682)

* feat(app): refine workspace pane actions

Prioritize Changes when available, keep Files useful before a selection, and reserve desktop diff clicks for selection so comments require the explicit affordance. Move the Changes shortcut to Shift+G to avoid consuming a likely copy variant.

* feat(app): improve access to workspace changes

* Expand workspace panel actions and keyboard navigation (#3685)

* fix(app): keep file navigation in place

Retargeting a File tab allocated a new tab identity, causing the tab track to unmount and remount during navigation. Preserve the identity for File-to-File changes while retaining new instances for cross-kind replacements.

* feat(app): expand workspace panel actions

Keep the Command Center aligned with workspace tab and pane primitives. Add explicit panel placement, focused open behavior, keyboard navigation for empty panes, and a Changes pill that toggles the visible Side panel without closing its tab.

* Restore compact and stable chat timelines (#3716)

Restore compact message and footer spacing, reserve independent composer-pill clearance for transcript content and floating controls, and keep visible web tool-call expansion from shifting the reading position.

* perf(app): defer replica persistence until user inactivity

Whole-replica capture and serialization blocked the JS thread during navigation and streaming. Defer that work until the user has been inactive while allowing long-running streams to persist without becoming the idle signal.

* Give every pane a browser-style New tab (#3715)

* feat(app): replace empty panes with New tabs

Give every retained pane an independently identified New tab, replace it in place when a destination is chosen, and preserve split close and move invariants. Keep existing workspace startup and compact explorer routing explicit, with focused unit and browser regression coverage.

* test(app): count content tabs in subagent integration

* fix(app): preserve workspace shortcut ownership

* fix(app): keep compact model options scrollable

The compact model sheet had no sheet-aware scroll owner when profiles filled its viewport, leaving the controls below them unreachable. Keep the root provider view inside Gorhom's scroll container so native sheet gestures work on both platforms.

* fix(app): order new tab launcher actions by workflow

* fix(app): show the New agent shortcut

The command center resolved New agent through the New tab binding, so it advertised Cmd+T despite creating an agent. Resolve the target-agent binding instead and cover the mapping.

* fix(app): prioritize side panel launcher actions

Lead with supporting workspace tools when the launcher is in the Side panel, while ordinary panes continue to lead with agent creation.

* fix(app): align terminal profiles header

* docs: add 0.5.0-beta.5 changelog

* test(app): align new tab launcher expectations

* chore(release): cut 0.5.0-beta.5

* fix: update lockfile signatures and Nix hash [skip ci]

* fix(app): make compact git actions menu-only

Keep compact toolbars narrow and require users to choose potentially consequential Git and workspace actions from the menu.

* fix(app): keep pinned project actions reachable (#3722)

* fix(app): keep pinned project actions reachable

Preserve an empty project shell after its final workspace is hoisted into the pinned section so project-scoped workspace creation remains available.

Co-authored-by: Agent Op <agentop718@gmail.com>

* test(app): verify pinned project workspace context

---------

Co-authored-by: Agent Op <agentop718@gmail.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>

* fix(app): stop a string SVG transform from crashing the app (#3711)

* fix(app): stop a string SVG transform from crashing the app

A root <Svg> is a real React Native view, so react-native-svg rewrites a
*string* `transform` into RN style syntax in `extractTransformSvgView`,
using a PEG parser whose grammar only accepts SVG transform functions.
Upstream lets that parser's SyntaxError escape, so any string it does not
understand takes the whole app down during render:

  SyntaxError: Expected transform functions but "n" found.
  "found": "n", "location": { "start": { "offset": 0 } }

`none` reproduces that message character for character; so does any other
CSS value (`rotate(-90deg)`, `translate(10px, 0)`). Every sibling
extractor in that file already catches the same error and degrades, so
the asymmetry is the bug: a string that is merely ignored on a <Path>
crashes on an <Svg>.

We feed that path untrusted input. The daemon scans a project for
favicon.svg / icon.svg / logo.svg (packages/server/src/utils/project-icon.ts)
and ships it to the client, where ProjectIconImage hands it to SvgCss.
Running the library's own XML parser over `<svg style="transform:none">`
yields exactly `transform: "none"` on the root, which is why the crash
starts once a host connects and the project icons render, with no
terminal involved.

Note this is *not* reachable from an RN-style array transform: the
extractor returns arrays untouched and only parses strings, so
`style={{ transform: [{ rotate: "-90deg" }] }}` (the context window meter
and the checks ring) was never a factor.

Patch the three builds a consumer can resolve - Metro takes the package's
`react-native` field (src/*.ts), Node and Vitest take main/module - and
register react-native-svg with the postinstall patch runner so the patch
actually applies. The new test pins all three.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EDwfsMWoSFfPm2cFXe5noQ

* test(app): cover parsed root SVG transforms

Exercise the dependency patch with root props produced by the library's XML and CSS parsers, including embedded style rules that can arrive through project icons. Keep dependency-patch documentation focused on the reusable install and resolution constraints.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>

* fix(pairing): warn that pairing links grant daemon access (#3734)

A pairing offer remains valid across daemon restarts, so remove the incorrect rotation guidance and warn users where the durable credential is displayed.

* fix(app): preserve timeline retry backoff (#3736)

Equivalent visibility publications were acting as implicit retry commands, so unrelated tab and layout changes could restart failed timeline work. Keep visibility identity stable and reserve immediate retries for explicit user action.

* Open the tab chooser beside the pointer (#3735)

* feat(app): open the tab chooser beside the pointer

Keep keyboard-created and empty-pane New tabs as persistent launcher surfaces, while giving pointer users an anchored chooser at the pane-local plus button. Both presentations resolve the same launch catalogue so future ordering and customization cannot drift.

* test(app): follow the anchored tab chooser

The browser helpers still searched inside the pane for persistent-launcher controls after clicking the portalled menu trigger. Exercise the menu through its own roles and stable catalogue IDs.

* fix(app): restore composer height lifecycle (#3740)

Native Fabric can omit the initial content-size callback and retain stale size after deletion. Keep the common composer surface free of platform branches while native and web adapters own their measurement mechanisms.

* fix(server): stop quota fetcher from writing provider credentials (#3442)

* fix(server): recover already-settled interrupts (#3742)

* fix(server): recover already-settled interrupts

Provider state can drift from Paseo’s tracked run. Treat an authoritative already-idle interrupt as settled and retire the captured run token so retrying can start new work without weakening ambiguous-cancellation protection.

* fix(codex): fence interruption during turn startup

Reserve foreground-start ownership before asynchronous provider setup. An interrupt can now cancel preflight before turn/start or wait for an issued start to become interruptible, preventing acknowledged cancellation from leaving unmanaged work.

* docs(changelog): prepare 0.5.0 stable release

* chore(release): cut 0.5.0

* fix: update lockfile signatures and Nix hash [skip ci]

* fix(app): stabilize native composer resizing

JavaScript height state competed with native intrinsic layout across draft and agent ownership transitions. Let native inputs own sizing and scrolling while preserving DOM measurement on web.

* docs(changelog): prepare 0.5.1 stable release

* chore(release): cut 0.5.1

* fix: update lockfile signatures and Nix hash [skip ci]

* perf(app): move replica cache validation to restore

Add an observational live measurement harness for the row-store migration baseline. Trust already-shaped cache values on persist while retaining full schema validation at the restore boundary.

* feat(app): add replica cache row store

Ship the platform storage engines behind a dark interface so the cache migration can wire them separately. Both engines share a contract, including atomic writes and destructive schema-version resets.

* feat(app): switch replica cache to row storage

Persist only changed replica entities so steady-state writes scale with the update rather than the complete cached world. Restore remains an all-or-nothing validated boundary, with whole-host byte-budget eviction.

* fix(app): harden replica row persistence

Serialize pending batches at write execution time so retries cannot overwrite newer rows or resurrect removed hosts. Reject oversized hosts before storage writes and preserve unchanged directory identities so routine reconciliation stays row-grained.

---------

Co-authored-by: BrianAguilarWasco <146237330+BrianAguilarWasco@users.noreply.github.com>
Co-authored-by: paseo-ai[bot] <266920839+paseo-ai[bot]@users.noreply.github.com>
Co-authored-by: Yuri Gui <yugui923@users.noreply.github.com>
Co-authored-by: Agent Op <agentop718@gmail.com>
Co-authored-by: Omer Cohen <639682+omercnet@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Dan Berindei <dan.berindei@gmail.com>
2026-08-24 20:45:49 +02:00
Mohamed Boudra ac4331456c Manage projects from the CLI (#3460)
* feat(cli): manage projects

Expose the existing project lifecycle through one CLI command group so projects can be managed without opening the app.

* fix(cli): preserve remote project paths

Project paths belong to the daemon when a target is explicit. Require that path instead of resolving the CLI machine's cwd, and keep local convenience resolution for the default connection.

* docs(cli): document project management

* docs(cli): remove repeated project reference
2026-08-16 20:32:08 +02:00
Mohamed Boudra 16120ebff1 Make local plugins manageable and debuggable (#3446)
* feat(plugins): expose Paseo API to local plugins

Plugin surfaces borrow the selected host connection while subprocess handlers receive an exclusively owned IPC session, so plugins can use normal workspace and agent operations without inventing another transport.

* feat(plugins): reload global enablement safely

Treat the global plugin switch as a live daemon setting and teach plugin automation to inspect it before acting. Enabling remains permission-gated because plugin backend code can access the daemon machine and client contributions execute inside the app.

* feat(plugins): expose plugin logs

Capture plugin stdout and stderr separately from IPC so authors can debug initialization, RPC, cleanup, and crash behavior from the app or CLI. Keep a bounded tail across lifecycle recovery while preserving structured output in the daemon log.

* fix(build): order plugin dependency builds

* test(plugins): align startup and CLI expectations
2026-08-16 18:54:44 +08:00
Mohamed Boudra 4748aad103 Reload daemon configuration without restarting (#3365)
* feat(daemon): reload configuration without restarting

Keep config validation, reloadability classification, override handling, and atomic runtime application inside the daemon so local and remote clients share one authoritative workflow.

* fix(daemon): preserve reload resource ownership

Keep every materialized provider client under daemon-lifetime ownership and classify speech overrides from resolved providers. Restore the complete WebSocket config-store test contract exposed by CI.
2026-08-14 22:46:32 +02:00
Mohamed Boudra d012b6f071 Help agents diagnose Paseo provider problems (#3243)
* feat(skills): add Paseo help and provider diagnostics

Give support agents a topology-aware troubleshooting entry point backed by the deployed documentation index, and expose the daemon provider diagnostic through the CLI so agents can inspect the affected host directly. Agent-launching skills now use configured profiles instead of the removed orchestration preferences.

* test(cli): keep provider diagnostics on real boundaries

Remove the internal client mock and preserve its normalization and host-targeting assertions through the existing isolated-daemon coverage.
2026-08-12 00:57:33 +02:00
Martin HanzíkandClaude Opus 5 a39959219f feat: Implement workspace rename cli (#3209)
* feat(cli): add workspace rename command

`paseo workspace rename <workspace-id> <title>` sets a workspace's
user-visible title through the existing workspace.title.set RPC, and
`--reset` clears the override so the name reverts to the branch or
directory. The command reads the descriptor back so the output shows
what a reset reverted to.

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

* fix(cli): harden workspace rename against read-back and arg failures

Review findings on the rename command:

- A failed workspace list sweep after a successful title change reported
  the whole command as failed. The read-back is display-only, so it now
  runs outside the mutation's error path and degrades to a null name.
- Only --reset needs the descriptor read back; a set title is already the
  resolved display name, so the common path no longer pages the list.
- Dropped the `"code" in error` rethrow. Nothing in that block throws a
  CommandError, but DaemonRpcError always carries a `code` property even
  when it is undefined, so the guard let those through to render as
  UNKNOWN_ERROR with a stack instead of WORKSPACE_RENAME_FAILED.
- Commander 12 allows excess positionals, so an unquoted multi-word title
  renamed to its first word and dropped the rest. The command now rejects
  excess arguments.

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

* refactor(cli): report the rename RPC response instead of reading back

Reading the descriptor back to show what --reset reverted to was the only
reason rename needed a paged workspace sweep, and that sweep dragged in
everything around it: a failure path that could report an applied rename
as failed, a nullable name, and a bespoke warning sink to tell the two
null cases apart.

Sibling commands don't do this. `workspace archive`, `agent mode`, and
`schedule update` all report what the RPC returned and stop; the one
command that reads back is `agent update`, which has the same "applied
but reported as failed" bug. So rename now mirrors `workspace archive`.

`--reset` reports `title: null` rather than naming what it reverted to;
`paseo workspace ls` shows the resolved name. This also reverts the
`collectWorkspaces` extraction, whose only second caller was rename.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 16:36:58 +00:00
Mohamed Boudra 05a0cabc07 Deploy multi-file Hub workflows (#3128)
* feat(hub): deploy canonical workflow bundles

Keep CLI deployment aligned with Hub's authored bundle boundary so validation and activation receive the same deterministic source files. Public documentation now teaches that boundary as the only configuration format.

* test(hub): assert validation diagnostics directly

* refactor(hub): deepen HTTP client module

* feat(hub): validate named agent configurations

* docs(hub): align Claude mode example
2026-08-10 12:32:59 +02:00
Mohamed Boudra ccb4e444ed Rework Hub workflow docs around explicit delivery and GitHub authority (#3086)
* docs(hub): name the Hub tools in workflow prompts

Every Hub workflow example now tells the agent which tool to call:
hub.reply for the user-facing response, hub.finish_execution to
complete the step and record structured output. Granting a capability
through allow_outputs makes the tool available; it does not make an
agent use it, and an agent that is not told answers in its own
transcript and leaves the step to its idle timeout. Explicit
instructions are what worked against real agents.

Classifier and other intermediate steps name hub.finish_execution
alone, so the docs do not imply every step must reply.

Drops the github.reply allow_outputs blocks from the GitHub examples.
The GitHub provider registers no output capabilities, so that
declaration never materializes a tool; those agents comment through
their scoped GH_TOKEN with gh, which the reference and trigger pages
already said. The read-only answer branch has no network authority for
that, so it only finishes.

Also quotes the "Request: ${{ paseo.prompt }}" prompt block in the
reference, which was invalid YAML as written.

* docs(hub): wrap triggering input in its own prompt block

Every Hub YAML example now ends its prompt with the raw triggering
message wrapped in <user-prompt> tags, as a separate block after the
workflow instructions. The agent can see where your instructions stop
and the request starts, which matters most on the steps that read
external Slack, Discord, and GitHub text.

The first workflow explains the convention in two sentences and points
at Hub security for the controls that actually limit an agent. Tags
label the input; they are not containment, and the docs do not claim
otherwise.

Replaces the earlier quoting fix in the prompt-blocks reference: the
message now lives in a block scalar, so the colon needs no quoting.

* docs: base worktree examples on origin/main

Every branch-off example now passes a remote-tracking ref. Paseo
fetches remote refs in the background, so origin/main is current while
the local main is whatever was last pulled, and base resolution tries
refs/heads first: an unqualified main silently branches off stale
history. The gotcha is explained once, next to the first branch-off
example in the worktrees guide; the other pages just use the ref, and
the Hub environment reference links there.

Also points the safety-gate classifier at the structured-output
section, which shows the output wrapper the finish call takes.

* docs(hub): configure Codex read-only through provider options

Hub examples described a read-only Codex step as mode: read-only. That
Paseo mode preset pairs the read-only sandbox with approval_policy
on-request, so an unattended Hub step stops to ask a question nobody is
there to answer. It is also not one of the modes the Codex provider
advertises.

Every read-only step now uses the provider-native passthrough proven by
the real-provider evidence in #3025: approval_policy never,
sandbox_mode read-only, web_search disabled. The security guide already
used this block; the workflow guide, examples, and reference now agree
with it, and the reference says plainly that a Paseo mode ID is a
separate surface from these options.

mode: full-access is unchanged. It maps to approval_policy never, so it
does not stall an unattended step.

* docs(hub): show a classifier choosing the repository and the model

Both routing jobs were only documented as caller-supplied inputs, so
there was no example of the thing people actually want: one read-only
classifier that reads the request, picks the repository it belongs to
and the model it needs, and hands both to a worker. Replaces the
caller-only model example, which the new one covers through its
override values.

The two axes are not symmetric and the example says so. A model is a
value, so values.selected_model goes straight into agent.model. An
environment cannot come from an expression at all: the compiler accepts
a literal name or paseo.inputs.<name> and nothing else, so each
repository needs its own environment and its own conditional step. One
classifier, exactly one worker.

Corrects the routing section, which listed environment among the fields
an agent output may select. It never could.

Also puts a blank line between adjacent steps and environments in the
deadline and repository-routing examples, and bases the daemon worktree
example on origin/main, which the earlier sweep missed.

* fix(website): keep development requests local

Canonical host redirects were applied to LAN-addressed development traffic, including inspector WebSockets. The redirected socket retained the Vite port and caused the development proxy to terminate when the public host timed out.

* docs(hub): order the workflow scenarios from simple to hard

The Hub pages were arranged by the shape of the configuration schema
rather than by what a reader is trying to do, which showed up three
ways: the overview opened on the storage hierarchy, workflow
configurations were split between workflows.md and a separate examples
page, and the examples that existed started with the hardest one.

workflows.md is now a ladder. One Slack trigger answering in a thread
stays inline as the first workflow, and nine further scenarios sit
under it as collapsible blocks, each a complete configuration adding
one idea to the one above: full access, repeated replies, a GitHub
trigger, two ordered steps, a typed input, two triggers, a classifier
choosing a model, a classifier choosing a repository, and last the
override composed with ??. configuration/examples.md is removed and its
configurations live there now.

Two claims in the old material were wrong and are corrected. Nothing
carries between steps except a declared output, so the planning
scenario passes its plan through output.schema instead of telling the
second agent to follow a plan it cannot see, and a worktree environment
creates a worktree per execution rather than sharing one. GH_TOKEN
authenticates the gh CLI but does not configure git, so the pages no
longer imply a step can push without a commit identity and credential
helper on the daemon host.

The overview drops the organization/connections/daemons/projects tree,
which concepts.md already owns, in favour of the smallest configuration
that does something. Hosting is now a link from the product pages
rather than a section in them.

* docs(hub): make GitHub access an explicit step grant

GitHub authority gets its own page. A step holds a token only when it
declares a github block — connection, repositories, permissions,
duration — and Hub supplies GH_TOKEN plus environment-only git
configuration, revoked when the step ends. No credential is ever
implied by the trigger source.

Rewrites the issue-to-pull-request scenario onto the block, repoints
the trigger, reference, FAQ, and concepts pages, and sweeps the Hub
pages for announcing sentences and compressed paragraphs.

* docs(hub): lighten dense reference prose

Restructure paragraph walls into short paragraphs, lists, and code
blocks: logout behavior in daemons, deploy paths and precedence in
configuration, install limits and dispatch fields in the API page,
required outputs and partial resolution in the reference, and the
policy translation and secrets guidance in security. Fixes two
remaining trigger-derived credential claims in triggers/index.

* fix(nix): update npm dependency hash

* docs(hub): document shared prompts and authority defaults
2026-08-09 16:59:00 +02:00
Mohamed Boudra 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 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 dfd18a44a7 Document Hub and let docs pages nest (#2864)
* feat(website): nest docs pages under landable groups

The docs sidebar could already render groups from subdirectories in
public-docs, but nothing used it because every doc was a flat file, and
two things stopped it being usable.

A group had no page of its own, so its label was only a toggle and its
name came from the directory name via formatLabel. A directory's
index.md now supplies the group's label, order, and href, which makes
the group landable and lets the label be authored rather than derived.

Categories and top-level directories named the same thing, so a
categorised doc at hub/index.md produced a group nested inside its own
category. Only directories below the top level become groups now.

Also fixes two behaviours in the sidebar:

- The open/closed effect assigned containsActive on every change, so a
  group you expanded to browse collapsed the moment you navigated
  anywhere else. Navigating in now opens a group and navigating away
  leaves it alone.
- The sidebar scrolls independently of the page and never scrolled the
  active entry into view, so loading or refreshing a page far down the
  list left the sidebar parked at the top with no visible position.

The group row is a single target: it navigates and expands, and
collapses when clicked again from the page it points at. A separate
chevron button would put two actions a few pixels apart.

* docs(hub): document Hub

Adds a Hub section covering what it is, how it works, daemons,
triggers, configuration, activity, and the two ways to run it.

The section separates using Hub from running it. Overview, quickstart,
daemons, triggers, and configuration hold regardless of who operates
the deployment; the self-hosting branch owns deploying the service and
creating the GitHub, Slack, and Discord apps it talks to. That split
falls on a real seam, because those apps are exactly what a hosted Hub
would own on your behalf. Hosted is a placeholder until it ships.

Each provider gets a trigger page carrying its events, filter
semantics, and the full paseo.event.* reference for the data a prompt
can read. That data is the thing a config author actually needs and
the thing they cannot get from anywhere else.

Every YAML block was parsed against the Hub config schema rather than
eyeballed, which kept several plausible-looking things out of the
docs: worktree prNumber takes a literal number and not a merge
variable, only daemon environments run today, and github.push has no
actor so it can never satisfy the required from_users filter.

Troubleshooting moves to order 90 so it stays last.

* refactor(website): let the active docs entry scroll itself into view

The layout queried the sidebar for the active entry after every
navigation, which coupled it to when that entry mounts. A link inside a
collapsed group only mounts once the group's own effect opens it, so
the query could run against a DOM that does not contain it yet.

Moving the scroll to the entry removes the ordering question rather
than answering it, and drops the ref and the cross-component query.

Reported by Greptile on #2864. The failure it described does not
reproduce: navigating from a page outside a collapsed group into one of
its children still scrolls correctly, verified with the group confirmed
collapsed and the sidebar forced to the top. The coupling was real even
though the bug was not.

* docs(hub): stop listing BETTER_AUTH_URL as required

It falls back to PASEO_CLOUD_PUBLIC_URL, so a deployment that sets the
public origin already has a correct auth base URL. Listing it as
required made the Fly recipe look incomplete when it was not.

Moved to optional with its default, and recorded what actually breaks
without BETTER_AUTH_SECRET: the auth routes close and nobody can sign
in.

Reported by Codex on #2864, which read the same inconsistency the other
way round and proposed adding the variable to the Fly example.

* docs(hub): attach a database in the Fly recipe

The Fly section showed inline config and a secrets command, neither of
which supplied DATABASE_URL. It defaults to localhost:5432, which on
Fly reaches nothing, so the documented deployment could not start.

fly postgres attach sets DATABASE_URL as a secret, so the step is the
attach itself rather than another variable to copy.

Reported by Codex on #2864.

* docs: align Hub self-hosting contract
2026-08-05 03:58:27 +08:00
Mohamed Boudra d2de309fc3 Make relay opt-in when pairing devices (#2706)
* feat: make relay opt-in for device pairing

* fix(relay): keep pairing state authoritative

* fix(relay): refresh pairing state across transports

* fix(onboard): honor explicit relay opt-out

* fix(relay): surface transport startup failures

* fix(pairing): gate offers for older daemons

* test(cli): rely on real pairing transport coverage

* fix(cli): report live relay without a pid file

* fix(relay): synchronize config during daemon startup

* fix(relay): preserve mutability for ignored overrides

* fix(cli): use reachable daemon for pairing

* fix(app): redirect invalid pair device routes

* fix(pairing): support remote host device pairing

Keep Pair Device host-relative through the existing settings and daemon RPC flow. Route CLI output through an injected adapter so workflow tests do not mutate process globals.

* fix(pairing): validate pairing destinations

Expose the host-relative Pair Device flow on every supported app platform. Reject a reachable daemon whose identity does not match the requested Paseo home before reading or mutating its pairing state.

* fix(pairing): render QR codes across platforms

Generate SVG text without DOM or canvas and render it through the existing cross-platform SVG stack.

* fix(cli): pair from persisted relay state

Allow explicit relay pairing while the daemon is stopped when relay is already enabled in persisted configuration.
2026-08-01 04:35:06 +08:00
Matt CowgerandMohamed Boudra 21404fbdec feat(cli): manage workspace scripts (#1992)
* feat(cli): manage workspace scripts

* docs: document workspace script management

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-07-24 21:12:14 +08:00
Mohamed Boudra 246c07fba5 fix(cli): make new workspace creation explicit (#2315)
Agent callers now stay in their current workspace unless --new-workspace explicitly requests a separate local or worktree workspace.
2026-07-22 18:26:40 +08:00
Mohamed Boudra 72752b7db6 Document workspace-first agent automation (#2192)
* docs: explain workspace-first automation

* docs: make automation examples self-contained
2026-07-18 15:18:05 +02:00
Mohamed Boudra c72bcb49e5 docs: restructure schedules IA (app-first), add Orchestration category 2026-07-08 13:43:59 +02:00
Mohamed BoudraandHerbrant 2c9db5279c Run Paseo from an official Docker image (#1740)
* Add Docker images and agent Docker Mods

Ship official container images that run the Paseo daemon headless. One
Dockerfile parametrized by BASE_IMAGE covers Debian 12/13, Ubuntu 22.04/24.04
and Alpine; it bundles Node 22, the npm-published server + CLI, a vendored
s6-overlay as PID 1, and a small Docker Mods loader.

Agents are chosen at runtime via DOCKER_MODS (pipe-separated mod images). Each
mod is a FROM scratch image carrying only an install hook that runs
`npm install -g <agent-cli>`; the loader pulls the layers from the registry,
extracts them, and runs the hook before the daemon starts, so any requested
agent is on PATH when Paseo probes provider availability.

- docker/base: Dockerfile, install scripts, s6 services, mods loader
- docker/mods/*: claude-code, codex, copilot, opencode, pi
- docker/docker-compose.example.yml + docker/README.md
- .github/workflows/docker.yml: multi-arch (amd64/arm64) buildx matrix,
  publishes to ghcr.io/getpaseo on version tags
- docs/docker.md + CLAUDE.md docs index row

* feat(docker): print pairing QR and link on daemon startup

Add an s6 oneshot service that waits for the daemon to listen, then runs
`paseo daemon pair` so the pairing QR code and link surface in the container
logs. Best-effort: never blocks boot, skips gracefully when relay is disabled.
Opt out with PASEO_PAIRING_QR=0.

* build(docker): add Arch image support

* ci(docker): build Arch without Buildx

* docs(docker): document paseo env contract

* feat(docker): add opt-in sudo mode

* docs(docker): link env references

* fix(docker): create agent config dirs

* fix(docker): default home to /home/paseo

* docs(docker): document agent auth setup

* docs(docker): document relay port setup

* fix(docker): install Node from tarball

* docs: add Docker quick start

* docs(docker): remove legacy home example

* docs(docker): set container hostname

* fix(docker): prepare opencode storage

* fix(docker): allow paseo login shell

* fix docker opencode permissions

* ci(docker): use Node 24 actions

* fix(docker): install bzip2 runtime tools

* fix(docker): update Pi mod package

* fix(docker): quiet default daemon logs

* fix(docker): split home from state

Docker images now keep HOME at /home/paseo and store Paseo daemon state under /home/paseo/.paseo by default.

Existing volumes can keep the old layout by setting PASEO_HOME=/home/paseo.

* fix(docker): keep mods out of paseo home

* ci(docker): skip alpine arm64 builds

* fix(docker): address review findings

* fix(docker): verify s6 overlay downloads

* fix(docker): honor custom healthcheck port

* fix(docker): fail on mod extraction errors

* feat(docker): add official container image

Ship a focused daemon image with the bundled web UI enabled and document extending it with agent CLIs.

* ci(docker): publish images only on stable releases

* fix(docker): check daemon health over HTTP

---------

Co-authored-by: Herbrant <cdavide98carnemolla@gmail.com>
2026-06-26 14:48:13 +08:00
Mohamed Boudra 7e46f6a647 Rework docs navigation and move alternatives to top-level pages (#1487)
* Rework docs navigation and move alternatives to top-level pages

- Add nested docs nav with categories, collapsible groups, breadcrumbs,
  and a right-hand page outline with heading slugs.
- Move alternatives out of /docs/alternatives into /alternatives/*
  marketing pages with 301 redirects.
- Add github-slugger for heading IDs and require explicit width on
  SiteShell so new pages can't accidentally pick the narrow prose layout.
- Remove the best-practices doc from public-docs.

* Make docs content column prose-width
2026-06-12 12:53:30 +00:00
Mohamed Boudra 8e6455f1d6 docs: add workspaces page explaining project/workspace/session model
Adds a new conceptual Workspaces doc that explains how Paseo organizes work around projects, workspaces, and sessions. Reframed the existing Git worktrees page as the technical deep-dive it always was. Updated glossary with agent session and tab terminology. Reordered docs nav so workspaces and worktrees come before providers.
2026-06-10 15:50:19 +07:00
Mohamed Boudra a1ac402154 docs(website): add provider pages and refresh docs 2026-05-07 22:52:26 +07:00
Mohamed Boudra 30f7842e8f feat(cli): connect via relay using pairing offer URL (#639)
Pass `--host https://app.paseo.sh/#offer=...` (or `PASEO_HOST=...`) to
any CLI command and the connection routes through the relay with E2EE,
the same way the mobile and desktop apps connect. Same `paseo daemon
pair` link the app already accepts — no new pairing flow.

Closes #181
2026-04-30 21:37:51 +08:00
Mohamed Boudra dec47d72d9 docs: lowercase internal docs + migrate website docs to public-docs/ (#634)
* docs: rename to lowercase + drop leftover plans

Rename docs in docs/ to lowercase kebab-case for consistency and update
all references in CLAUDE.md, CONTRIBUTING.md, CHANGELOG.md,
packages/server/CLAUDE.md, and inter-doc links.

Drop two leftover design plan docs:
- docs/ATTACHMENT_BASED_REVIEW_CONTEXT_PLAN.md
- docs/plan-approval-normalization.md

* docs: drop stale uppercase entries from case-insensitive rename

* feat(website): power /docs from public-docs/ markdown tree

Move website docs out of TSX route components and into a root-level
public-docs/ directory of plain markdown files with frontmatter
(title, description, nav, order).

- Add packages/website/src/docs.ts loader using import.meta.glob with
  ?raw to compile the markdown into the bundle at build time.
- Replace the 9 hand-written docs/*.tsx routes with a single $.tsx
  catch-all that renders any slug via react-markdown.
- Drive the docs sidebar nav from frontmatter order/nav.
- Auto-discover docs routes in vite.config.ts so the sitemap stays in
  sync without manual edits.

* fix(website): bind dev server to 0.0.0.0 so port collisions trigger fallback

`host: "127.0.0.1"` (or unset) lets macOS coexist with another process
holding an IPv6 dual-stack `*:8082` socket, so Vite never sees
EADDRINUSE and silently binds alongside it. Forcing IPv4 wildcard
makes the conflict real, and Vite's default `strictPort: false`
falls through to the next free port.

* fix(website): restore docs page styling after markdown migration

Add a .docs-prose class that mirrors the styling the original
docs/*.tsx components hand-rolled (h1/h2/h3 sizes, paragraph/list
spacing, link colors, code blocks, callout-style blockquotes).

ReactMarkdown was emitting unstyled HTML because the previous
wrapper class only had inline-code rules — headings and code
blocks fell back to user-agent defaults.
2026-04-30 17:27:15 +08:00