Commit Graph

3175 Commits

Author SHA1 Message Date
Mohamed Boudra
34df55a10a Fix chat fanout + unify system-injected agent prompts (#830)
* Fix chat everyone fanout

* Unify system-injected agent prompts

Chat mentions, schedule fires, and notifyOnFinish now share one shape:
prompt wrapped in <paseo-system>...</paseo-system>, no user-message
turn recorded, no auto-unarchive. Agents can recognize system-injected
context vs a real user turn.

- Move agent prompt orchestration out of mcp-shared.ts into a neutral
  agent-prompt.ts module (mcp is an interface, not the home).
- Add `unarchive` and `recordUserMessage` flags to sendPromptToAgent.
- Add formatSystemNotificationPrompt helper.
- Schedule fires now identify themselves: "Schedule \"<name>\" fired
  (id=..., run=...)." Plumb runId through runner so the paseo.schedule-run
  label matches the actual run record.
- Replace chat-mention validateChatMentionFanout + duplicate
  resolveChatMentionTargetAgentIds with single prepareChatMentionFanout
  that owns expansion + cap + poster snapshot.
2026-05-09 17:10:22 +08:00
Mohamed Boudra
0229ef94f1 fix(app): submit typed path from iOS project picker (#829) (#831)
Wire onSubmitEditing on the picker TextInput so the iOS return key
submits a typed daemon-side path through the existing
open_project_request flow. Prepend the typed path to options when not
already present so it's tappable and participates in keyboard nav.
2026-05-09 16:46:49 +08:00
Mohamed Boudra
5b884aee0c Import existing Claude/Codex/OpenCode sessions (#766)
* Restrict provider import fan-out to enabled, non-derived allowlist

Move provider eligibility from a session-level post-filter to the agent
manager's fan-out so disabled providers are never spawned (fixes the
Gemini-disabled hang) and derived providers (like a custom Z.AI extending
Claude) don't duplicate their base provider's sessions.

Add an IMPORTABLE_PROVIDERS allowlist (claude, codex, opencode); ACP-based
providers are excluded from import discovery entirely.

* Filter Codex persisted threads by cwd before hydrating turns

Codex's listPersistedAgents previously called thread/read includeTurns on
every thread returned by thread/list, even when the import sheet was
scoped to one cwd. With many recent threads outside the scope, that fan
of reads dominated the import-sheet open latency.

Add an optional cwd hint to ListPersistedAgentsOptions, thread it through
the agent manager, and have Codex pre-filter the cheap thread/list rows by
cwd before scheduling the per-thread hydration. Widen the thread/list
window when filtering since most rows will be from other cwds. Other
providers ignore the hint.

* Fan out import session fetch per enabled provider

Sheet now reads the providers snapshot already warmed by the workspace
screen, filters to enabled importable providers (claude/codex/opencode),
and issues one fetchRecentProviderSessions request per provider via
useQueries with limit 15. Results are merged, deduped by handle, and
sorted by last activity.

Falls back to a single unfiltered request when the daemon does not
expose a provider snapshot (older servers). Per-provider failures show
inline so a single broken provider does not hide the rest.

* Add provider filter badges to import sheet

Show "All" plus one badge per enabled importable provider above the
session list when more than one is available. Single-select narrows the
already-merged list — no refetch. Each row now shows the provider's
icon next to its label so multiple providers stay visually distinct.

Selection resets when the sheet closes.

* Distinguish already-imported empty state in import sheet

Track how many descriptors were dropped because their handle was
already imported, surface the count back to the client as an optional
`filteredAlreadyImportedCount`, and switch the sheet copy to
"All recent sessions are already imported." when the merged list is
empty solely due to that filter.

* Cover already-imported provider session filtering

* List persisted OpenCode sessions for import

* Revalidate import sessions when sheet reopens

* Add agent persistSession plumbing

* Drop redundant visibility-transition refetch and test boilerplate

The import sheet's queries are gated on `enabled: visible`, and react-query
already refetches stale data when `enabled` flips on, so the manual
`wasVisibleRef` + `invalidateQueries` effect was redundant. Collapse the
two `selectedProvider` reset effects into one.

Drop `expect(...).toBeTruthy()` after `findByText` / `getByText` — those
helpers throw on miss, so the assertion was noise.

Drop `| null` from `buildOpenCodePersistedAgentDescriptor`'s return type
(it never returns null) and the dead `Boolean(descriptor)` filter at the
caller.

* Add provider-opaque session import path

Introduces fetchRecentProviderSessions on the daemon client plus an
importAgent overload accepting providerId/providerHandleId, and a
projection helper that renders persisted descriptors as provider-opaque
recent-session payloads with prompt previews.

* Unslop the import-session feature

Centralizes the importable-providers list, normalizes import_agent_request
shape at one boundary, and removes the per-provider fan-out fallback in
the import sheet. Old daemons without features.providersSnapshot now show
"Update host" instead of being served a degraded path.

Replaces internal-helper unit tests on the OpenCode and Codex providers
with boundary tests through listPersistedAgents; makes storageRoot
injectable on the OpenCode provider so the boundary test can target a
temp directory.

Splits the CLAUDE.md back-compat rule into two contracts: protocol stays
backward-compatible (always); features may require new daemons (no
fallback paths). Tags the two new compat shims with COMPAT(name) and
target removal dates.

* Extract importable-session listing into its own module

Move listImportableProviderSessions out of session.ts and drop the
unused generic listPersistedAgents from AgentManager.
2026-05-09 16:36:06 +08:00
João Sousa Andrade
8be2692b8a Protect local state file permissions (#825) 2026-05-09 16:28:23 +08:00
Mohamed Boudra
103601705d feat(app): focus attention-needing agent tab on workspace navigation (#828)
When navigating to a workspace, auto-open the tab for the agent that
most urgently needs attention (permission > error > finished, oldest
first). Adds pickAttentionAgent + wires it into navigateToWorkspace.
2026-05-09 16:07:14 +08:00
Mohamed Boudra
68ceac492c test(claude): drop flaky token-echo assertion in autonomous-wake test
Relied on Claude echoing AUTONOMOUS_WAKE_<token> verbatim, which is model-compliance, not the wake mechanism. The structural turn_started + turn_completed pair on the post-foreground subscribe stream is uniquely emitted by startAutonomousTurn() and proves the autonomous live turn fired.
2026-05-09 14:53:47 +07:00
Mohamed Boudra
ed07452c80 feat(checkout): merge PR action with real-GitHub e2e test (#814)
* feat(checkout): merge PR action with real-GitHub e2e test

Adds a `checkoutPrMerge` server RPC backed by `gh pr merge` (squash, merge,
or rebase) and surfaces three new actions in the git-actions menu —
"Squash and merge", "Create a merge commit", "Rebase and merge" — with
squash taking the primary slot when a PR is open and mergeable. Also
renames the existing local merge action to "Merge locally" to disambiguate
it from the new GitHub-side merge.

Adds an end-to-end test that creates a real temporary GitHub repo, opens a
PR via the daemon, polls until mergeable, calls `checkoutPrMerge`, and
verifies the merge landed on `main` — no mocks. The test pre-flights the
`delete_repo` scope so it skips cleanly on tokens that can't tear down,
and fails loud if cleanup ever errors so we don't leak repos.

PR status gains an optional `mergeable` field (`MERGEABLE | CONFLICTING |
UNKNOWN`) populated from `gh pr view --json mergeable`. The schema is
backward compatible — old payloads without the field still parse and
default to `UNKNOWN` via `z.catch`.

* Update PR status snapshot expectations

* refactor: collect git/checkout/PR feature into packages/app/src/git/

Move 21 git/checkout/PR files out of components/, hooks/, stores/, screens/,
utils/ flat-peer slop into a single packages/app/src/git/ module with
shrunk filenames (git-diff-pane.tsx -> diff-pane.tsx, use-git-actions.ts ->
use-actions.ts, etc).

Dedupe: drop the duplicate useGitActionHandlers from diff-pane.tsx — the
shared useGitActions hook is the single source. Drop the
buildGitActionsForPane wrapper. Collapse the merge-PR triple fan-out
(mergePrSquash/Merge/RebaseStatus) into one record keyed by
CheckoutPrMergeMethod. Streamline server-side single-use helpers
(dispatchStash/PullRequestMessage adapters, GitHubPullRequestMergeError
wrapper, legacy worktree test helpers).

Schema-additive only: messages.ts exports CheckoutPrMergeMethod and
PullRequestMergeable types; daemon-client.ts uses the typed alias.
Backward-compatible.

Net delta: 43 files, +219/-1212.

* fix(app): use GitHub icon for all merge-PR menu actions

* test: namespace temp GitHub repos under `paseotmp-` and centralize naming

Old prefixes (`paseo-checkout-ship-`, `paseo-e2e-`, `paseo-checkout-pr-merge-`)
collided with the `paseo-*` namespace of real repos, making bulk cleanup
unsafe. Switch every test that creates real GitHub repos to a single
unmistakable `paseotmp-` prefix, owned by one shared helper per test
boundary so no caller can name a repo outside the namespace.

- New `packages/server/src/server/test-utils/temp-github-repo.ts` exports
  `TEMP_GITHUB_REPO_PREFIX` + `createTempGithubRepoName(category)`; both
  server e2e tests (checkout-ship, checkout-pr-merge) consume it.
- `packages/app/e2e/helpers/github-fixtures.ts` locks the prefix internally
  and now requires callers to pass a `category` instead of an arbitrary
  `prefix`. Updated pr-pane and composer-attachments specs accordingly.
2026-05-09 15:39:26 +08:00
Mohamed Boudra
c097678cdf Surface paseo subagents in a collapsible section above the composer (#532)
* Surface paseo subagents in a collapsible section above the composer

Subagents no longer auto-open as tabs. Each parent agent's pane shows
a compact tab attached to the top of the composer, listing its direct
children with name and status; clicking a row opens or focuses the
subagent's tab. The section is provider-neutral so Claude Code Task
and Codex subagent sources can feed the same UI later. Reconciliation
now separates pruning (`activeAgentIds`) from auto-opening
(`autoOpenAgentIds`) so manually opened subagent tabs survive.

* Tighten subagents composer code from review

Collapse duplicate helper in workspace reconciliation, swap a hand-rolled
shallow comparator for fast-deep-equal (matching repo convention), drop
an unused React default import and a single-use formatCount helper, and
extract a shared PaneOverrides type in the agent-panel test.

* Fix oxlint failures in subagents code

Switch type aliases to interfaces, hoist inline JSX styles/handlers in
SubagentsSection to satisfy react-perf rules, and lift the test
makeAgent helpers onto AGENT_DEFAULTS so their cyclomatic complexity
drops below the configured cap.

* Add missing unistyles mock exports after rebase

After rebasing onto main, SubagentsSection now imports both
useUnistyles and (transitively, via WorkspaceTabIcon) withUnistyles.
Two test mocks needed updating to expose them.

Test drift fix: the implementation contract is correct; the local
react-native-unistyles mocks were stale.

* Tuck subagents tab under the composer

Subagents tab now grows visually from beneath the composer's rounded
top edge: the surface overlaps the composer by the corner radius so
the rounded curve nests into the tab. Composer's top padding is
removed so the tab sits flush against the input box with no
click-dead gutter. The tab is full-width in both states, the header
label drops the medium weight to follow design-system §3 (content
text inside a surface is normal), and hover covers the entire
visible tab when collapsed but stays scoped to the header row when
expanded.

* Decouple subagent close-tab from archive

Closing a subagent's tab no longer archives the agent — the tab is
removed from the per-client layout, the agent stays in the parent's
track until explicitly archived. Track rows gain an archive button (X)
to make the lifecycle gesture explicit. AgentManager.archiveAgent now
cascades to children carrying the paseo.parent-agent-id label so
subagent fleets don't outlive their orchestrator.

Trade-offs documented in docs/agent-lifecycle.md, including the known
limitation that handoff agents launched via the same MCP path get
cascade-archived alongside true subagents until a richer relation
model lands.

* Stabilize subagent archive button hover

Apply the sidebar workspace row pattern to fix two issues with the
new archive button: the row no longer drops hover when the pointer
moves onto the button, and the button slot reserves layout space
when invisible so adding/removing it doesn't shift the row.

The wrapper View tracks hover via onPointerEnter/onPointerLeave
(workspace rows do the same), and the slot stays mounted with
opacity 0 + pointerEvents="none" when the button is hidden. Also
swap the X icon for the Archive icon to match the gesture's intent.

* Match design language for subagent archive button

Hover state now changes the icon color from foregroundMuted to
foreground, matching the project-row trailing-action pattern in
sidebar-workspace-list.tsx. Drops the surface3 background hover
that didn't match the design language.

Adds a tooltip ("Archive subagent") on the trigger using the
canonical Tooltip + TooltipTrigger asChild pattern. Disabled when
the slot is hidden so it never appears unbidden.

* Slot subagents into one module + harden cascade

Reshape the subagents-in-composer feature so it lives behind one
module and integrates at a few explicit sites instead of being
smeared across generic files.

- Extract `PARENT_AGENT_ID_LABEL` to `packages/server/src/shared/agent-labels.ts`. Sweeps 16 literal sites across server, client, CLI, and tests.
- `packages/app/src/subagents/` is now a deep module: `index.ts` is the only React entry (`SubagentsSection`, `useArchiveSubagent`, `useSubagentsForParent`, `selectSubagentsForParent`, `resolveCloseAgentTabPolicy`, `shouldAutoOpenAgentTab`); `policies.ts` is a second designed entry for non-RN data consumers. Internal files renamed (`section.tsx`, `select.ts`, `close-tab-policy.ts`, `auto-open-tab-policy.ts`, `use-archive-subagent.ts`).
- Move the 22-line subagent archive flow out of `agent-panel.tsx` into `useArchiveSubagent`. Agent-panel becomes a one-line consumer.
- Move the close-tab subagent branch out of `workspace-screen.tsx` into `resolveCloseAgentTabPolicy` (discriminated union).
- Move the auto-open exclusion rule out of `workspace-agent-visibility.ts` into `shouldAutoOpenAgentTab`.
- Theme chevrons in `section.tsx` via `withUnistyles`; drop the forbidden `useUnistyles()` call from the hot path.

Server-side cascade hardening:

- Single write path: extract `markRecordArchived` and route both `archiveAgent` and the off-memory cascade branch through it. Off-memory cascaded children now notify subscribers (previously silent).
- Cascade no longer swallows child failures with `try/catch warn`. Failures propagate.
- Cascade test extended from 1 weak assertion to 5 contract tests: full archive shape (archivedAt, normalized lastStatus, requiresAttention=false), running child runtime stop, off-memory branch, subscriber notification for in-memory and off-memory children, partial-failure surfacing.

Net diff vs the start of this PR: -1037 / +444 lines. The feature now slots into the repo at four explicit sites: `agent-panel.tsx` (3 imports + a hook + the section), `workspace-screen.tsx` (one policy import), `workspace-agent-visibility.ts` (one predicate import), and `agent-manager.ts` (the cascade plus the shared label constant).
2026-05-09 15:39:13 +08:00
Mohamed Boudra
0364e8dc5e fix(app): preventDefault in capture phase for MarkdownLink anchor
Pressable's click handler calls stopPropagation, so the bubble-phase
preventDefault on the wrapping <a> never fired and the Electron view
navigated to the href in addition to the existing helper opening it
in the OS browser. Move preventDefault to onClickCapture (and
onAuxClickCapture for middle-click) so it always runs first. The
existing Pressable.onPress helper remains the only path that actually
opens links.
2026-05-09 12:44:07 +07:00
Mohamed Boudra
650d3148e9 feat(desktop): rich context menu — copy link, image, spellcheck
Replaces the hardcoded Copy/Paste/Select All menu with one driven by
ContextMenuParams: link items (Open in Browser, Copy Link Address),
image items (Copy Image, Save Image As), spellcheck suggestions, and
proper Cut/Copy/Paste in editable inputs. Both the main window and
the browser webview menus go through the shared helper.

MarkdownLink (web branch) now wraps its Pressable+Text in a real
<a href> with `display: contents` so Electron exposes linkURL on
right-click. Native branch is untouched.
2026-05-09 12:24:08 +07:00
Mohamed Boudra
511580a47e feat(mcp): inherit target's unattended mode when caller is unattended cross-provider
create_agent previously threw on cross-provider inheritance unless an explicit
mode was passed. In practice, sub-agents launched without a mode should run
unattended — keying permissions per sub-agent rarely makes sense. When the
caller is in an unattended mode (Claude bypassPermissions, Codex/OpenCode
full-access, Copilot autopilot) and no mode is requested, default the child
to the target provider's own unattended mode. Non-unattended callers still
require an explicit cross-provider mode, and explicit modes always win.
2026-05-09 12:06:42 +07:00
ezra
0be9764194 feat(codex): archive native Codex thread when archiving agent (#827)
When a Codex-backed agent is archived from Paseo, call Codex's
thread/archive API so the session is also marked archived in the
Codex app. This keeps both UIs in sync.

- Add optional archiveNativeSession() to AgentClient interface
- Implement for Codex provider via thread/archive JSON-RPC call
- Call best-effort in both archiveAgent() and archiveSnapshot() paths
- Failures are logged as warnings and do not block the archive flow

Closes #822
2026-05-09 12:37:09 +08:00
Mohamed Boudra
c40e1f03db docs: deep audit pass — fix drift across every doc, rebase coding-standards on /unslop
Each doc verified against current code; stale claims fixed in place.

- architecture: handshake/binary-frame/route/module-table corrections
- data-model: atomic-write claim narrowed, missing daemon files/fields added
- development: db:query removed (SQLite/Drizzle gone), tmux→concurrently+portless, PASEO_HOME split
- unistyles: withUnistyles(Icon) is the dominant pattern; new Animated.View+dynamic-styles iOS gotcha
- SECURITY: bearer-token auth, DNS-rebinding allowlist semantics, ephemeral phone keypair, wire format
- glossary: Project-checkout rename has shipped
- providers: claude-acp not built-in, Pi/mock, async isCommandAvailable, interface drift
- coding-standards: rewritten as compressed /unslop adaptation
- product/release/testing/custom-providers/android/mobile-testing/ad-hoc-daemon-testing/file-icons/design: smaller corrections
2026-05-09 11:35:26 +07:00
Mohamed Boudra
3a5d999006 docs: refresh CLAUDE.md doc index, fold design.md into design system
- Make `docs/` the explicit source of truth: "the docs"/"check the docs" mean this directory, not the web.
- List all 16 docs in CLAUDE.md (was 8); group by purpose so unistyles, glossary, data-model, providers, etc. are discoverable.
- Delete the old design.md (designing-features prose) and rename design-system.md → design.md so design tokens own the name.
2026-05-09 11:21:58 +07:00
Mohamed Boudra
832a2031bb fix(app): hoist QueryProvider above PortalProvider so portaled sheets see QueryClient
@gorhom/portal renders children at the host's location in the tree, so
sheets portaled to the root host lost QueryClient context (crash:
"No QueryClient set" when opening provider diagnostic sheet). Reorder
RootProviders and document the invariant: PortalProvider stays innermost.
2026-05-09 11:04:38 +07:00
Mohamed Boudra
1042270298 ci(nix): merge Fix Nix hash into Nix Build, commit only after green build
Single workflow now: update lockfile + hash, build, then commit the fix on
main only if the build passed. Eliminates the structural first-failure where
Nix Build always raced the autofixer and lost.
2026-05-09 10:26:36 +07:00
Mohamed Boudra
436bab0c8a fix(fastlane): populate whatsNew on new App Store version before review
submit_for_review failed with "appStoreVersions ... is not in valid
state - missing required attribute 'whatsNew'". Apple requires a
non-empty "What's New" string on every new version. With skip_metadata,
deliver never sets it, so we populate it ourselves via spaceship for
every existing localization on the edit version.
2026-05-08 21:47:46 +07:00
Mohamed Boudra
4792cba794 fix(fastlane): create editable App Store version before deliver
submit_review reached the deliver step but failed with "could not find
an editable version for 'IOS'". With skip_metadata + skip_screenshots,
deliver does not auto-create the "Prepare for Submission" entry on App
Store Connect, so submitting fails when the marketing version has not
been created yet.

Use Spaceship's ensure_version! to create or reuse the editable App
Store version for the build's marketing version, and pass app_version
to deliver so it targets the right entry.
2026-05-08 21:42:24 +07:00
Mohamed Boudra
39099a54b8 fix(fastlane): filter Build.all by build_number, not version
latest_testflight_build_number returns the build number (e.g. "2"), not
the marketing version. Previous fix used the version: kwarg which maps
to filter[preReleaseVersion.version] — that's the marketing version
("0.1.70") and never matched the integer build number, so the query
returned no builds and the lane spun on "processing state: unknown"
until timeout.

Apple's API names are confusing: filter[version] is the build number,
filter[preReleaseVersion.version] is the marketing version. The
build_number: kwarg in spaceship maps to filter[version], which is
what we want.
2026-05-08 21:37:45 +07:00
Mohamed Boudra
c9d842bd33 chore(gitignore): ignore app.json 2026-05-08 21:34:15 +07:00
Mohamed Boudra
ee4d72c706 fix(fastlane): use version: kwarg for Spaceship Build.all
The submit_review lane was crashing with "unknown keyword: :filter"
because Spaceship::ConnectAPI::Build.all doesn't accept a filter hash;
it takes specific kwargs (app_id, version, build_number, etc.) and
constructs the JSON:API filter internally.

Also drop includes: "preReleaseVersion" — it's part of ESSENTIAL_INCLUDES
by default.

Add resubmit-ios-review.yml workflow so we can re-run only the
fastlane submit_review lane (no rebuild) against an existing
TestFlight build via workflow_dispatch.
2026-05-08 21:25:41 +07:00
github-actions[bot]
53e477bc00 fix: update lockfile signatures and Nix hash 2026-05-08 13:34:00 +00:00
Mohamed Boudra
58fc2bb434 chore(release): cut 0.1.70 v0.1.70 2026-05-08 20:32:20 +07:00
Mohamed Boudra
6e75c72492 chore: refresh lockfile dev markers, document release gotcha
`release:prepare` runs `npm install` which churns optional-dep `dev`
markers in package-lock.json. That dirties the tree before the
`npm version` step in `version:all:*`, and the pre-commit format hook
rejects a lockfile-only commit because oxfmt internally skips
package-lock.json. Document the run-format/lint/typecheck-first
guardrail in docs/release.md.
2026-05-08 20:31:26 +07:00
Mohamed Boudra
3ed5567c8a docs(changelog): promote 0.1.70-beta.1 to 0.1.70 2026-05-08 20:21:47 +07:00
Mohamed Boudra
ad08868778 Fix Claude binary resolution for replace-mode command override
isAvailable() honored runtimeSettings.command.mode === "replace" but
resolveClaudeBinary() only looked for `claude` on PATH. Users with a
custom claude wrapper and no `claude` itself on PATH passed the
availability check and then failed at session creation with "Claude
binary not found".

resolveClaudeBinary now mirrors isAvailable: if the runtime command is
in replace mode, look up the override binary first.
2026-05-08 20:13:27 +07:00
Mohamed Boudra
5b6e974248 Fix terminal PTY teardown races 2026-05-08 20:02:52 +07:00
Mohamed Boudra
04b04fe5ee Fix terminal subscription snapshot ordering 2026-05-08 19:29:15 +07:00
Mohamed Boudra
9d5a47b1c3 Fix Claude integration query cleanup 2026-05-08 19:17:25 +07:00
Mohamed Boudra
5c90449707 Centralize Claude SDK launch path 2026-05-08 18:47:56 +07:00
Mohamed Boudra
e490bf1dab fix(server-tests): unblock Windows CI for Claude SDK + .exe launch test
- spawn.launch-regression: insert `--` between `node -e <body>` and the
  user JSON args. Without it Node treats `--config` as a CLI option and
  exits with code 9 ("bad option"). The `--` stops Node's flag parsing
  so userArgs land in process.argv intact.
- claude-agent.integration + claude-sdk-behavior: inject
  spawnClaudeCodeProcess that routes the SDK's launch through our
  spawnProcess (shell: false). On Windows the SDK's default spawn fails
  with EINVAL when pathToClaudeCodeExecutable resolves to a `.cmd` shim
  (CVE-2024-27980). This mirrors what claude-agent.ts does in production.
- 3 cleanup sites (event-stream, integration, sdk-behavior): swallow
  EBUSY/ENOTEMPTY/EPERM around rmSync. The maxRetries window doesn't
  reliably win the Windows cwd-lock race after Claude exits; the OS will
  reap the tmpdir.
2026-05-08 17:43:15 +07:00
Mohamed Boudra
b131876e69 test(server): cover .exe/.cmd/.bat end-to-end launch on Windows
Add three end-to-end tests in spawn.launch-regression.test.ts that exercise
findExecutable -> spawnProcess for each Windows-supported extension. Each
test creates a uniquely-named binary in a tempdir, prepends it to PATH,
verifies findExecutable returns the fixture path, then launches via
spawnProcess and asserts an exact JSON-arg roundtrip plus stdout/exit code.

The previous "does not detect a PowerShell shim from PATH" test is removed:
its expectation was satisfied via PATH-prepend (not isolation), so global
claude.CMD on CI made it return non-null. The supported-extension contract
matches which's hardcoded fallback (.EXE;.CMD;.BAT;.COM); .ps1 is not
supported by spawnProcess and not surfaced by which without explicit PATHEXT.

Also add maxRetries: 5 / retryDelay: 100 to rmSync cleanups in three Claude
integration tests to absorb the brief Windows cwd lock after Claude exits.
2026-05-08 17:24:52 +07:00
Mohamed Boudra
b7d2e6d3ec Revert "ci: gate Claude Code install to Linux server-tests"
This reverts commit 09ed46b997.
2026-05-08 16:28:49 +07:00
Mohamed Boudra
09ed46b997 ci: gate Claude Code install to Linux server-tests
Installing claude-code globally on the Windows server-tests runner
exposed 12 pre-existing Windows-specific test failures that were
silently skipped before (rmdir EBUSY during cleanup, .CMD spawn
EINVAL via the SDK, plus the launch-regression test that asserts
claude is *not* on PATH).

The Linux e2e tests (model-catalog, live-preferences, models) are
the only ones that needed the binary to flip isAvailable() true;
none of those run on Windows. Skip the install on Windows so those
Windows compatibility gaps stay surfaced separately and don't block
the SDK upgrade.
2026-05-08 16:26:43 +07:00
Mohamed Boudra
a4d365c8f2 fix server-ci: install only claude-code, set SDK path in contract test
Two regressions from the previous CI install:

- Installing opencode-ai globally in server-ci/server-tests caused
  opencode-agent.test.ts (gated on \`hasOpenCode\`) to start running
  for the first time and surface real but unrelated test breakage.
  Trim the install to claude-code, which is all that's needed for the
  Claude e2e tests that motivated the change.

- claude-agent.integration.test.ts's supportedModels test calls the
  SDK's \`query()\` directly (bypassing ClaudeAgentClient) and didn't
  pass \`pathToClaudeCodeExecutable\`. The 0.2.71 SDK had a bundled
  cli.js fallback; 0.2.133 doesn't, so on Ubuntu CI it tries the
  per-platform binary path (\`...claude-agent-sdk-linux-x64-musl/claude\`)
  which isn't installed via the \`claude-code\` global. Resolve the
  binary explicitly in the test and pass it as the SDK option.
2026-05-08 16:14:28 +07:00
Mohamed Boudra
13538dd710 ci: install claude-code globally in server-tests
server-tests in ci.yml and Server CI both need claude on PATH for the
e2e tests that probe model catalogs and live preferences. cli-tests
already installs the trio (claude-code, codex, opencode-ai); server
tests now match.
2026-05-08 16:05:30 +07:00
Mohamed Boudra
e60350be08 refactor(claude-agent): inject binary resolver via client constructor
The session previously called resolveClaudeBinary() directly in
buildOptions(), with no seam for tests. CI ran without claude on PATH,
so every test that exercised buildOptions hit the production throw.

Apply the ports-and-adapters pattern at the client boundary (the same
place tests already configure queryFactory):

- ClaudeAgentClient takes optional resolveBinary, defaults to
  resolveClaudeBinary which throws "Claude binary not found..." when
  the executable is missing — preserving production fail-fast.
- Client passes resolveBinary through to ClaudeAgentSession options.
- buildOptions calls this.resolveBinary() instead of importing the
  free function directly.
- Tests construct the public client with a stub resolver. No
  __claudeAgentInternals export, no reaching into the session.

User-facing fail-fast still lives where it always did — AgentManager
gates start/resume/draft flows on client.isAvailable(). The throw in
resolveClaudeBinary is defense-in-depth that mirrors Codex.
2026-05-08 15:55:14 +07:00
Mohamed Boudra
b0c36f2bab test: Claude isAvailable now reflects PATH binary, no SDK fallback 2026-05-08 15:28:46 +07:00
Mohamed Boudra
d708f099be fix(claude-agent): don't fail-fast in buildOptions
resolveClaudeBinary threw eagerly inside buildOptions, breaking unit tests
that mock the SDK at the module level (env, redesign, interrupt-restart-
regression suites). The user-facing fail-fast already lives in isAvailable();
in buildOptions, fall back to omitting pathToClaudeCodeExecutable so the SDK
surfaces its own error if the binary truly is missing.
2026-05-08 15:25:57 +07:00
Mohamed Boudra
cb2fe91998 fix CI fallout from claude-agent-sdk 0.2.133 upgrade
- mcp-server tests: MCP SDK 1.29 renamed RegisteredTool.callback → handler
- ci: install @anthropic-ai/claude-code globally for cli-tests, matching
  codex/opencode now that bundled fallback is gone
2026-05-08 15:18:24 +07:00
github-actions[bot]
d7820b7a84 fix: update lockfile signatures and Nix hash 2026-05-08 08:08:58 +00:00
Mohamed Boudra
fb0fbb79f6 upgrade claude-agent-sdk to 0.2.133, drop bundled binary
Bumps @anthropic-ai/claude-agent-sdk from ^0.2.71 to ^0.2.133. The new SDK
hardens handleControlRequest against post-close transport writes (v0.2.94+),
which fixes the unhandledRejection that crashed the daemon when the SDK fired
a fire-and-forget control_request after we'd torn down the transport.

The SDK now ships per-platform Claude Code binaries via optionalDependencies
(~210 MB each). Paseo requires user-installed `claude` on PATH instead — same
posture as Codex/OpenCode:

- after-pack now prunes @anthropic-ai/claude-agent-sdk-* from the Electron
  bundle, matching the existing per-platform pruning for onnxruntime, sharp,
  and node-pty.
- claude-agent.ts gains resolveClaudeBinary() (mirrors resolveCodexBinary):
  throws with install instructions when `claude` is missing, instead of
  silently falling back to the bundled binary.
- isAvailable() now checks isCommandAvailable("claude") instead of always
  returning true.
2026-05-08 15:07:40 +07:00
Mohamed Boudra
c97c000d9c paseo-epic: lock requirements early and gate on them
Adds a pre-start preferences step (onboards users with no
orchestration-preferences.json), a capture-intent step that runs
even under --autopilot, an immutable Requirements block in the plan
that includes acceptance criteria, and a final lightweight check
that audits every requirement before deliver. Drops --no-grill.
2026-05-08 11:46:45 +07:00
Mohamed Boudra
c4e4a28bc0 Run server tests on Linux+Windows matrix (#809)
* Run server tests on Linux+Windows matrix

Replace the hand-curated Windows server test allow-list with a full-suite matrix run.\nBoth Ubuntu and Windows now run the same server test command with shared setup and secrets.

* Add isPlatform helper and gate Windows-hostile server tests

- Add a shared server test isPlatform helper.

- Migrate existing Windows-gated spawn, worktree, executable, and worktree-core tests to the helper.

- Gate Windows-hostile symlink, macOS path-normalization, and POSIX shell setup tests with skipIf.

* Replace POSIX shell calls in test fixtures with Node primitives

- Replaced test fixture mkdir/echo shell setup with fs mkdirSync/writeFileSync calls in touched server tests.

- Replaced git shell strings with execFileSync/spawnSync argv calls across checkout, worktree, MCP, script, and workspace git fixtures.

- Gated the directory suggestion symlink escape tests on Windows because those fixtures require POSIX symlink behavior.

* Fix Windows server-test failures and split POSIX-only suites into sibling files

- Fix Windows path/cwd assertions in terminal, session/workspace, git-service, checkout-git, MCP, logger, spawn, and registry bootstrap tests.\n- Keep terminal tests runnable on Windows by canonicalizing temp cwd fixtures and ensuring a Windows shell fallback.\n- Gate POSIX-only shell, signal, Unix socket, and git-worktree reuse fixtures that need dedicated Windows coverage later.

* Move POSIX-only test blocks into sibling .posix.test.ts files

- terminal.test.ts: moved PTY/bash interaction blocks into terminal.posix.test.ts.

- worktree.test.ts: moved git-worktree and teardown shell blocks into worktree.posix.test.ts.

- worktree-bootstrap.test.ts: moved setup shell and terminal-backed service blocks into worktree-bootstrap.posix.test.ts.

- worktree-core.test.ts: moved the POSIX-only worktree-core suite into worktree-core.posix.test.ts and removed the empty original.

- provider-availability.test.ts and file-explorer/service.test.ts: moved POSIX PATH/symlink blocks into sibling suites.

* Fix Windows 8.3 short-name, EBUSY cleanup, and node-pty shell-path failures in server tests

- Normalize temp home directories in directory suggestion assertions to avoid Windows short-name mismatches.

- Use Windows-valid terminal shells/cwds in terminal fixtures and wait for terminal-manager PTYs before cleanup.

- Replace hardcoded POSIX paths in workspace-git/MCP/loop fixtures with platform-resolved paths or command files.

- Gate the two explicitly Linux-only workspace-git watcher tests.

* Replace hardcoded POSIX paths with portable Node path constructions in server tests

- checkout-git.test.ts and worktree-session.test.ts: compare Windows temp paths with realpathSync.native to avoid 8.3 short-name mismatches.

- directory-suggestions.test.ts: canonicalize result and expected paths with realpathSync.native.

- session.workspaces.test.ts: replace literal /tmp and /Users fixtures with path.resolve/path.join constructions.

- workspace-git-service.primitive.test.ts, loop-service.test.ts, and mcp-server.test.ts: use canonical repo/temp paths and shell-safe relative verify commands.

* Fix loop-service verify-check shell and workspace-git-service path-separator on Windows

- Run the loop-service verify script through the current Node executable with a relative script path.

- Normalize the workspace-git-service expected repo cwd to forward slashes for the listWorktrees assertion.

* Fix loop-service worker PTY spawn on Windows

- Canonicalize the loop-service temporary root and workspace with realpathSync.native before worker agents use the path as cwd.

* Gate loop-service real-worker-PTY test on Windows

- Skip the real worker PTY loop test on Windows after ConPTY path resolution still fails with node-pty error 267.\n- Keep the test running on POSIX so the loop behavior remains covered.

* Stub getMetricsSnapshot in test mocks to suppress async-leak uncaught exceptions on Windows

- Add a no-op AgentManager metrics snapshot to the WebSocket notification test server stub.

* Fix terminal-manager Windows-path test to avoid PTY spawn into nonexistent dir

- Use an existing temporary cwd for the createTerminal absolute-path validation assertion so node-pty does not spawn into a missing Windows directory.
2026-05-08 10:57:22 +08:00
Mohamed Boudra
39e461b872 perf(website): render hero text immediately 2026-05-07 23:38:32 +07:00
Mohamed Boudra
734e15e5a3 perf(website): cache GitHub release metadata in KV 2026-05-07 23:22:48 +07:00
Mohamed Boudra
90bf6571d6 Revert "perf(website): avoid blocking root render on GitHub"
This reverts commit 89a500cd3e.
2026-05-07 23:10:26 +07:00
Mohamed Boudra
90fe71bd54 chore: ignore generated TanStack files 2026-05-07 23:06:48 +07:00
Mohamed Boudra
89a500cd3e perf(website): avoid blocking root render on GitHub 2026-05-07 23:06:27 +07:00
Mohamed Boudra
a1ac402154 docs(website): add provider pages and refresh docs 2026-05-07 22:52:26 +07:00