feat(agent-core-v2): read and write the v1 session index file - #1616
feat(agent-core-v2): read and write the v1 session index file#1616sailist wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: f1274fc The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eebd61ed28
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| */ | ||
| private async readLegacyIndex(): Promise<Map<string, string>> { | ||
| const result = new Map<string, string>(); | ||
| const bytes = await this.storage.read(LEGACY_SESSION_INDEX_SCOPE, LEGACY_SESSION_INDEX_KEY); |
There was a problem hiding this comment.
Fall back when the legacy index read fails
When <homeDir>/session_index.jsonl is present but unreadable (for example EACCES or a directory at that path), FileStorageService.read throws storage.io_failed; because this new read is outside a try/catch, get() / list({ sessionId }) now fail before the existing directory scan can run. v1's index reader and the helper comment both treat unreadable index files as empty, so catch read failures here and return the empty map to keep sessions discoverable from the authoritative directory tree.
Useful? React with 👍 / 👎.
| // Project the new session into v1's global index so a v1 CLI sharing | ||
| // this homeDir can discover it. `AppendLogStore` framing is | ||
| // byte-identical to v1's `JSON.stringify(entry) + '\n'`; the explicit | ||
| // flush makes the line durable before create/fork returns. Placed at | ||
| // the materialization point (not fork completion) because v2 does not |
There was a problem hiding this comment.
Move inline implementation notes into the file header
packages/agent-core-v2/AGENTS.md says comments must live “solely in the top-of-file /** */ block” and “never beside functions, methods, or statements.” This newly added method-level rationale violates that package rule (as do the other new inline source comments in this patch), so move the explanation into the module header or remove it.
Useful? React with 👍 / 👎.
- add a legacySessionIndex module defining the v1 session_index.jsonl byte format: tolerant line parsing and v1's entry validation (absolute sessionDir inside sessionsDir, basename matches sessionId), shared by the v2 readers and writer - append a v1-compatible index line on session create/fork (never on resume) through the append-log store, so a v1 CLI sharing the homeDir discovers v2 sessions - locate sessions through the legacy index in FileSessionIndex.get (and read-model cold misses) with a directory-scan fallback for stale lines - reuse the shared parser in the workspace registry's one-shot rebuild
eebd61e to
f1274fc
Compare
commit: |
An explicit withThinking('off') collapsed to the same internal state as
"never configured" on chat-completions providers, so the history-based
auto reasoning_effort injection (#1616) silently switched reasoning back
on and could leak the field to models that reject it. Store the requested
effort verbatim and derive the wire encoding per request, suppress the
auto-enable for an explicit 'off', and report the accurate current effort
('on'/'off') instead of recording 'off' for both.
…shotAI#1774) An explicit withThinking('off') collapsed to the same internal state as "never configured" on chat-completions providers, so the history-based auto reasoning_effort injection (MoonshotAI#1616) silently switched reasoning back on and could leak the field to models that reject it. Store the requested effort verbatim and derive the wire encoding per request, suppress the auto-enable for an explicit 'off', and report the accurate current effort ('on'/'off') instead of recording 'off' for both.
Related Issue
No linked issue — the problem is explained below.
Problem
v2 (
packages/agent-core-v2) and v1 (packages/agent-core) can share the same homeDir / data directory, but they discovered each other's sessions asymmetrically:<homeDir>/session_index.jsonl(one{sessionId, sessionDir, workDir}line per create/fork). v2 never wrote to it, so sessions created by the v2 engine were invisible to a v1 CLI pointing at the same directory.FileSessionIndex.get()located a session by scanning every workspace bucket (listper workspace until a match) — O(workspaces) per cold point lookup — even though v1's index file usually already knows the answer.What changed
1. Single definition of the v1 index byte format
Problem: the JSONL parse/validate logic for
session_index.jsonlwas duplicated inline inworkspaceRegistryServiceand about to be needed in two more places (a writer insessionLifecycle, a reader insessionIndex).What was done:
packages/agent-core-v2/src/app/sessionIndex/legacySessionIndex.ts: the single definition of the legacy file's scope/key, plusparseLegacySessionIndexLine(tolerant line parsing, same acceptance as v1'sparseIndexLine) andvalidateLegacySessionIndexEntry(v1's read-side rules: absolutesessionDirinsidesessionsDir, basename === sessionId; derives the workspaceId from the bucket directory).workspaceRegistryServicenow imports these instead of keeping its own copy — no behavior change.2. v2 writes v1-compatible index lines on create/fork
Problem: v2-created sessions were undiscoverable by v1.
What was done:
SessionLifecycleService.materializeSessiongains alegacyIndexoption, set for brand-new sessions (create, and the fork target) but never for resume — matching v1, which only writes on create/fork.AppendLogStore(framing is byte-identical to v1'sJSON.stringify(entry) + '\n') and explicitly flushed before create/fork returns, so the line is durable.3. Point lookups consult the legacy index first
Problem: cold
get()cost onelistper workspace bucket.What was done:
FileSessionIndex.getLegacy()and the read-model cold path (getFromReadModel()) first read the legacy index as a sessionId → workspaceId locator and try that bucket directly; a missing or stale line (deleted dir / unreadable metadata) falls through to the existing per-workspace scan, so correctness never depends on the file.Tests
sessionIndex.test.ts: lookup via the index file, rejection of lines failing v1 validation (relative path, outsidesessionsDir, basename mismatch, corrupt JSON), later-lines-override, stale-line fallback to the scan, and read-model cold-miss backfill through the locator.sessionLifecycle.test.ts: create appends exactly one v1-compatible line that round-trips through the v1 parser, resume appends nothing, fork appends a line for the target session.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.