The published nightly omits both MarkdownRenderers type files. Represent them as new files so patch-package succeeds on a fresh npm install, matching the already patched development package.
Prepare original source blocks with source-position edits from the assistant parser. Keep reference scope and live/history projection consistent, and retain link text when dispatching workspace file actions.
* fix: reject a question permission that would deliver no answer
Answering an `AskUserQuestion` permission in any shape but
`updatedInput.answers` keyed by one of its questions resolves as `allow` and
delivers nothing. The waiting agent is told "The user did not answer the
questions." — an affirmative falsehood rather than silence, so neither side can
see that anything failed. The responder sees success; the agent believes the
human declined and acts on that.
The field it is most natural to reach for cannot work. `selectedActionId` is
`z.string().optional()` with no membership check, and is read only for `plan`
kinds; a question advertises `actions: undefined`, so there is no action id to
name in the first place.
It is also unrecoverable. `respondToPermission` deletes the request from
`pendingPermissions` on entry, so the malformed answer consumes it and the retry
fails with "No pending permission request". The guard runs before that delete,
and `AgentManager` drops its own copy only once the call resolves, so both maps
survive the throw and the request stays answerable.
The answer contract is per provider, so the guard is in the Claude provider
rather than in `AgentManager`. Claude keeps an answer only when its key is a
question's full text or its header and its value is a non-empty string; Codex's
`mapCodexQuestionResponseByHeader` reads headers only and deliberately selects
each question's first option when nothing maps, which a shared guard would have
rejected; OpenCode reads headers only as well.
`resolveClaudeAskUserQuestionAnswers` is extracted from
`normalizeClaudeAskUserQuestionUpdatedInput` with no change in behaviour, and
both it and the guard now use it. So "would this deliver anything" is answered
by the code that does the delivering, rather than by a second copy of its rule —
which matters more than it looks, because that rule has two details easy to
restate wrongly: `readNonEmptyString` tests `value.trim()` for emptiness but
returns the *original* string, so a question named `" Which colour? "` is keyed
by that padding; and an empty `updatedInput.questions` array is a list that wins
over the request's own questions and maps nothing, not an absent one.
Verified on a live daemon over the MCP `respond_to_permission` path against a
seat whose question text is literally `" Which colour? "`: `selectedActionId`,
the trimmed key, an empty echoed question list and a numeric answer are each
rejected with the request still pending, and
`{"answers":{" Which colour? ":"Ochre"}}` then reaches the agent.
One known limit: the WebSocket path (`session.ts handleAgentPermissionResponse`)
catches the throw and emits an `activity_log` error rather than failing the
call, because a permission response is modelled as a notification. So
`paseo permit allow` still prints `allowed` and exits 0 for a rejected shape.
The destructive half is closed on every path — the request survives and the
agent is never told a falsehood — but closing the reporting half needs a
protocol change.
* test(claude): cover the question-answer-required retry path
A non-deliverable answer to an AskUserQuestion permission is rejected, but
the pending request survives it: a corrected retry against the same request
id is accepted, and the waiting agent receives the answer.
Mutation testing on a fork found that the existing suite does not pin this
ordering — moving the deliverability check to run after the request is
deleted from pendingPermissions still leaves every existing test green. This
test reds against that change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Thomas Van <minhthong@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: paseo-bot <paseo-bot@users.noreply.github.com>
* fix(server): name an imported Claude agent after its /rename
Importing a Claude session named the agent after its first prompt even when the
session had been renamed in the CLI, so the name had to be set again by hand.
#4216 fixed the Import Session listing; the imported agent still kept the
prompt.
Read the last usable `custom-title` from the transcript and pass it as the
imported config title. The record is re-emitted on every save, so searching
backwards from the end of the file finds the current name without walking a
transcript that can reach tens of megabytes.
Only an explicit rename is carried over. Agents are named after their first
prompt line since #1563, so adopting the generated `ai-title` here would put
that LLM summary back.
Refs #2513
* refactor(server): drop unused generality from the Claude title reader
readLastClaudeTitle took content, type and field so it could read both
custom-title and ai-title records, which the listing needed before #4216 took
that over. Only the custom-title read is left, so the two extra parameters are
dead flexibility that also broke the single-object-parameter rule in
packages/server/CLAUDE.md.
Collapse it to readLastClaudeCustomTitle(content) and pull the record type into
a constant so the needle and the type check share one literal.
Hooks skipped: the repo-wide typecheck fails on two pre-existing Electron
typing errors in packages/desktop/src/main.ts that reproduce with this change
stashed. packages/server typecheck, lint and format all pass.
* test(server): drive the real resume path in the import title tests
The tests assigned an inline arrow function to client.resumeSession to keep
Claude Code from spawning. Importing replays the transcript from disk and never
starts a process, so the stub was never needed. Dropping it runs the real
resume path, which is what docs/testing.md asks for, and removes the arrow
assignment that docs/coding-standards.md rules out.
Hooks skipped: the repo-wide typecheck fails on two pre-existing Electron
typing errors in packages/desktop/src/main.ts that reproduce with this change
stashed. packages/server typecheck, lint, format and the 83 claude tests pass.
* fix(server): narrow the transcript read error instead of asserting it
The ENOENT check cast the caught value to NodeJS.ErrnoException to reach
`code`, which is the kind of assertion docs/coding-standards.md rules out:
narrow with `if`, do not silence the compiler. Add the same local type guard
pid-lock.ts already uses and test through it.
Behaviour is unchanged. A value that is not an Error still falls through to
the rethrow, exactly as reading `code` off the assertion did.
Hooks skipped: the repo-wide typecheck fails on two pre-existing Electron
typing errors in packages/desktop/src/main.ts that reproduce with this change
stashed. packages/server typecheck, lint, format and the 83 claude tests pass.
* fix(server): let claudeProjectDirSync resolve the Claude config dir
The import title reader built the config dir itself through os.homedir(),
but os is no longer imported here, so the fallback threw a ReferenceError
whenever CLAUDE_CONFIG_DIR was unset. claudeProjectDirSync already resolves
the same default.
* test(server): give the import title helpers object parameters
* fix(server): preserve imported session renames across history locations
The import title reader must use the same provider environment and transcript
lookup as history replay. Otherwise an imported session has its history but
loses its explicit name when its configuration directory or project changed.
Co-authored-by: platanus-kr <platanus.kr@gmail.com>
---------
Co-authored-by: platanus-kr <platanus.kr@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
ACP command updates discarded input hints before the autocomplete command list reached consumers. Preserve the advertised hint, retaining the empty hint for commands without input metadata.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: bwestlund17 <brian.westlund@proton.me>
* fix(gitea): prefix fork PR branches with the owner
Two forks with the same branch name (say patch-1) ended up on the same
local branch. The gitea service had no buildPrLocalBranchName or
defaultCheckoutRefs, github has both.
getPullRequestCheckoutTarget also fell back to refs/heads/<name> on
origin for fork PRs. Thats the base repo's branch, not the fork's, so
a failed refs/pull fetch checked out the wrong branch without an error.
fixes#3515
* refactor(forge): share fork branch naming with github
moved the owner normalizer and the branch name builder to
utils/change-request-checkout.ts, gitea and github both use it.
github changes a bit: a fork PR with an unknown or unusable owner (eg
deleted fork) now gets pr-N/<branch> instead of the bare branch name,
which could collide with a local branch the same way #3515 did.
* fix(gitea): PRs from a deleted fork are still cross repo
the API drops head.repo once the fork is gone, we read that as a same
repo PR and fetched refs/heads/<name> from origin
* fix(gitea): handle AGit PRs and deleted head branches
neither has a branch on origin. forgejo reports refs/pull/N/head as
head.ref for both, so skip the refs/heads fallback and dont track
origin.
also try refs/pull/N/head on upstream for clones where origin is your
fork, like github does. that list lives in the shared util now.
* test: resolver harness uses the real branch helper
plus deleted forks and same repo PRs without an origin branch
* fix(gitea): collapse pull-ref-only heads to pr-N in local branch names
buildForkLocalBranchName still used the raw headRef even when it was
just the pull ref (refs/pull/N/head), which agit prs and deleted head
branches report as their headRefName. that gave local branches literal
names like refs/heads/refs/pull/3/head.
* fix(gitea): surface hasHeadBranch on the pr checkout target
resolveForkCheckoutFacts already knew whether a pr had a real head
branch (agit flow, or head.ref == the pull ref both mean no). now that
signal reaches buildPrLocalBranchName so worktree names and local
branches stop being named after the raw pull ref.
covers same-repo, fork with owner known, and deleted fork all with no
real head branch. added an end to end gitea-service test plus resolver
cases since the worktree slug follows localBranchName too.
* refactor(forge): one head ref kind on the checkout target
hasOriginHeadBranch and hasHeadBranch could describe contradictory
states (cross-repo checkout claiming an origin head branch). collapse
both into headRefKind: "branch" | "pull-ref" and derive origin
tracking in the resolver instead of storing it separately.
Co-authored-by: Alexander Adam <alexander.adam@vade.io>
* fix(worktrees): avoid branch namespace conflicts in fork checkouts
Fork owner prefixes can name an existing branch as a parent, which Git rejects even when the full proposed name is unused. Allocate names against parent and child refs, suffixing the conflicting segment while preserving existing branches and fetched commits.
* test(worktrees): make fork fixture line endings deterministic
Windows Git converts the fixture checkout to CRLF when core.autocrlf is enabled. Set the repository fixture to preserve its written LF bytes so the content assertion checks the fetched patch on every platform. Reproduced the failure locally with inherited core.autocrlf=true and verified the corrected setup against that same configuration.
---------
Co-authored-by: Alexander Adam <alexander.adam@vade.io>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>