Commit Graph
1102 Commits
Author SHA1 Message Date
roboomp 72df91c3c8 fix(compaction): route plan-mode guidance via internalGuidance channel
Plan-approval's 'Approve and compact context' used to pass the rendered
plan-mode-compact-instructions prompt as the first positional argument
to handleCompactCommand -> session.compact(), which landed on the
session_before_compact extension hook as customInstructions. Extensions
treating that field as user focus (e.g. to bias a query-focused summary)
would then see plan-mode boilerplate instead of operator intent and
produce query-biased compactions.

Add CompactOptions.internalGuidance: a private summarizer-only channel.
session.compact() reads it into the fallback-model summarizer while the
session_before_compact hook payload still only carries the public
customInstructions arg (undefined for the plan-compact path). The
snapcompact-disable predicate and the /compact rejectsFocus guard cover
both fields so a directed summary is never silently downgraded.

Extend the interactive-mode handleCompactCommand facade + command
controller with a fourth internalGuidance parameter, and switch the
plan-approval callsite in interactive-mode.ts to route the plan prompt
through it.

Fixes #4359
2026-07-03 00:36:38 +00:00
can1357 43856f70d9 Merge remote-tracking branch 'origin/farm/8994bf65/rebind-advisor-model-role' 2026-07-02 23:43:09 +02:00
can1357 5de45fa975 Merge remote-tracking branch 'origin/farm/61175132/fix-windows-session-tail-loss' 2026-07-02 23:33:20 +02:00
roboomp 01ab7e26d7 fix(session): kept newer fence owner on stale rewrite unwind
SessionManager.#runFencedAtomicRewrite's finally now only clears #atomicRewriteFenceEpoch when it still matches the unwinding task's epoch. When flushSync supersedes an in-flight rewrite (bumping #diskEpoch and resetting #diskTail), a fresh atomic task scheduled at the new epoch can take ownership of the fence before the stale rewrite finally settles; the previous unconditional clear stranded the newer rewrite's bookkeeping so subsequent sync appends took the hot writer path and were then detached by the newer publish.

Regression: SequencedRewriteStorage pauses the first N writeTextAtomic calls on per-call gates. Test schedules a stale rewrite, forces flushSync to bump the epoch via a fenced append, schedules a newer rewrite that parks at pauses[1], releases the stale gate (stale unwinds and guard-rejects), then appends a custom entry — asserts writerOpens does not grow (fence preserved) and the fenced entry lands in the newer publish's body. Without the fix, writerOpens grows from 1 to 2.

Fixes #4338
2026-07-02 21:20:53 +00:00
can1357 234a46fa3f feat(session): delayed session termination for pending async background jobs
- Added `#hasPendingAsyncWake` to detect running or pending background jobs owned by the agent.
- Deferred todo reminders and `session_stop` hook passes until all agent-owned background async jobs complete.
- Ensured scheduling pauses caused by async jobs do not trigger terminal session stops or premature todo nags.
2026-07-02 23:14:22 +02:00
roboomp 549b4c13a8 fix(session): relaxed fence once flushSync superseded the atomic
Replaced the boolean #atomicRewriteActive flag with #atomicRewriteFenceEpoch: number | null. The fence branch in #appendToSessionFile now applies only while the pending atomic rewrite's epoch still matches #diskEpoch. Once flushSync -> #rewriteSynchronously bumps the epoch, the in-flight writeTextAtomic is guaranteed to abandon via its commitGuard, so subsequent sync appends can (and must) take the hot path against the freshly-published body instead of being stranded in memory when close() returns without another rewrite.

New regression: pauses writeTextAtomic mid-flight, appends a fenced custom entry, calls flushSync (which captures it into the durable body), then appends a message + custom entry after the epoch bump. Reads the current JSONL BEFORE releasing the paused atomic and asserts both post-flushSync entries are already on disk; then releases the atomic (commitGuard rejects) and closes the session and asserts nothing is lost.

Fixes #4338
2026-07-02 20:54:24 +00:00
can1357 d82b9bdc5f feat(agent): allowed dynamic model resolution per LLM call
- Added `getModel` to `AgentLoopConfig` to allow runtime model resolution.
- Updated `streamAssistantResponse` to resolve the model dynamically per provider call instead of using the stale configuration snapshot.
- Enabled mid-run model switches to take effect immediately for context promotion and retry fallbacks.
2026-07-02 22:43:11 +02:00
roboomp babb731cf5 fix(session): looped title fallback + drained backend on close
SessionManager.#persistTitleChangeEntry's catch fallback previously did a single-shot atomic rewrite: any prompt/tool appended while it awaited was fenced with #atomicRewriteDirty=true but never re-serialized. Extracted the fenced-rewrite do-while loop into #runFencedAtomicRewrite and used it from both #rewriteAtomically and #persistTitleChangeEntry, so fenced entries during either path are captured before the task resolves.

Added SessionStorage.drain(): for FileSessionStorage and MemorySessionStorage it is a no-op; IndexedSessionStorage already had one and now conforms to the interface. SessionManager.flush() and close() await it so a graceful shutdown does not exit while a fire-and-forget writeTextSync publish (queued by flushSync on an indexed backend) is still on the wire — reducing the residual publish-window race for Redis/SQL where the backend cannot be aborted mid-flight.

Regression covers the title fallback loop: TitleFallbackPausingStorage forces updateSessionTitle to throw, pauses the fallback's writeTextAtomic, appends a message and a custom entry during the pause, and asserts (a) both fenced entries land on the current JSONL, (b) the final title is applied, and (c) writeTextAtomicCalls >= 2 proving the loop iterated.

Fixes #4338
2026-07-02 20:32:27 +00:00
roboomp f614ec1537 fix(session): honored commitGuard at indexed publish time
IndexedSessionStorage.writeTextAtomic no longer delegates directly to writeText, which yielded on #awaitPath between the guard check and the backend publish. The new impl consults the guard three times — up front, again after #awaitPath resolves, and finally inside the enqueued task immediately before #backend.writeFull — so a flushSync that bumps #diskEpoch while the atomic rewrite is suspended cannot land stale content on Redis/SQL backends. When the enqueue-time guard rejects, the optimistic index update is restored only when nothing has advanced it past our mtime, so a concurrent writer's state is preserved.

Added a PausableWriteFullBackend regression: the first writeTextAtomic parks inside backend.writeFull holding the per-path tail; the second queues with a guard that flips after the first is released. The backend records only the first content, confirming the guard is honored at publish time.

Fixes #4338
2026-07-02 19:17:45 +00:00
roboomp 23d9f7c898 fix(session): discarded temp on EPERM guard-reject branches
FileSessionStorage.#replaceSessionFileAfterEpermSync now unlinks the staged temp file when commitGuard returns false in both fallback branches: the ENOENT-vanished-target path and the post-move-aside path (where the moved-aside backup is also restored). Honors the writeTextAtomic contract that a guard-rejected stage is discarded.

Regressions cover all three guard-reject exits: the direct rename pre-check, the ENOENT branch inside the EPERM fallback, and the move-aside branch that also restores the backup. Each asserts no orphan .tmp remains in the session dir.

Fixes #4338
2026-07-02 19:11:25 +00:00
roboomp 24c6b3a9f9 fix(session): fenced writer close-yield inside atomic rewrite
SessionManager.#rewriteAtomically now enables #atomicRewriteActive before #closeWriterHandle() and keeps it set until the rewrite task exits, so a sync append landing in the close-yield window is fenced and cannot open a fresh writer that the pending writeTextAtomic would then detach from the current JSONL path. Same pattern applied to the #persistTitleChangeEntry atomic fallback.

Added a regression that pauses the fake storage's writer.close() gate, appends a message and a custom entry during the pause, and asserts (1) no new writer opens (writerOpens counter unchanged) and (2) the fenced entries land on the current JSONL path after the rewrite completes.

Fixes #4338
2026-07-02 19:07:36 +00:00
roboomp 326a3406a8 fix(session): guarded atomic rewrite against flushSync overwrites
SessionStorage.writeTextAtomic now accepts a commitGuard the backend calls synchronously immediately before publishing the staged body. FileSessionStorage performs the guard check and rename in the same tick via fs.renameSync (both on the direct path and the EPERM move-aside fallback), so a concurrent #rewriteSynchronously (flushSync -> Ctrl+C / session exit) that bumps the disk epoch cannot be overwritten by the stale body serialized before it ran. MemorySessionStorage and IndexedSessionStorage honor the same guard.

SessionManager.#rewriteAtomically threads a guard that returns false when the disk epoch changes, and re-checks the epoch after every writeTextAtomic before touching #fileIsCurrent / #rewriteRequired. #persistTitleChangeEntry's atomic fallback wires the same guard.

Added a regression that pauses the fake storage's writeTextAtomic mid-flight, appends a session_exit custom entry (which the fence records in memory), calls flushSync, releases the paused rewrite, and asserts the exit record is still on the JSONL path and the atomic publish was rejected by the guard.

Fixes #4338
2026-07-02 19:00:27 +00:00
roboomp 69db80fbe7 fix(session): preserved tail after atomic compaction rewrites
Fenced synchronous session appends while an atomic full-file replacement is active so Windows EPERM fallback cannot detach the append writer from the current JSONL path.

Added a deterministic storage fake regression covering superseded compaction rewrites, title changes, session-exit diagnostics, resume, and post-rewrite tool/assistant tail persistence.

Fixes #4338
2026-07-02 18:36:56 +00:00
roboomp d41d0c4253 fix(agent): kept advisor enable idempotent
Compared the resolved advisor runtime inputs before rebuilding on explicit enable so unchanged /advisor on calls keep the live advisor agent and history.

Added regression coverage for the unchanged-config path preserving advisor context.

Fixes #4302
2026-07-02 11:36:07 +00:00
roboomp f3834dfce8 fix(agent): rebuilt advisor on explicit enable
Rebuilt live advisor runtimes when the explicit enable path runs so changed modelRoles.advisor values are re-resolved without restarting the session.

Added a regression test covering a live advisor moving from one role model to another through setAdvisorEnabled(true).

Fixes #4302
2026-07-02 11:27:47 +00:00
can1357 88f0aab994 style: formatted eval fix commits with biome 2026-07-02 10:34:53 +02:00
can1357 71209f025d fix(session): drop stale plan-mode-decision tool choice on skip/exit
The settle-time reminder queues a hard 'required' tool choice paired with a
scheduled continuation. If that continuation never runs (user prompt bumps
the generation, dispose, compaction/handoff) or plan mode is exited first,
the queued directive leaked onto the next unrelated turn as a forced tool
call. Remove it by label on continuation skip, on user-initiated prompts,
and when plan mode is disabled.
2026-07-02 10:30:51 +02:00
can1357 2875dd3349 merge PR #3911: fix(session): converge plan mode on ask/resolve across continuation paths (@metaphorics)
# Conflicts:
#	packages/coding-agent/src/session/agent-session.ts
2026-07-02 10:30:51 +02:00
can1357 4940a053ad merge PR #3777: Fix todo HUD and goal context follow-ups (@jeffscottward) 2026-07-02 10:30:12 +02:00
can1357 a5043b11ec merge PR #4221: fix(session): keep model switches active after rate limits (@roboomp) 2026-07-02 10:29:47 +02:00
roboomp a06d0c54b3 fix(session): switched models during goal recovery
Removed the live-context guard that let default model selection persist a new role without changing the active session model. The next prompt's compaction path now owns oversized-context recovery after a switch.

Fixes #4219
2026-07-02 06:48:30 +00:00
can1357 95b91c7f73 feat(coding-agent/tools)!: replaced paths arrays with path strings
- Replaced `grep`, `glob`, and `ast_grep` `paths` inputs with optional single `path` strings while preserving default workspace-root behavior.
- Added shared `toPathList` normalization for legacy arrays and JSON-encoded arrays across tool execution and TUI renderers.
- Updated prompts, fixtures, shims, transcript summaries, and tests to send and display the new `path` argument.
- Updated collab-web search tool cards to read `path` while falling back to legacy `paths` for historical transcripts.
- Recorded the contiguous coding-agent changelog run for the tool-path breaking change and adjacent TTS entries.
2026-07-02 08:30:33 +02:00
can1357 c4c0331345 fix(coding-agent/session): prevented data loss in session serialization
- Persist signed message blocks (`text`, `thinking`, `toolCall`) and encrypted reasoning payloads verbatim during session serialization instead of clearing or truncating them.
- Preserve signature keys instead of replacing them with empty strings when they exceed persistence size limits.
- Exempt official first-party OpenAI and Anthropic API endpoints from the leaked-thinking stream healing wrapper to prevent misfires on legitimate visible text fences.
2026-07-02 03:58:10 +02:00
can1357 0d96c2b6ec Merge remote-tracking branch 'origin/farm/bf21f607/frame-rewind-completion'
# Conflicts:
#	packages/coding-agent/src/session/agent-session.ts
#	packages/coding-agent/test/agent-session-checkpoint-rewind-branch.test.ts
2026-07-02 03:56:59 +02:00
can1357 af748c3e90 fix(coding-agent/session): prevented truncation of signed thinking and redacted reasoning blocks
- Excluded signed `thinking` blocks and `redactedThinking` blobs from size-based persistence truncation.
- Preserved signature-bound reasoning verbatim to prevent provider validation failures on session replay.
- Maintained normal truncation behavior for unsigned thinking and standard text blocks.
2026-07-02 03:39:59 +02:00
roboomp 0b8164facb fix(agent): restored active checkpoints when rehydrating rewind state
The branch-scan rehydrator only rebuilt `#lastCompletedRewind` and wiped
`#checkpointState` unconditionally at entry — so a branch whose latest
checkpoint had not yet been rewound came back with neither an active
checkpoint nor completed-rewind guidance. Reloading such a session (or
`switchSession()` on the same file) made the next `rewind` fail with
"No active checkpoint" even though the checkpoint entry was still the
branch leaf.

Extended the walker to also track the last unresolved checkpoint entry
and, when the branch ends without a rewind-report, seed `#checkpointState`
from that entry (id, `details.startedAt`) so `rewind` can complete
normally. Renamed the method to `#rehydrateCheckpointRewindState` to
reflect the widened responsibility and added a regression test that
truncates the branch to the checkpoint entry, resumes into a fresh
`AgentSession`, and calls `rewind` end-to-end.

Fixes #4187
2026-07-02 01:33:21 +00:00
can1357 3660b0973a chore: revert brain damage 2026-07-02 03:29:30 +02:00
roboomp a030373665 fix(agent): cleared rewind state on session resets
Cleared checkpoint rewind runtime state when starting new sessions or creating branch sessions so stale completed-rewind guidance cannot leak into unrelated contexts.

Added regression coverage for /new and branch reset paths.

Fixes #4187
2026-07-02 01:19:18 +00:00
roboompandcan1357 cf570a90f0 fix(agent): rehydrated completed rewind state
Reconstructed the completed rewind marker from the active branch so resumed sessions keep repeat-rewind recovery guidance.

Covered resume rehydration with the checkpoint rewind branch regression test.

Fixes #4187
2026-07-02 03:17:17 +02:00
roboomp 125dd36ead fix(agent): rehydrated completed rewind state
Reconstructed the completed rewind marker from the active branch so resumed sessions keep repeat-rewind recovery guidance.

Covered resume rehydration with the checkpoint rewind branch regression test.

Fixes #4187
2026-07-02 01:09:44 +00:00
can1357 0159d86023 Merge remote-tracking branch 'origin/farm/bf21f607/frame-rewind-completion' 2026-07-02 03:08:23 +02:00
can1357 b149cfce71 feat(coding-agent): implemented targeted mid-run todo nudges for mutating tools
- Refactored the mid-run todo nudge to trigger on mutating tools (bash, eval, edit, write, ast_edit) rather than overall tool turns.
- Simplified the nudge prompt template to a concise, non-escalating reminder.
- Migrated nudge messages from "developer" role with public events to a hidden "custom" role that is excluded from the TUI and transcript.
- Introduced a separate per-cycle reminder cap of 2 to decouple mid-run hints from the user-visible stop-time escalation budget.
- Avoided triggering the todo nudge when read-only exploration tools (e.g. grep, read, glob, lsp) or errored results are returned.
2026-07-02 03:03:23 +02:00
roboomp 1109c25629 fix(agent): framed completed rewind context
Wrapped retained rewind reports with completion guidance so the post-rewind turn knows the checkpoint is closed.

Added repeat-rewind recovery errors and regression coverage for both the retained context and no-active-checkpoint path.

Fixes #4187
2026-07-02 00:56:06 +00:00
can1357 0e2feab742 refactor(coding-agent): reduced session log footprint
- Projected full tool-call arguments down to a compact summary containing only `command` and `path`.
- Truncated summarized argument fields to 200 characters to prevent inflating session log sizes.
- Replaced routine clean session disposal warnings with debug logs to reduce noise.
- Streamlined debug context in assistant message removal and agent continuation skip paths.
- Extracted duplicate user-facing compaction warning strings into a helper function.
2026-07-02 02:40:08 +02:00
can1357 db53707b8e Merge remote-tracking branch 'origin/farm/32976ff8/anthropic-fable-fallback' 2026-07-02 02:04:02 +02:00
can1357 1b458f2e61 fix(coding-agent): preserved teardown exit reasons during session disposal
- Thread the postmortem reason through the session teardown pipeline to the session dispose process.
- Prevent generic "dispose" logs from overwriting real triggers like SIGTERM, SIGHUP, or uncaught exceptions.
- Ensure the first teardown trigger's reason is preserved when concurrent disposal calls occur.
- Add comprehensive test coverage verifying signal-specific reason mapping inside exit diagnostics.
- Fix a minor unhandled-exception test utility expectation in input controller tests.
2026-07-02 01:51:10 +02:00
roboomp 1bbd0c92f6 feat(anthropic): opt-in server-side fallback beta chain
Added AnthropicOptions.fallbacks + wire types + response parsing gated on the opt-in — server-side fallback stays fully inert on every request that does not set the option.

Coding-agent surfaces the feature via providers.anthropic.serverSideFallback (default off). When enabled, Fable/Mythos requests inject fallbacks: [{ model: claude-opus-4-8 }]; caller-supplied fallbacks always win.

transformMessages centrally strips persisted fallback blocks on cross-provider hops and non-official Anthropic replays so a stored fallback turn never wedges downstream converters. Retry resets restore output.model to the requested id.

Fixes #4177
2026-07-01 23:34:48 +00:00
can1357 73ef29bd00 fix(session): corrected branch traversal order breaking ctrl+p cycling
- Removed a duplicated branch.reverse() left by the PR #3862 merge in
  SessionEntryIndex.pathTo(), which returned branches leaf-to-root and made
  getLastModelChangeRole() read the oldest model change instead of the
  newest — pinning the ctrl+p cycle to one slot and breaking session model
  restore.
- Hardened getRoleModelCycle() to trust the recorded role only while its
  resolved model still equals the active model, falling back to matching by
  model after switches through alt+m, /model, or retry fallback.
- Added mutation-verified regression tests for branch ordering and the
  stale-role fallback.
2026-07-01 23:48:26 +02:00
can1357 1bf06d9cec Merge PR #3640 (selective): advisor inherits main agent promptCacheKey (@roboomp)
Ports only the advisor cache-key fix (advisorPromptCacheKey = agent.promptCacheKey ?? advisorSessionId) + its provider-options parity regression. tan/shared sessions read the parent provider cache shard byte-for-byte. Excludes the unrelated CI-isolation test commit e3160a09c. Fixes #3639.
2026-07-01 22:34:13 +02:00
can1357 2e53c40c9e Merge PR #3412 (selective): clamp compaction reserve budget for small windows (@wolfiesch)
Cherry-pick of the reserve-budget clamp only (resolveBudgetReserveTokens + no-op compaction guard): applies compaction.ts + agent-session.ts + compaction/shake/progress-guard tests. Excludes unrelated Julia prelude timeout and ai/test churn from the PR head.
2026-07-01 22:29:53 +02:00
can1357 a5a7b5b77c fix(coding-agent): limit mnemopi shutdown timeout to interactive exit 2026-07-01 22:22:09 +02:00
roboompandcan1357 efaad3ffb5 fix(coding-agent): bounded mnemopi consolidate-on-dispose so /quit returns within ~2 s
/quit and /exit hung for many seconds because AgentSession.dispose()
awaited MnemopiSessionState.dispose() unconditionally, and that path
runs consolidate() (state.ts:421) which fires a fresh LLM fact
extraction for the just-retained transcript and then awaits
flushExtractions() per owned bank. One LLM round-trip per shutdown,
no upper bound, no visible status.

- Add a timeoutMs option to MnemopiSessionState.dispose. When the cap
  fires the in-flight consolidate is detached to the background and the
  SQLite handles close once it settles, so writes never race a closed
  handle.
- AgentSession.dispose passes SHUTDOWN_CONSOLIDATE_BUDGET_MS = 1_500 on
  the user-visible shutdown path. Per-turn maybeRetainOnAgentEnd has
  already retained earlier turns, so the worst case is losing episodic
  promotion for the last few turns. State-replacement disposes
  (mnemopiBackend.start) stay unbounded.
- InteractiveMode.shutdown surfaces a 'Closing session…' status before
  dispose runs so the brief pause is explained rather than mysterious.

Two regression tests in memory-tools.test.ts cover (1) dispose returns
within the budget when flushExtractions stalls and the deferred close
still runs once consolidate settles, and (2) unbounded dispose still
runs the full #2320 consolidate-then-close pipeline.

Fixes #3641
2026-07-01 22:18:44 +02:00
can1357 5ca07b1759 Merge PR #3759: Merge remote-tracking branch 'can1357/main' into fix/compaction-summary-chronological-position (@DarkPhilosophy)
# Conflicts:
#	packages/coding-agent/src/session/session-context.ts
2026-07-01 22:04:47 +02:00
can1357 9403df1abd fix(session): avoid empty exit-marker sessions 2026-07-01 21:55:01 +02:00
can1357 b8ae1e21b1 Merge PR #2607: fix(session): detected pending tool calls without toolUse (@roboomp)
# Conflicts:
#	packages/coding-agent/src/session/agent-session.ts
2026-07-01 21:55:01 +02:00
can1357 569b83d5a6 Merge PR #4166: fix(agent): preserve approved plan path (@roboomp) 2026-07-01 21:53:19 +02:00
can1357 021d4fc1e3 Merge PR #4161: fix(agent): interrupt waits for IRC delivery (@roboomp) 2026-07-01 21:53:19 +02:00
can1357 debe71ae0e Merge PR #4129: fix(coding-agent): preserved explicit :auto suffix in modelRoles (@roboomp) 2026-07-01 21:53:17 +02:00
can1357 1479b689d8 Merge PR #4121: fix(coding-agent): route SIGTERM/SIGHUP/uncaughtException through session teardown (@roboomp) 2026-07-01 21:53:16 +02:00
can1357 9ae2b48b10 Merge PR #4077: fix(agent): add retry path diagnostics (@roboomp) 2026-07-01 21:53:14 +02:00