Commit graph

6875 commits

Author SHA1 Message Date
Mario Zechner
0104fef1e9 feat(chord): replace the facet and service runtime with the chord2 host runtime
Moves chord2 (contracts, objects, cells, roots, plugins, hot reload, memory/IPC/WebSocket links)
from p2 into packages/chord, with its tests (now on vitest) and docs. The root export is the new
runtime plus the shared base (Context, JsonValue, JsonRepresentation, Draft, copyJson, isJsonValue,
replicatedState and its types). ./node now holds the host manager, child control, process locks,
and nodeLoader. ./context and ./delta are unchanged; ./bundler is removed.

replicatedState stays a standalone module with the same exports. It no longer exposes the exact-op
subscription the removed service provider used, so listener failures of mutable states are always
reported asynchronously. The package depends on typebox instead of esbuild. The wire format stays JSON.
2026-10-12 10:41:59 +00:00
Mario Zechner
7472cda0d6 feat(coding-agent): remove the experimental server stack and the client, protocol, and server packages
Deletes packages/client, packages/protocol (CBOR codec), packages/server, coding-agent's
src/experimental (except source-resolver.ts, which source runs still preload), src/cli/experimental,
src/client, the example plugin, and their tests. coding-agent no longer depends on @earendil-works/chord.
The installer lock drops esbuild, so its platform-package sanity check goes too.
2026-10-12 10:41:59 +00:00
Mario Zechner
2ee5c1e56d docs(durable): drop the Chord facet usage guide and its test
The guide and chord-guide.test.ts used Chord's facet and service runtime, which is being replaced.
2026-10-12 10:41:59 +00:00
Armin Ronacher
148622bc6f fix(coding-agent): describe pi mcp login --timeout as a limit on the whole sign-in
closes #10636
2026-10-12 12:16:11 +02:00
Mario Zechner
eb2fce43d2 fix(durable): reject waits that close a wait cycle
A wait is rejected when it closes a cycle in the wait-for graph: waits of
tasks that are not abort-marked, and holds of live ordinary owned work. The
rejection names the cycle. Replaces the owner-chain rule, so a background task
may wait on its conversation's owner. Approach and tests from #10533 by
@odysseus0.

fixes #10411
2026-10-12 10:15:26 +00:00
David Brailovsky
ee409a85f6
feat(ai,coding-agent): list only the OpenRouter models a key may use (#10672)
On each OpenRouter refresh, combine the catalog (built-in models plus a newer pi.dev catalog) with GET /models/user for the user's key: keep only the listed chat models and take their context size, max output and prices from it. Image and classifier models are kept. The combined list is saved and replaces the OpenRouter catalog, including offline.

- ai: add personalizeOpenRouterCatalog and RefreshModelsContext.baseUrl, so the request follows a base URL configured in models.json (regional endpoints)
- ai: share OpenRouter price conversion with the generator; negative prices (router models such as openrouter/auto) become 0
- coding-agent: add withPersonalizedCatalog next to withRemoteCatalog
- docs: OpenRouter section in providers.md

fixes #10353
2026-10-12 10:17:40 +02:00
Mario Zechner
77332f7a34 feat(durable): deferred tools and the tool_search extension
- ToolRegistration.deferred: not offered to the model until modelTools names it
- modelTools gains { remove?, add? }; control.addTools writes names to add, so
  loaded tools follow the others in load order and a load only appends
- Agent.deferred lists deferred tools not loaded yet
- @earendil-works/pi-durable/tool-search: the coding agent's tool_search (BM25)
- the codemode section leaves deferred tools out
2026-10-12 08:05:26 +00:00
Armin Ronacher
f11e50f72e fix(coding-agent): make codemode output joining linear
joinAdjacentText checked endsWith on the growing joined string, which
makes V8 flatten the concatenation on every item. Collect each text run
in an array, check the separator against the last non-empty part, and
join once. Same fix in the durable code mode copy.

closes #10678
2026-10-12 10:03:56 +02:00
David Brailovsky
a447ba11f3
fix(coding-agent): emit before_agent_start for runs started by custom messages (#10739)
Runs started by pi.sendMessage(..., { triggerTurn: true }) skipped before_agent_start. After their first tool call, the next-turn refresh fell back to the base prompt options and patched away the sections handlers had added, changing the system prompt mid-run and breaking the provider prompt cache.

These runs now go through the same setup as prompt(): compaction check, before_agent_start, tool loadout, nextTurn messages, handler messages, and the system patch. BeforeAgentStartEvent is a union on messageType ("user" or "custom"); custom events carry the starting message.

The session counts as busy from the start of setup, so messages sent while handlers run go into the starting run instead of starting a second one, and an abort requested meanwhile aborts the run once it starts.

closes #10267, closes #5581, closes #10639, closes #10289
partly fixes #10606
2026-10-12 09:53:24 +02:00
Armin Ronacher
c661bc574f fix(durable): drop shared MCP types from the codemode section
pi-codemode no longer exports MCP_TYPESCRIPT_PREAMBLE or mcpStructuredContentSchema(); output schemas render as their own types, as in the coding agent.
2026-10-12 09:21:04 +02:00
Armin Ronacher
24382bedd0 refactor(codemode,coding-agent): decouple MCP from codemode
Tools that reach other tools declare it with reachesTools(input); MCP uses it to wait for the servers a call needs and to decide which codemode or tool_search tool to activate, instead of parsing codemode scripts and comparing parameter schemas. Namespaces get aliases, replacing codemode's MCP-specific __ suffix rule.

pi-codemode no longer knows MCP: MCP_TYPESCRIPT_PREAMBLE and mcpStructuredContentSchema() are removed, and the MCP result schema describes itself, with the server's local $refs relocated so they resolve as before.
2026-10-12 09:19:30 +02:00
Mario Zechner
4270522473 fix(coding-agent): code mode description says schema tools resolve with their value on failure 2026-10-12 00:23:28 +00:00
Mario Zechner
8bb180bec1 feat(durable): code mode listing, tool search, output, models.*, grammar; tool error rule
- structuredOutput is always the tool's own value; a schema tool's error text
  without one becomes a tool_error diagnostic on nested results
- code mode: a schema tool's error result with its value resolves (bash nonzero
  exit), schema-less tools resolve with their text, as in the coding agent
- codemode prompt section with tool declarations by namespace within an inline
  budget; searchTools/describeTool/describeNamespace from the start data
- result layout of the coding agent: header, text markers, console block,
  truncation within max_output_tokens and the tool's output limits, spill and
  image files in a recorded output directory (a rerun overwrites them)
- models.*: catalog globals; classify/generateImages as hidden internal tools
  (nested calls with usage), at most four at once
- Lark grammar of the script source as constrainedSampling
2026-10-12 00:06:09 +00:00
Mario Zechner
04566631c2 fix(durable): closing the code mode sandbox does not decide the script's outcome
A host that closes its CodemodeSandbox before the Harness, as on a graceful
shutdown, made the running code mode call store "Script error (aborted):
Sandbox closed" as its result, so the script never resumed. A run the sandbox
aborts without the call asking for it now stores nothing and ends with the
call's invocation; the next process replays the script.
2026-10-11 22:04:18 +00:00
Mario Zechner
127b77121e feat(durable): code mode result lists the script's tool calls
details.calls (CodemodeToolDetails): each tool call the script made, in call
order, with status ok/error/cancelled, duration, and cut arguments and error;
calls still running or not yet launched when the script ended are cancelled.
Written once with the result: live progress is pi.live.nestedTools.
bench:codemode compares a code mode script with a plain tool making the same
nested calls: 1,000 parallel calls 0.82 vs 0.78 ms/call, sequential 1.52 vs
1.10 ms/call (two more commits per call: the journal entry and the count).
2026-10-11 22:00:44 +00:00
Mario Zechner
7b597d1481 feat(durable): durable code mode (@earendil-works/pi-durable/codemode)
A codemode tool whose scripts call the conversation's tools as nested calls and
survive a restart. The script runs deterministically (pi-codemode start()); each
result is journaled before it is delivered. A rerun starts the script again from
the stored start data, delivers the journaled results in order (nested calls
reattach by key c<id>), checks every recorded call count, holds calls the
journal lacks until replay ends and checks them against earlier attempts' keys,
then continues live. A call launches only once the count of the drain that made
it is committed, so a rerun can prove which calls are new. Divergence, a changed
module version or sandbox fingerprint, and a passed deadline end the call with a
stored error; a decided outcome is returned without a VM. store()/load() keep
values per conversation, rewindable and forked as of the fork's entry.

pi-codemode: CodemodeRun.finished, set when the outcome is decided.
2026-10-11 21:31:48 +00:00
Armin Ronacher
8a9c051f80 feat(codemode): show schema constraints in tool declarations
Property comments now carry constraints the TypeScript type cannot
express (integer, bounds, lengths, pattern, format, default, ...) as
@ tags, read from nullable anyOf branches too. Types over the size
limit are rendered again without tags before becoming unknown.

closes #10707
2026-10-11 22:19:06 +02:00
Can Celik
78a3c10ba7
fix(tui): enable kitty images in herdr (#10774) 2026-10-11 22:01:02 +02:00
Mario Zechner
0fb1aee749 feat(codemode): deterministic runs that the host answers (sandbox.start)
For durable code mode: a host can rerun a script after a restart and get the
same calls with the same arguments. start() runs the script with a virtual
clock (startedAt, then each result's time, never backwards), randomness from a
seed, UTC, no WeakRef/FinalizationRegistry, and the run's own tools and
globals; every call goes to onCall with the VM's call id, and the host answers
with run.deliver(seq, now, id, outcome). The VM applies results strictly in
seq order whatever the transport does, reports its fingerprint (engine
versions, prelude hash, limits) before the script runs, the calls issued after
each result (onIdle), and the result it was processing when the script ended
(result.during). Rejections can carry properties for the script's Error.
execute() is unchanged.
2026-10-11 19:50:26 +00:00
github-actions[bot]
56cd194314 chore: approve contributors from issue #10809 2026-10-11 19:46:00 +00:00
Jay Madden
dbc7ff87c2
fix(tui): support override colorFn options in CompactionStatusIndicator (#10810) 2026-10-11 21:45:37 +02:00
Armin Ronacher
cd8bdf7518 fix(durable): allow image conversion tests more time on CI 2026-10-11 20:25:00 +02:00
Armin Ronacher
edb2eb966d fix(tui): keep delayed terminal replies out of keyboard input
Preserve terminal reply strings until their terminator while retaining bounded recovery for ambiguous legacy Alt-key input.

Related: #10256, #10657. Missing OSC prefixes and fragmented DA1 replies remain separate issues.
2026-10-11 20:17:00 +02:00
Mario Zechner
091bcc0998 fix(chord): append to arrays without walking or copying them twice
The delta tracker planned every structural array change with a walk over all
base elements; inserts and removals that keep the remaining elements in order
now get the same plan from the changed ranges alone. applyImmutable built an
appended array as a copy that the insert then grew, a second copy; an append now
builds it with one concat. Ops, values, immutability and errors are unchanged.
32,000 one-item appends, one commit each: 11.3 s -> 1.4 s
(npm run bench:document-append in packages/durable).
2026-10-11 18:04:43 +00:00
Mario Zechner
d77d2752e5 feat(durable): a schema tool's error result without structuredOutput gives programs its output
Programs calling a tool with structuredOutputSchema got isError and no text when
its error result omitted structuredOutput; they now get its bounded output,
collapsed like a schema-less tool's, so a code mode script sees the error text.
A thrown error's message stays in its tool_error diagnostic.
2026-10-11 18:04:34 +00:00
Mario Zechner
97195ec3c4 build: share root config with pi-private
Everything outside apps/ is the same in pi and pi-private; apps/ exists only
in pi-private, and these files now tolerate its absence:
- workspaces include apps/*
- biome.json leaves apps/ to its own config
- the pre-commit hook runs the apps/mobile check only when it exists
- the Android APK workflow runs only in earendil-works/pi-private
- scripts/biome-check.mjs runs native Biome, or Biome WASM on Android
  (@biomejs/wasm-nodejs 2.3.5), for npm run check
From pi-private bac58de97, c2a8629da, 106714ebc.
2026-10-11 16:36:26 +00:00
Mario Zechner
0f4b1465a4 fix(ai): send the codex request body as a Uint8Array<ArrayBuffer> copy
fetch accepts it as a body under the DOM lib too. From pi-private e97c4a60e.
2026-10-11 16:35:13 +00:00
Mario Zechner
2a33bbee89 test(ai): e2e repro for radius dropping replayed Claude thinking
Radius omits providerThinkingLevel from pi-messages done events, so replayed
signed thinking has no effort marker and Anthropic drops it (block_binding
drop_block). Compares follow-up input tokens with and without the thinking
block against an Anthropic control.
2026-10-11 16:35:08 +00:00
Armin Ronacher
74613de659 feat(env): report a terminal's foreground job with ptyForeground
The process group in front of the pty when it is not the program's own (a job its shell runs), with the leader's name on macOS and Linux. Exposed as RemoteTerminal.foreground().
2026-10-11 16:35:08 +00:00
Armin Ronacher
367b2bcf59 feat(env): run programs in pseudo-terminals for interactive terminals
The daemon gets pty, ptyWrite and ptyResize: raw terminal output as events, input in arrival order, SIGWINCH on resize, and the process group killed on cancel or a lost connection. openTerminal() is the client, localDaemon() the daemon for this machine. Unix only; Windows answers ENOTSUP.
2026-10-11 16:35:08 +00:00
Mario Zechner
89e5c2c075 fix(durable): fail a run whose blocking compaction failed; keep compaction failures visible
A generation waiting on a blocking threshold compaction sent its request
whatever the outcome. When summarization failed, the context was still above
the blocking threshold: a session at 996K of a 1M window got an empty length
answer on every turn and nothing showed the error. The generation now fails
with model_error (Compaction failed: <error>) when its compaction failed,
faulted, was orphaned, or was aborted directly; a compaction that found
nothing to compact or was declined still lets the request go ahead.

pi.live.compactionFailure keeps the newest failure (task, reason, error)
until a compaction produces a summary. The agent events' snapshot carries it,
and compaction_end carries error for a failed compaction.
2026-10-11 15:45:13 +00:00
Mario Zechner
15e7bdc14c fix(coding-agent,durable): omit thinking from compaction summaries
Replayed thinking is often not part of the live context (for example,
radius drops it, and Anthropic drops blocks that do not match their
request), but serializeConversation included all of it. A Pi Mobile
session at ~996K of 1M context tokens produced 1.38M-token summarization
requests that failed on every attempt. Providers only return summaries of
the reasoning anyway; visible text, tool calls, and tool results record
what happened.
2026-10-11 15:45:13 +00:00
Christian Klotz
96abe9a5ad
feat(coding-agent): use pi.dev configuration schemas (#10751)
* feat(coding-agent): use pi.dev configuration schemas

* refactor(coding-agent): separate schema URL from output path

* docs(coding-agent): document published schema URLs
2026-10-11 14:48:42 +01:00
Mario Zechner
e6b9a6af5d feat(durable): inputs placed as application entries, display-only writes appended at once
- Input submissions take entry: { kind, data } (InputEntry): placed as that
  kind with that data and the content as user message, instead of pi.user.
  Kinds must be non-empty strings outside pi.*. Queued inputs carry it in
  their pi.inbox item.
- Writes with only kind and data (no model, head, edits) are appended and
  settled done at once, busy or not; only a queued reset holds them back.
- Experimental hosts render such inputs as user messages and title by them.

closes #10407
2026-10-11 13:12:44 +00:00
Mario Zechner
cdaf6c227d feat(durable): prepare images in a worker thread in /images/node
createNodePhotonImages() now runs Photon in a worker thread, so preparing a large photo (a second or two) no longer blocks the event loop: in the test a 6-megapixel image blocked the loop over 200 ms inline and under 100 ms with the worker. The worker keeps the process alive only while it prepares an image, exits after 30 s without images to free Photon's WebAssembly memory, and a worker that cannot start falls back to the calling thread. Options: { worker: false, idleMs }.
2026-10-11 08:16:47 +00:00
Mario Zechner
f44707e7f7 feat(durable): bound the Session's document cache by use (documentRetentionMs)
The Session kept every document it loaded or created in memory for the life of the process: every conversation and
session document ever touched, built-in and third-party. Now a cached document that no watch or document state
observes leaves the cache once it was not read or written for `documentRetentionMs` (HarnessSettings and
createSession options; default ten minutes, 0 drops it right after use, Infinity keeps documents; anything that is not
a number of milliseconds means the default), and loads from Storage again on its next access. An observer pins the
incarnation it observes and releases it when that incarnation retires.

Eviction runs on the mutation line, after every commit outcome (published, no-write, rejected, failed settlement) and
from one unreferenced timer for the next expiry, so a transaction never loses a tracker it holds. Where timers cannot
be unreferenced only commits evict. Cache bookkeeping is contained: a throwing clock or setting never changes an
operation's outcome. A hot document moves in the eviction order at most once per granule (a tenth of the retention,
up to 1 s), keeping the fan-out bench at HEAD's speed.

`npm test` runs the durable suite a second time with every unattached document evicted right after use.
5,000 conversations kept 26,000 documents and about 3.8 KB each; with a 50 ms retention, after waiting, none
(bench:document-cache).
2026-10-11 08:03:35 +00:00
Mario Zechner
ea448f454a fix(durable): bound the scheduler's ownership indexes by live work
The scheduler kept every conversation edge it saw and, for conversation owners, chain loads, and owners of known
tasks, the ended tasks above them, for the life of the process. Now an owner edge or an ended task's ownership is kept
only while something is listed below it in #below: only the chains above live work are known, whole. Unneeded nodes
are queued and swept at the end of each publication, or by a sweep job on the Session line after operations that
publish nothing (rejected commits, reads), never during an operation, so a commit that creates work below a chain its
own callback loaded keeps it. Later work in an old conversation loads its chain from storage again.

Removes #conversationOwners, #pinned, #knownChildren, and #forget. A queued input triggers the withdrawal scan only
while some task has cancellation intent, and chains are loaded only for queued inputs.

10,000 ended subagent cycles under a live parent: 10,000 settled tasks and 10,001 edges before, 0 and 1 now
(bench:scheduler-memory); a later message into an old subagent conversation reads 7 records instead of 5.
2026-10-10 19:29:35 +00:00
Mario Zechner
e37cdc4206 fix(durable): drop redundant nested-call parent check; test ownedTasks() with a held child 2026-10-10 17:40:24 +00:00
Mario Zechner
b9f2b3e827 docs(durable): read only through tx inside a commit callback
Session, Harness, and runtime reads such as snapshot(), documentState(), watchDoc(), and getTask() may queue on the mutation line a commit callback holds; on a cache miss they never resolve. Documented on Session.commit, TaskRuntime.commit, and in the spec's pitfalls.
2026-10-10 17:36:39 +00:00
Armin Ronacher
4130bd5b32 fix(codemode): ignore Node --watch notifications from the worker
Node 24/26 --watch posts watch:import/watch:require messages on the
worker channel (nodejs/node#65044), which failed the run as a broken
bridge.

closes #10725
2026-10-10 19:13:19 +02:00
Mario Zechner
4ac0bd8c7b feat(durable): one document per nested call; TaskRuntime.ownedTasks()
NestedCallsDoc (one index per caller, a key per nested call, copied on every admission) and NestedResultDoc are
replaced by NestedCallDoc (`pi.tool.nested-call`): a task-scoped family of the caller keyed by the call's key,
holding { taskId, result? }. Admission creates the member; the nested call's terminal commit adds its result;
executeTool() reads it by key. No commit rewrites another call's document.

TaskRuntime.ownedTasks(context) returns copies of the live tasks a task owns directly, whichever invocation
created them, from the scheduler's index. A tool call settling aborts the live nested calls among them, which
the removed index used to list. A task memo marks a tool that made a nested call in any attempt, so settling a
leaf still skips scanning pi.live.

8000 parallel nested calls: 9.2 s -> 2.7 s, linear (bench:scheduler-fanout).
2026-10-10 15:35:29 +00:00
Mario Zechner
cb43754931 fix(durable): scheduler no longer visits every live task on each pass
Every reconcile pass walked up from every live task to the root to find cancelling owners, and owned live work,
idle scopes, chain loads, reservation, finalization, and failFast checks each scanned all live tasks. A chain of n
held tasks cost about n^3 steps: 500 took 12 s, 20,000 never finished.

The ownership semantics are unchanged; in-memory indexes, rebuilt at open, hold each pass's candidates:
- #below: the ownership tree downward around live work; owned live work, idle scopes, and conversation aborts walk
  down from one node. Ended tasks stay while walks pass through them (conversation owners, chain loads, owners of
  known tasks), so loaded chains stay loaded.
- #intent: cascades walk down from tasks with cancellation intent, not entering another such task, so the nearest
  cancelling owner still decides the reason; upward checks return at once when it is empty.
- #unloaded: only tasks whose chain is not loaded are loaded, and count as inside every scope until then.
- #finalizeChecks: finalize is a worklist in rounds from the nearest live holder of ended work.
- #runnable and #waiters: reservation and failFast candidates.
No walk recurses.

Chain of 500 held tasks: 12.3 s -> 50 ms; 20,000: 1.5 s. 8000 parallel nested calls: 18.8 s -> 9.2 s
(bench:scheduler-fanout); the rest is document copying of the nested-call index.
2026-10-10 14:56:43 +00:00
Armin Ronacher
c5f5b3282d fix(mcp): reject iss when no authorization server metadata was discovered
Without metadata there is no issuer to compare an RFC 9207 iss parameter with, so the check was skipped and a malicious MCP server could receive a code from another authorization server. Reject the response before the code exchange instead.

Reported by Alexios Stefanopoulos (@AizenvoltPrime).
2026-10-10 14:44:35 +02:00
Armin Ronacher
748b351fe2 feat(codemode): run sandboxes on Cloudflare Workers via a remote Durable Object
Split the host into transport-independent execution logic and a VM driver, so the VM can run in a worker thread (CodemodeSandbox, unchanged), on the calling thread (InlineCodemodeSandbox), or in another isolate over RPC (RemoteCodemodeSandbox).

Add the /portable entry without Node imports and the /cloudflare entry with CodemodeSandboxDurableObject, meant to be deployed as its own Worker so scripts never block the caller's thread. Scripts that compute without awaiting a tool are bounded by an interrupt budget, now also polled while draining promise jobs. examples/cloudflare shows the setup.
2026-10-10 14:41:36 +02:00
Mario Zechner
42a3497d03 feat(durable): read returns images, optional Photon image processor
read returns an image file as one image block (programs get an ImageContent) plus an info diagnostic,
instead of an unsupported_image error. Limits are the conversation model's inputLimits.images.resize,
by default 2000x2000 pixels and 4.5 MB of base64.

- Without a processor: PNG, JPEG, GIF, and WebP within the limits pass through undecoded; the byte limit
  is checked before reading the file, dimensions from the header. Larger images and BMP are error results.
- With createCodingTools({ images }): the processor orients by EXIF, converts BMP to PNG, and shrinks to
  fit. ImageProcessor.prepare is async.
- @earendil-works/pi-durable/images: createPhotonImages(wasm) on @silvia-odwyer/photon 0.3.3 (web build);
  /images/node and /images/cloudflare load the wasm. The root and ./tools entries never reach it (enforced
  in check-entry-graphs).
- An image must be read whole: one that changes while read is read again, unlike a growing text file.
- A model without image input gets a diagnostic that it sees a placeholder.
2026-10-09 19:38:02 +00:00
Mario Zechner
eba8497395 feat(durable): nested tool calls, structured output, callers; storage errors end the Session
Nested tool calls: ToolExecutionApi.executeTool() runs another tool as its own pi.tool task owned by the caller,
keyed for replay-safe reattachment. Results for programs live in the caller's NestedResultDoc until it settles;
pi.live.nestedTools shows each call with the arguments it runs with and a summary. TaskOptions.abandonOnRestart
aborts work its creator awaited only in memory before it runs again after a restart.

Structured output: structuredOutputSchema and structuredOutput, validated; a tool without a schema gives programs its
bounded output (one text as a string, one image as itself, else the list). ToolExecutionResult.content is now output.
callers and modelTools decide who may call a tool.

Storage errors: any error a Storage method throws fails the Session, which closes itself; later calls and waits reject
with SessionFailed, watches end with session_failed, and Session.closed settles. Nothing retries in the Harness.
StorageRejected is removed; StorageRequestError marks invalid reads that fail only their call. Listener and onReport
errors are contained and reported.

Also updates scripts/issues/triage.ts and the vacation experiment for the content -> output rename.
2026-10-09 17:13:34 +00:00
Mario Zechner
5a10492690 feat: add local issue triage tool in scripts/issues
Syncs issues with gh, triages new ones with a pi-durable agent (category, duplicates, summary, p0/p1/none suggestion), and serves a web UI for decisions.
2026-10-09 17:12:26 +00:00
David Brailovsky
f1b2e77f5b fix(coding-agent): load symlinked AGENTS.md in nested git worktrees
The nested worktree dedup compared the context file's realpath with the
main repo's file, so a worktree AGENTS.md symlinked to the main repo's
copy was skipped along with it. Compare the walked directory's realpath
plus the file name instead.

fixes #10681
2026-10-09 09:40:08 +02:00
Christian Klotz
6fb2e78151
fix: support npm 12 pack JSON output (#10680)
fixes #10633
2026-10-08 17:26:34 +02:00
David Brailovsky
ce950d78f4 fix(ai): use models.dev metadata for Claude 5.5 models
Remove hardcoded Opus 5.5, Sonnet 5.5, and Haiku 5.5 fallbacks now that models.dev lists them. Fix the Sonnet 5.5 test to expect the official cache read price of 0.1.
2026-10-08 11:20:26 +02:00