* fix(relay): use TLS for any port-443 relay endpoint
`shouldUseTlsForDefaultHostedRelay` only returned true for
`relay.paseo.sh:443`. Self-hosted relays on port 443 connected via
`ws://` instead of `wss://`, causing HTTP 400 from the TLS server.
Port-based detection: port 443 → wss, everything else → ws.
* Test port-based hosted relay TLS detection
Cover the new behavior: any port-443 endpoint (hosted Paseo or
self-hosted) and IPv6 hosts return true, non-443 ports and malformed
input return false.
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Auto-naming should only run during worktree creation (mnemonic on create,
async LLM rename moments after). Creating a new agent inside an existing,
properly-named branch must not trigger a rename.
Adds optional `tryHandleOutOfBand` on AgentSession so providers can
handle slash commands as side effects without allocating a turn or
tripping the activeForegroundTurnId gate. Codex provider implements it
for /goal (set/pause/resume/clear), version-gated to codex >= 0.128.0
with `--enable goals`. Mid-turn /goal pause now works alongside a live
turn against the same thread.
E2E test against real codex 0.128.0 covers set + mid-turn pause + clear.
* refactor(server,app): lift return types and parse at boundaries (T2 typeaware production sweep)
* fix(server): align acp-agent test assertions with pino call shape
Introduce createStub<T> (Proxy-based) and asInternals<T> (single-cast wrapper)
in test-utils/class-mocks.ts to replace all as unknown as casts in server test
files that construct fakes of private-field classes or access class internals.
- class-mocks.ts: one justified as unknown as T cast inside createStub; Proxy
throws on any unstubbed method call, adding real runtime safety vs bare cast
- session-stubs.ts: all 19 as unknown as casts replaced with createStub/asInternals
- websocket-server.notifications.test.ts + relay-reconnect.test.ts: createStub
for HTTPServer / pino.Logger / AgentManager stubs; asInternals for internals access
- snapshot-mutation-ownership.test.ts: createStub for SessionOptions field stubs
- session.workspace-git-watch.test.ts: createStub for all SessionOptions fields;
asInternals for SessionInternals access
- session.workspace-resolution-invariants.test.ts: createStub + asInternals
- acp-agent.test.ts, claude-agent.redesign.test.ts, codex-app-server-agent.test.ts,
acp-wrapper-smoke.test.ts: asInternals for all internal-access casts
Errors cleared: ~80 as unknown as casts removed across 11 files.
Lint: 0 errors on all 11 files. Typecheck: green.
Cluster T1.b — first wave of no-unsafe-type-assertion fixes across agent
provider and MCP test files. Clears all type-aware lint errors in 6 files;
remaining 4 files (class-mock patterns) deferred to T1.b2.
Fixes all reachable typescript-eslint(no-unsafe-type-assertion) errors
in the four in-scope test files by:
- Adding Zod schemas (WireEnvelopeSchema, BinaryFrameSchema,
RuntimeMetricsLogSchema) and helpers (parseSentEnvelope, parseSentFrame,
assertStr) to parse wire frames instead of casting JSON.parse() results
- Replacing `JSON.parse(x) as { ... }` with Zod-validated helpers
- Replacing `mock.sent[N] as string` / `String(mock.sent[N])` with assertStr()
- Replacing `frame as Uint8Array` with direct type annotation (Buffer extends Uint8Array)
- Replacing `session.args.onMessage as (() => void) | undefined` with typeof
function guard and direct invocation
- Replacing `metricsCall![0] as RuntimeMetricsLog` with RuntimeMetricsLogSchema.parse()
Remaining `as unknown as SomeClass` patterns in constructor mocks and
`client as unknown as { ... }` internal-access patterns are out of scope:
they require extracting interfaces from concrete classes (AgentManager,
AgentStorage, FileBackedChatService, etc.) with private members, or
refactoring the client API to expose internal methods — neither is safe
to do in a test-only sweep.
* refactor(agent-providers): add type guards and fix type-aware lint errors in diagnostic-utils, generic-acp-agent, and tool-call-detail-primitives
* fix(server): defer PTY onExit to allow pending data events to fire
On Linux, node-pty's onExit callback can arrive before the last buffered
PTY data chunk is delivered via onData. Wrapping finish() in setImmediate
gives libuv's I/O poll phase a chance to flush remaining PTY reads before
the promise resolves, preventing tail bytes from being silently dropped.
Fixes a flaky worktree-bootstrap test that reliably reproduced on Linux CI.
* unslop: remove dead guard, fix import order, trim comment
- generic-acp-agent: remove isNonEmptyStringArray and its guard; the
constructor parameter is already typed [string, ...string[]] so the
check can never fire
- provider-registry: move isNonEmptyStringArray below the import block
instead of between two import groups
- worktree: condense 3-line comment to one line per project convention
Adds isObjectRecord, isUnknownArray, isChildProcessWithStreams, and
isImageMimeType type predicates to eliminate all no-unsafe-type-assertion
violations surfaced by typeAware lint mode. Fixes floating promise, adds
throw after exhaustive switch, and widens readCompactionMetadata to accept
unknown so callers need no casts.
* fix(app): make e2e setup an auto fixture so it runs for the first test of every spec
The fixtures.ts beforeEach was declared at the top level of a non-test
fixture file. Playwright sometimes skipped it for the first test of a
subsequent spec when multiple specs ran in the same worker — that test
hit page.evaluate without the seed nonce or daemon registry in
localStorage and failed (then passed on retry, masking the bug behind
retries: 1). Reproduced locally by running sidebar-workspace then
startup-loading: the first startup-loading test got no fixture setup
and threw "Expected e2e seed nonce".
Move the setup into an `auto: true` fixture, the canonical Playwright
pattern for running setup for every test in a workspace. The afterEach
console-attachment is folded into the same fixture's teardown.
* refactor(server): simplify nullish handling in workspace-git-metadata
Drop the explicit `null` arg in `deriveProjectSlug(input.cwd, null)` —
the second parameter already defaults to `null`. Inline the
`repoName && repoName.length > 0` check in `parseGitHubRepoNameFromRemote`
since `pop()` on a non-empty array returns a non-empty string here, and
`|| null` covers the empty-string case directly.
* schedule: add `paseo schedule update` to edit schedules in place
Editing a schedule today requires delete+recreate, losing run history
and the schedule id. This adds an additive RPC and CLI command that
patches name, prompt, cadence, new-agent target fields, max-runs, and
expires-in without touching runs or in-flight executions. nextRunAt is
recomputed only when the cadence actually changes.
* schedule(cli): share cadence flag parser between create and update
Both paths were turning --every/--cron into a ScheduleCadence with
near-identical code. Extract `parseCadenceFromFlags` so the literals
and the exclusivity check live in one place; create wraps it to
require a value, update lets it stay optional.
Reconciliation overwrote correctly-set project display names with the
directory name for non-GitHub remotes (e.g. gitlab.com/acme/app), because
buildWorkspaceGitMetadataFromSnapshot only handled GitHub URLs while the
registry-model layer used the more general deriveProjectGroupingKey path.
Reuse that path here so both layers agree on the owner/repo display name.
Replace unsafe 'JSON.parse(content) as Type' patterns with proper Zod schema
validation. This fixes type-aware lint errors and provides runtime safety.
- pid-lock.ts: Add pidLockInfoSchema and parsePidLockInfo helper
- package-version.ts: Add packageJsonSchema and parsePackageJson helper
- relay-transport.ts: Add isRecord type guard to avoid unsafe type assertion
* feat(server): derive owner/repo display name for any remote host
Generalize deriveProjectGroupingName so any remote:<host>/<segments...>
project key returns the last two path segments (owner/repo) instead of
just the trailing segment. Path-fallback for non-remote keys is unchanged.
Brings GitLab, Gitea, Bitbucket, and self-hosted remotes to parity with
the prior github.com-only behavior — no separate special-case needed.
* feat(app): show projects from any git remote in Projects settings
Remove the isSupportedProjectKey filter so workspaces with non-GitHub
remotes (GitLab, Gitea, Bitbucket, self-hosted, ssh-style) appear in the
Projects list and route to the project settings screen. The daemon RPCs,
config schema, and registry were already host-agnostic; this lifts a
client-side filter that hid them.
Also remove the now-vestigial hiddenUnsupportedRemoteCount field from
ProjectSummary, BuildProjectsResult, and UseProjectsResult — once the
filter is gone, the count is always zero and the field is dead state.
This is an internal app-package type, not a wire schema, so deletion is
safe.
* chore(app): simplify Projects empty state to "No projects yet"
Drop the "Non-GitHub remote projects aren't supported yet" empty-state
branch — it can no longer fire now that any git remote is shown. The
empty state is unconditional now.
* test(app): cover non-GitHub remote in projects-settings e2e
Add a fixture that creates a temp git repo with origin pointing at a
gitlab.com URL and exercises the same paseo.json read/edit/save flow
already covered for local repos. Verifies the project surfaces with the
"acme/app" display name and that the round-trip persists correctly.
Extracted the remote-setup branch in createTempGitRepo into a small
configureRemote helper to keep the main function under the cyclomatic
complexity limit.
---------
Co-authored-by: Mathias Kurz <mkurz@stamus-networks.com>
Interval schedules used to wait the full interval before their first run, which
made `--every 1d` feel broken for fresh schedules. Now `--every` fires
immediately on creation; `--cron` keeps waiting for the next slot. `--no-run-now`
and `--run-now` override the defaults, and the parser rejects redundant combos.
Adds `paseo schedule run-once <id>` to manually trigger a single run without
advancing cadence or recomputing completion. The new `runOnCreate` field and
`schedule/run-once` RPC are additive on the WebSocket schema.
The MCP create_agent tool inherited the parent agent's mode regardless of
provider, so a Claude parent in bypassPermissions spawning an opencode
child would feed an invalid mode to opencode and the new agent would die
in error state. The fix:
- Add an optional mode field to the agent-to-agent input schema.
- Validate any explicit mode against the target provider's available
modes; throw with the list when invalid.
- Inherit the caller's mode only when both sides use the same provider;
otherwise refuse with a helpful error listing the target's modes.
- Add scripts/lower-thinking-tone.mjs to scale the thinking-tone PCM
amplitude in-place; apply it at 0.1 so the cue is a soft indicator
instead of triggering the VAD via mic feedback.
- Drop the "Received first voice_audio_chunk" log that re-fires every
summary window because the chunk counter is reset.
- Match Spoke message icon and label sizing to the standard tool-call
badge.
- Append a spoken-input instruction so voice replies route through the
speak tool.
OpenCode subproviders (e.g. opencode/kimi-k2.6 on OpenCode Zen) emit
session.status:retry events with messages like "Internal server error"
when the upstream provider returns 5xx. opencode itself retries
indefinitely with backoff and never emits a terminal event for these.
Previously the adapter only surfaced retry messages on a small allowlist
of "fatal" tokens (insufficient balance, invalid api key, etc.) and
silently dropped everything else. The agent appeared hung from the
user's perspective — no message, no spinner update, nothing — until the
upstream eventually recovered or the user manually interrupted.
Forward every session.status:retry as a non-terminal timeline error
item so the user can see what opencode is doing, mirroring opencode's
own TUI. Drop the fatal-token allowlist: classifying which retries are
"really" terminal is opencode's job, and synthesizing turn_failed for
ones we guess at is misleading anyway because opencode keeps spending
upstream while we tell the user the agent is done.
Also a small design pass on the timeline error rendering:
- drop the redundant "Agent error" prefix (the message is descriptive)
- drop the colored box background (visual weight too high for a retry)
- align the icon vertically to the first text line (height+center)
- make the message text selectable so users can copy errors
Plumb --mode/--verify-mode through paseo schedule create and paseo loop run.
Mark each provider's unattended mode (claude bypassPermissions, codex/opencode
full-access, copilot autopilot) so loop and schedule services pick it by
default when no explicit modeId is given.
Add oxlint-tsgolint and configure typescript/no-unnecessary-type-assertion
to flag redundant `!` and `as Foo` casts. Type-aware mode is left off by
default to keep `npm run lint` fast; the rule sits configured for when we
turn type-aware on intentionally. Auto-fix removed ~283 redundant casts;
two manual touch-ups: a real tsgolint false positive in split-container.tsx
and a stale ChildProcess import after a double-cast collapsed.
Subagent task_notification system messages arrive without
parent_tool_use_id but with tool_use_id pointing at the parent's Task
call, so they slip past the sidechain router and render as top-level
"Task Notification" rows in the parent's timeline. Skip them when the
referenced tool_use is a Task call; parent-level background bash
notifications still flow through.
Translate Paseo's header-keyed AskUserQuestion answers into the full question-text keys Claude expects before resolving question permissions.
Also preserve the original question payload when UI callbacks return an answers-only updatedInput, and lock the behavior down with provider-flow regression coverage in claude-agent tests.
7c85341a moved first-agent branch auto-naming out of the worktree
service (sync slugify) into the workflow layer (async LLM call).
The MCP test stubs createPaseoWorktree, bypassing the workflow,
so the placeholder mnemonic branch is never renamed at this layer.
Replace the obsolete sync-slugify outcome assertion with a
placeholder-presence check; the auto-name logic itself is covered
in worktree-branch-name-generator.test.ts.
Plumbs persistSession through the structured response pipeline so
metadata, branch name, commit message, and PR text generators no
longer leave provider-side artifacts behind:
- Codex: passes ephemeral: true on thread/start (no rollout file)
- OpenCode: deletes the session via session.delete on close
- Claude: forwards persistSession: false to the SDK query
Adds a real-credentials e2e test per provider that asserts the
generated title is applied AND no persistence artifact is produced
(rollout for Codex, project JSONL for Claude, leftover session for
OpenCode).
Server: drop the synchronous branch-name generator dep, use the
worktree slug as the initial branch, and move first-agent branch
auto-naming to an async post-creation step backed by a structured
LLM call. Guards against renaming when the placeholder branch has
already been changed.
App: make workspace and worktree archive optimistic, rolling back
the local hide if the daemon call fails, and suppress incoming
workspace upserts while an archive is pending.
Spawn the shared OpenCode server in its own POSIX process group and terminate provider child process trees with graceful timeout escalation before forcing SIGKILL. Reuse the same helper for Codex app-server cleanup so process-tree behavior is covered consistently across providers.
Add cross-platform unit coverage for macOS/Linux process groups, Windows taskkill cleanup, and timeout escalation. Add the process-tree test to the Windows-critical CI allowlist; Linux full server tests already include it.
Refs #227
Update CHANGELOG.md in place per beta policy and add a compat note on the
`sub_agent.actions` field flagging it as drop-on-cleanup once clients
<= 0.1.65-beta.3 are out of the field.
With capability negotiation in place, the consumer-side tolerance pattern is unnecessary. Drop the dead readDeclaredClientCapabilities helper, inline the parsing in ClientCapabilities.fromHello, delete a tautological assertion in the relay reconnect test, and document the compatibility rules in architecture.md.