Commit graph

102 commits

Author SHA1 Message Date
robopipi[bot]
d0fe213089
fix(coding-agent): apply context edits to branch summary input (#11680)
When a partial assistant reply fails and automatic retry succeeds, pi
appends a `context_edit` that hides the failed attempt from model
context. Branch summarization (`/tree` with summarize) built its input
from raw session entries through `getMessageFromEntry`, which ignored
`context_edit` entries. As a result, the summarizer received the hidden
attempt again. Content replacements were ignored the same way.

What the change does:
- `prepareBranchEntries` now collects the latest `context_edit` for each
target in the branch.
- It applies those edits to the entries it converts, using the same
`projectContextEntry` logic as the live context and compaction:
  - an omission (`replacement: null`) drops the entry;
  - a replacement swaps its content.
- `projectContextEntry` is now exported from `session-manager.ts` so
both paths share it.
- The existing branch-summary behavior is unchanged: tool results are
still skipped, and compaction or branch-summary entries still become
summary messages.

Testing:
- Added `applies context edits to branch entries` to
`test/branch-summarization.test.ts`. It covers one omitted assistant
entry and one replaced assistant entry. The test fails without the
change and passes with it.
- Ran `branch-summarization`, `branch-summary-extensions`,
`agent-session-branching` and the compaction tests: all pass.
- `npm run check`: biome and the coding-agent checks pass. `tsc` reports
6 errors in `packages/ai` model-catalog tests, and the same 6 errors
occur on unmodified `main`.
- End-to-end reproduction with the faux provider and `AgentSession`: a
failed attempt, a successful retry, then `navigateTree(target, {
summarize: true })`. Before the change, the summary request contained
the failed attempt's text. After the change, it contains only the
successful retry.

Fixes #11602

Requested by @davidbrai

Co-authored-by: triagent <triagent@localhost>
2026-11-02 10:55:30 +01:00
Petr Houška
1be312b46a
fix(coding-agent): compare paths case-insensitively on Windows (#11588)
Fixes #10488

## Problem

On Windows, `c:\Users\me` and `C:\Users\me` are the same directory, but
pi compared paths as exact strings (`===`, `startsWith`, map and set
keys). `realpathSync` keeps the input casing, so canonicalizing does not
help.

The reported case, with cwd `c:\Users\me\project` and home
`C:\Users\me`:

1. Ancestor discovery finds `c:\Users\me\.agents\skills`. The exclusion
of the user dir `C:\Users\me\.agents\skills` does not match, so global
skills load as project skills.
2. The same dir loads again as user skills. Dedup does not match either,
so every skill is reported as a name collision.
3. `hasTrustRequiringProjectResources` treats `~/.agents/skills` as a
project resource that requires trust.

The same pattern affected other places:

- Saved trust decisions in `trust.json` were not found when cwd casing
differed, so pi asked for trust again.
- With a custom session dir, `--continue`, resume by id, and the session
list hid sessions recorded with different cwd casing.
- Git worktree `AGENTS.md` shadowing failed when cwd casing differed
from git's paths.
- Local package matching (`pi remove c:\pkg` vs configured `C:\pkg`) and
package identity across global and project settings.
- Extension preload matching, skill scope classification, resource
source info, the config and session selector keys, and the session
import self-check.

Separately, home-to-`~` shortening used an unanchored `startsWith(home)`
in four places, so `/home/me2/x` was shown as `~2/x`.

## Fix

New helpers in `utils/paths.ts`, all based on one comparison key:

- `getPathComparisonKey(path)`: the resolved path, lowercased on
Windows. It does not touch the filesystem.
- `pathsEqual(a, b)`
- `isPathWithin(parent, child)`: true for equal paths or paths inside
`parent`, matching whole path segments.
- `shortenHomePath(path, home?)`: replaces a leading home dir with `~`.

The helpers pick `path.win32` or `path.posix` from `process.platform` at
call time, so tests can exercise real Windows paths (drive letters,
UNC).

`getResourcePathKey(path)` in `source-info.ts` is the dedup key for
resources. Synthetic identifiers (`builtin:<name>`, `<inline:...>`) stay
unchanged, because they are not filesystem paths and their lookup is
case-sensitive. File paths go through realpath plus the comparison key.

The helpers are used only for comparison; stored and displayed paths are
unchanged. Trust store lookups and `set` match keys case-insensitively
on Windows.

Unchanged on purpose:

- The package install-root guards (`resolveManagedPath`,
`pruneEmptyGitParents`, the `node_modules` dependency check) keep exact
string checks. Both sides come from the same root string, and exact is
the stricter option.
- Checks based on `path.relative()` (`getCwdRelativePath`, the changelog
containment check) were already correct: Node's `path.win32.relative`
ignores case.

NTFS can enable per-directory case sensitivity, but it is rarely used
outside WSL interop, so it is ignored (see the comment on
`getPathComparisonKey`).

## Tests

- `test/paths.test.ts`: Windows drive-letter, UNC and root cases, POSIX
case sensitivity, segment-boundary containment, and home shortening,
including root and UNC homes.
- `test/source-info.test.ts`: file paths fold case on Windows; builtin
and inline identifiers keep their case.
- `test/trust-manager.test.ts`: the `.agents/skills` exclusion with a
lowercase `HOME`, plus saved trust decisions matched and replaced across
casing (simulated win32).

An end-to-end test of the reported loader scenario is not feasible on
Linux: the loader resolves paths with POSIX `node:path`, and the
filesystem is case-sensitive.

`npm run check` passes. The coding-agent suite passes, except for 13
failures that also occur without this change: clipboard tool detection
in this environment, and one test that needs built `dist/` output.
2026-10-29 21:53:06 +01:00
robopipi[bot]
807a5c051f
fix(coding-agent): keep session memory in line with the file when a write fails (#11486)
When writing a session entry failed (ENOSPC, EBUSY, ETIMEDOUT on network
drives), `SessionManager._appendEntry` had already added the entry to
memory and made it the current leaf. The process kept running, and the
next entry written to disk pointed to a parent that was not in the file.
On resume, pi follows the history only back to that gap: everything
before it disappears. In #11460 that also dropped the first system
message, which declares the tools, so resume turned off every built-in
tool.

`_appendEntry` now undoes the in-memory append if writing fails, then
rethrows. The error reaches the caller as before; later entries chain to
the last entry actually on disk.

Reports of this problem:
- #11460: ENOSPC; resume dropped all built-in tools.
- #10166: failure injected with EBUSY; the next entry on disk points to
a missing parent.
- #10661: ETIMEDOUT on Windows + NFS; resumed sessions showed only the
last few messages. That report also describes NUL holes in the file,
which this change does not address.

**Testing**
- Two tests in `test/session-manager/file-operations.test.ts` make
writes fail with real file permissions: a read-only session file (failed
append) and a read-only session directory (failed first file creation).
They check that the entry is not kept, the leaf is unchanged, the next
entry chains to the last written one, and the reopened session's branch
contains both entries. Both fail without the change and pass with it.
- `test/session-manager/` and `test/default-tools-setting.test.ts` pass
(140 tests).
- `npm run check` passes.

Fixes #11460

Requested by @davidbrai

Co-authored-by: triagent <triagent@localhost>
2026-10-27 15:58:08 +01:00
triagent
8ddc44ec8b fix(coding-agent): write forked session through one fd
forkFrom reopened the target file with appendFileSync for every entry,
which is slow where open/close is expensive. fixes #11253
2026-10-23 21:24:38 +02:00
Armin Ronacher
b485fa3128 fix(tui,coding-agent): reduce render cost of theme changes and streaming
Theme previews re-render the whole transcript, and streaming re-renders
every frame, so per-line and per-session work dominated in long sessions.

- visibleWidth: fast path for ANSI-styled ASCII, allocation-free escape scanning
- Box: compare unpadded child lines and skip a second width measurement
- Markdown: reuse parsed tokens across invalidation
- footer: cache usage totals and context usage per session state
- bash results: cache the collapsed preview including the expand hint
- sanitizeBinaryOutput: single regex replace
2026-09-29 08:23:46 +02:00
David Brailovsky
ff72faba28 fix(coding-agent): save new session file at the first user message
A new session was only written to disk once the first assistant message
existed. If pi exited during the first turn (Ctrl+C twice while streaming,
or the process was killed), the user's prompt was lost and the session
never appeared in /resume.

The file is now created as soon as the session has a user or assistant
message. A session with only setup entries (model, thinking level) still
stays in memory, so starting pi and quitting without chatting leaves no
file. Forking uses the same rule through a shared helper, so the two paths
cannot drift apart and write the header twice (#1672).

closes #10000
2026-09-25 16:27:51 +02:00
Mario Zechner
466db0fecd feat: add canonical session context boundaries
Make SessionManager projections authoritative for provider requests, add append-only context edits and actionable turn boundaries, and preserve existing queue scheduling during continuation and recovery.
2026-09-21 16:59:20 +02:00
Armin Ronacher
dd01f5b240 fix(coding-agent): speed up recent session discovery 2026-09-20 00:50:10 +02:00
Armin Ronacher
dfbf793b78 feat(coding-agent): load session picker progressively 2026-09-20 00:48:26 +02:00
Armin Ronacher
c596d09d9c
feat(coding-agent): add prompt cache warming (#9668) 2026-09-19 21:18:15 +02:00
Armin Ronacher
9e05370b29
Mid conversation system messages (#9548)
This change makes system prompt text and tool changes part of the transcript rather than silently rewriting its starting conditions. This lets Pi record when instructions changed or tools became available, restore that state after resuming or navigating branches, and preserve cached prompt prefixes where the upstream supports it.
2026-09-16 15:54:07 +02:00
metaist
9b791a4cc1
fix(coding-agent): avoid transcript scans for exact session IDs (#9601)
Fixes #9440
2026-09-16 11:15:16 +02:00
Julien Barbay
2b768ba42c
feat(coding-agent): ingest external entries in in-memory sessions (#8980)
* feat(coding-agent): ingest external entries in in-memory sessions

SessionManager.inMemory() takes the entries to start from, so a session
held outside the filesystem can be resumed as it was stored.

Replaying through appendMessage/appendCompaction cannot do this: ids are
minted fresh, so every stored firstKeptEntryId, targetId and fromId has to
be remapped, timestamps are rewritten, and parentId always comes from the
current leaf, so a branched tree needs the original branch() choreography
reproduced call by call.

The entry-adoption half of _setSessionFile becomes _loadEntries, which the
constructor also uses when there are entries but no file. Entries carrying
a session header keep it; without one, the header comes from the options
and the entries become its body.

* fix(coding-agent): carry the stored version through restored entries

inMemory() took SessionEntry[], which excludes SessionHeader, so an
external store could not pass the header it saved and the header branch of
_loadEntries was unreachable through the public API. A restore therefore
always synthesised a current-version header, and the migration that ran
next read that header and skipped: a v2 session kept its hookMessage roles
forever.

Widening to FileEntry[] lets the stored header through, version included.
Migration moves inside the header branch, where a version exists to compare
against; without a header the entries are adopted as current-version, which
is the only thing they can mean.
2026-09-03 10:49:41 +02:00
yaogangqiang
2631b25c34
fix(coding-agent): preserve compaction boundary when forking (#8990)
Fixes #8989
2026-09-03 10:47:40 +02:00
David Brailovsky
0b5ee5d8b3 fix(coding-agent): repair unterminated session files
Append a newline after loading a valid session so subsequent entries cannot fuse with an unterminated tail.\n\nFixes #8345
2026-08-26 12:07:30 +02:00
David Brailovsky
d711bd5f0a fix(coding-agent): preserve branch summary source leaf
Record the pre-navigation leaf in branch summary fromId instead of the destination node.
2026-08-19 12:02:37 +02:00
Anders Bech Mellson
ab0dc51fcf
fix(coding-agent): use APP_NAME in user-facing messages (#8067)
APP_NAME is derived from piConfig.name so a rebranded distribution shows
its own name, and roughly 90 call sites already use it. Twelve
user-facing strings hardcoded "pi" instead, so those messages named the
upstream binary regardless of piConfig.name:

- the trust project folder prompt
- "Restart pi for this to take effect." after a saved trust decision
- the "Start without extensions" hint on an extension load failure
- the invalid session file error
- the executable location in the self-update-unavailable notice
- the three auth command usage strings and the unknown auth command
  error, which are printed next to `${APP_NAME} --help` in main.ts and
  so contradicted it
- "is configured outside pi." in the provider setup dialog
- the uncaughtException line

Output is unchanged for pi itself: piConfig has no name, so APP_NAME
falls back to "pi".
2026-08-14 11:26:20 +02:00
muyiyr
da66636cc4
fix(coding-agent): discover sessions through symlinked directories (#7552)
fixes #7497
2026-08-03 18:34:12 +02:00
David Brailovsky
2fd3868401
add usage info to branch summary, compaction and tool result entries (#6671)
* add usage info to branch summary entries

* add usage to compaction entries

* allow custom tools to report llm usage in tool results

* allow observing and patching usage in tool_result hooks

* agent-harness: save usage in entries

for compaction, branch summaries and tool results
2026-07-20 16:41:43 +02:00
Alexey Zaytsev
d2f8dafb0f
fix(ai,agent,coding-agent): share UUIDv7 and use for Codex (#6834) 2026-07-20 01:23:47 +02:00
Armin Ronacher
f1c587dde3 fix(coding-agent): avoid duplicate session reads
fixes #6793
2026-07-19 20:18:49 +02:00
Armin Ronacher
8c0ccd14b3
fix(ai,agent,coding-agent): normalize null message content at ingestion boundaries (#6343)
The Message types require content to always be present, but untyped JS
extension tools, hand-built histories, and old or hand-edited session
files can violate that contract, crashing rendering, compaction, and
provider request conversion with 'content is not iterable'.

Normalize null/missing content to an empty array at the ingestion
boundaries instead of guarding every consumer:

- transformMessages (choke point before every provider request)
- createToolResultMessage in the agent loop
- session entry loading (message and custom_message entries)
- extension custom messages (sendCustomMessage, before_agent_start)
- message_end extension replacements

fixes #6259, fixes #6276
2026-07-06 20:47:08 +02:00
Vegard Stikbakke
6efc09b7eb fix(coding-agent): clear label timestamp cache on new sessions
closes #6354
2026-07-06 15:31:32 +02:00
Mario Zechner
ba10b60b51 fix(coding-agent): add entry renderers for session entries 2026-07-01 11:30:26 +02:00
Mario Zechner
0d145e895c fix(coding-agent): shorten invalid session error
Follow-up to #6002
2026-06-25 15:20:08 +02:00
Mario Zechner
543710f643 fix(coding-agent): reject invalid session files
closes #6002
2026-06-25 15:16:18 +02:00
Armin Ronacher
e454f50b48 fix(coding-agent): allow session id for no-session runs
closes #6070
2026-06-25 14:44:44 +02:00
Mario Zechner
7fedc3324e
Merge pull request #5999 from haoqixu/fix-5996
fix(coding-agent): normalize session names
2026-06-23 15:15:52 +02:00
haoqixu
7c1ef87756 fix(coding-agent): normalize session names 2026-06-23 19:14:02 +08:00
Armin Ronacher
a1da88aed4 fix(coding-agent): make session path traversal linear
Avoid quadratic path construction when walking deep session branches. On a 600k-entry pathological session, path construction improved from about 20.3s with Array.unshift() to about 35ms with push() plus reverse().

Refs #5804.
2026-06-20 20:29:15 +02:00
Armin Ronacher
adf567c1c6 fix(coding-agent): rechain fork paths without labels
closes #5669
2026-06-12 18:50:31 +02:00
Mario Zechner
3911d6f5cd fix(coding-agent): stream large session files
Some checks are pending
CI / build-check-test (push) Waiting to run
closes #5231
2026-05-30 21:21:42 +02:00
Armin Ronacher
4b4641c6b0 fix(coding-agent): scope custom session dir lookups 2026-05-28 23:25:33 +02:00
Armin Ronacher
52dc08c1f7
feat(session): Explicit session id naming (#5076) 2026-05-27 21:32:10 +02:00
Armin Ronacher
c100620bf4
fix(coding-agent): Clean up Path Handling (#4873) 2026-05-22 11:25:15 +02:00
Armin Ronacher
ae9450dc51 chore(ts): use source import extensions 2026-05-20 00:04:03 +02:00
Mario Zechner
758004d1bb fix(coding-agent): cap resume session metadata loads
closes #4583

closes #4591
2026-05-16 23:08:12 +02:00
Armin Ronacher
c00f1cf130 fix(agent): correct uuidv7 sequence handling 2026-05-13 15:53:53 +02:00
Armin Ronacher
2829146dde
chore(deps): Kill small dependencies (#4467) 2026-05-13 10:44:56 +02:00
Mario Zechner
3e5ad67e0f chore: migrate pi packages to earendil works scope 2026-05-07 15:59:42 +02:00
Mario Zechner
018b40c30c fix: align OpenAI cache affinity and use uuidv7 session ids 2026-04-14 23:20:13 +02:00
Mario Zechner
080af6fc06 fix(coding-agent): handle missing session cwd 2026-04-05 21:24:32 +02:00
warren
84d2b51a2e
feat(coding-agent): add label timestamps to the session tree (#2691) 2026-03-30 15:56:37 +02:00
Mario Zechner
6c8ac01947 fix(coding-agent): respect agentDir for sdk session paths closes #2457 2026-03-20 12:12:18 +01:00
Aliou Diallo
18d90b5c48
fix(coding-agent): handle empty session titles and session info metadata (#2304)
* fix(coding-agent): handle empty session titles in tree

* fix(coding-agent): ignore session info in summaries

* docs: update changelog for PR #2304
2026-03-17 16:22:04 +01:00
Haoyu Zha
f04d9bc428
feat(session-manager): allow supplying custom session ID in newSession() (#2130)
* feat(session-manager): allow supplying custom session ID in newSession()

Add optional `id` field to `NewSessionOptions`. When provided, this ID
is used as the session ID instead of generating a random UUID. Existing
callers are unaffected since the field is optional and falls back to
`randomUUID()`.

Closes #2097

* test(session-manager): add tests for custom session ID in newSession()

Verify that newSession() uses the provided id when supplied, falls back
to randomUUID() when omitted, and includes the custom id in the session
header.
2026-03-14 01:34:48 +01:00
Mario Zechner
2f64df1e52 fix(coding-agent): prevent duplicate session headers when forking from pre-assistant entry
createBranchedSession() wrote the file and set flushed=true even when the
branched path had no assistant message. The next _persist() call saw no
assistant, reset flushed=false, and the subsequent flush appended all
in-memory entries to the already-populated file, duplicating the header
and entries.

Fix: defer file creation when the branched path has no assistant message,
matching the newSession() contract. _persist() creates the file on the
first assistant response.

closes #1672
2026-02-27 22:18:26 +01:00
Mario Zechner
b5be54b8ef fix(coding-agent): persist user message after fork
When hasAssistant guard returns early, set flushed=false so
the next persist (after assistant arrives) writes all entries
including the user message.
2026-02-04 13:35:45 +01:00
Mario Zechner
13ac63c3cd fix(coding-agent): fork writes to new session file, not parent (fixes #1242)
- Store previousSessionFile before creating branched session
- Update sessionFile after writing new branch file
- Pass parentSession when forking from first message
- Add --local to git config in tests to prevent repo escape
2026-02-04 13:22:43 +01:00
Petr Baudis
42d54e0d1c feat(coding-agent): add threaded sort mode to /resume with tree display
Adds 'Threaded' as a new sort mode (now default) that displays sessions
in a tree structure based on parent-child relationships. The tree is
sorted by root session's modified date. Other sort modes (Recent, Fuzzy)
continue to show flat lists.

Sort mode cycles: Threaded -> Recent -> Fuzzy -> Threaded
2026-01-31 23:17:05 +01:00