Repository navigation
feat(opencode): support OpenCode v2 transfers + Windows rollback guard fix - #3
Merged
Merged
Conversation
- add bidirectional format adapter for Antigravity Desktop (antigravity-desktop) - support reading and writing SQLite conversation databases, conversation_summaries.db, and agyhub_summaries_proto.pb - handle both 19-column and 21-column schemas (with raw_summary, group_id) in conversation_summaries.db - support up to 512 MB native sessions in Antigravity CLI and Desktop - auto-register projects in app_storage.json and ~/.gemini/config/projects/ when installing desktop sessions - document the desktop storage architecture in docs/antigravity-desktop-interoperability.md - add comprehensive unit tests and native language server verification
…tform guards - add default_language_server_path() supporting darwin, linux, and win32 - add electron_user_data_dir() resolving app_storage.json on macOS, Linux (XDG), and Windows (APPDATA) - update verify_pinned_desktop() with clear platform diagnostics and unvalidated bypass for Linux/Windows - document platform support status in docs/antigravity-desktop-interoperability.md - add platform resolution tests across macOS, Linux, and Windows
…es/bin and bump version to 2.18.1
…, and Windows - add verified binary sizes and SHA-256 digests for Linux x64 and Windows x64 - implement PINNED_DESKTOP_SPECS table validating binaries against platform-specific digests - enhance Linux search order (PATH launcher, /opt, ~/.local/share) and Windows case-insensitivity - update documentation with complete multi-platform installer matrix
…ntigravity-x64 in Linux search order
…tion arguments - rename docs/antigravity-desktop-interoperability.md to docs/antigravity-desktop-format.md to match repository naming convention - update invocation flags and arguments to reflect verified package launcher details - update reference in CHANGELOG.md
…-format.md standards
…file URI paths - Close SQLite connection handles explicitly before unlinking or cleaning up temporary directories (prevents WinError 32 on Windows). - Bypass directory fd fsync on Windows NT in conversion.py, jsonl.py, and devin.py to avoid PermissionError [Errno 13]. - Normalize Windows drive letter parsing in file:/// URIs (strip leading slash before drive letter). - Adjust stat permission test assertion for Windows NT (0o666 vs 0o600). - Restore standard PyPI endpoints in uv.lock.
…a 31
- Move the Hermes pin from 0.20.6 (v2026.8.27, schema 26) to 0.22.2
(v2026.9.24, schema 31). Schema 31 keeps every column the adapter requires
from schema 26, so the source projection and the required-column gates are
unchanged.
- Match the version line on its pinned prefix instead of the whole line: 0.22.2
appends build provenance (" Hermes Agent v0.22.2 (2026.9.24) · stable ·
local <sha>"), which made the exact-equality gate fail on a correct install.
- Keep the full --version report, because it publishes the install directory
the runtime resolution needs (resolved in the next commit, which completes
the write path for this release).
- Track the new pin in the native corpus fixture script and parser docs.
- Ask the launcher for its runtime command (hermes --print-runtime-command) and reproduce the prelude the published launcher applies to itself: clear PYTHONHOME, PYTHONPATH and VIRTUAL_ENV, insert the install root and import hermes_bootstrap, which selects and activates the store's dependency generation. Store Python ships without third-party packages, so importing hermes_state failed on ruamel before this. - Recover the install root from the "Install directory:" line, or from the launcher's own sys.path.insert bootstrap when that line is absent. A Windows launcher is a compiled zipapp, so the file head carries no usable shebang; the shebang stays as the fallback for builds predating the flag. - Keep the Windows essentials (USERPROFILE, HOMEDRIVE/HOMEPATH, APPDATA, LOCALAPPDATA, SYSTEMROOT, SYSTEMDRIVE, WINDIR, PATHEXT, COMSPEC, TEMP, TMP) in the sanitized child environment and mirror HOME into USERPROFILE. Without them the CLI cannot resolve a home directory, returns non-zero and prints nothing, which surfaced as "observed <unavailable>". - Initialize a missing state.db through the native SessionDB constructor: hermes sessions list no longer creates the store on a read of an absent one. - Report the launcher's stderr tail when initialization fails instead of a bare "failed to initialize" message.
…nlink Windows refuses to delete a file that still has an open handle, so every failed shared-database install died with WinError 32 while unlinking its reserved manifest and masked the original install error. Close the guard first in each of the six install rollback paths and re-close it as a no-op in the surrounding finally, so the guard is still released exactly once on every path.
…d range Windows reports st_dev as an unsigned 64-bit volume serial (15332589137577146414 for an NTFS system volume), which SQLite cannot bind at all: the session INSERT raised OverflowError, the root scan failed closed, and every SQLite-backed root (OpenCode, Kilo, Hermes, MastraCode, Devin) ended as scan_failed. Store the folded value and compare through the same helper so refresh reuse keeps matching. Adds a regression test with a simulated unsigned identity, covering storage and the second-refresh unchanged path.
…s limits - Pin table moves to 0.22.2 (v2026.9.24, schema 31); drop the source-archive hash pin, since a git install reports its own build provenance on the version line and rebuilds would invalidate a recorded hash. - Document how the installer reaches the Hermes runtime: the published --print-runtime-command, the store Python plus hermes_bootstrap prelude, the install-directory recovery, and the Windows essentials the sanitized child environment must keep. - Record the native re-validation performed for this promotion (read-only store snapshot, isolated-bundle install, and a real Claude Code to Hermes transfer with linked tool calls) and state plainly what was not verified: a live resume against a provider, native compaction after import, and WAL snapshots under a concurrent writer. - Update the compatibility, specification, development, troubleshooting, and credential-free testing tables to the new pin, and move the native corpus fixture script to the new release, with an explicit note that its entry-point wrapper bytes are per-install and must be re-recorded.
…title Hermes enforces idx_sessions_title_unique, a partial unique index over non-null session titles, and its own importer treats the field as cosmetic: it wraps its title write in a suppressed exception. The migrator inherited the raw refusal, so re-migrating a conversation into a store that already held its title failed the whole bundle with "UNIQUE constraint failed: sessions.title". - Add hermes.title_taken(), hermes.without_title() and hermes.bundle_title(): an advisory read-only lookup, a bundle whose only change is the cleared cosmetic field, and the title a bundle currently carries. - Sample the target store before the import and drop the title when it is already taken, counting the omission as title:duplicate_in_target and reporting a target_title_omitted warning in the manifest instead of failing the migration over a cosmetic field. - The import transaction still enforces the constraint itself: a title created by a concurrent writer in between fails closed, with no partial session. - Covered by a credential-free round-trip test and a conversion-level test; verified end to end against a live 0.22.2 store, where the untitled session lists in the native picker with all 22 records intact.
The migration manifest carries the title:duplicate_in_target counter and the target_title_omitted warning; the CLI's content-free stdout summary is built before any installer runs for every harness, and a dry run writes no manifest.
The bundle crossed the process boundary as text: the caller encoded it with its own locale codec while the store interpreter always decodes UTF-8, so on a Windows console whose code page is not UTF-8 (no PYTHONUTF8/PYTHONIOENCODING set) the install either died with UnicodeEncodeError or replaced every non-ASCII character of the imported conversation — title and message bodies alike — with U+FFFD inside the target store. - Move the payload as bytes (`input=data`, no text mode) and have the helper read `sys.stdin.buffer`; decode the helper's stdout/stderr explicitly as UTF-8. - Start the child interpreter with `-X utf8`, and read the launcher probes with an explicit UTF-8 encoding, so neither direction depends on the console code page. - Extract `_run_helper()` so the transport has a single seam, covered by a credential-free regression test asserting bytes on stdin and the `sys.stdin.buffer` helper. - Give the opt-in native trajectory accented title/body text (which would have caught this) and the Windows essentials in its child environment.
HERMES_HOME still wins, but the fallback is now the CLI's own platform default: %LOCALAPPDATA%\hermes on Windows (or ~/AppData/Local/hermes without that variable) and ~/.hermes elsewhere. A bare ~/.hermes on Windows named a directory the CLI never writes to, so `--to hermes` and automatic root discovery missed the real store unless the caller exported HERMES_HOME by hand — the CLI finds its store with no environment at all, and now so does the migrator. - Thread a `platform` override through hermes_home()/state_database_path() so the resolution is testable off-platform, matching the Devin adapter's pattern. - Extend the resolution test and add one covering both platform defaults, the unset-LOCALAPPDATA fallback, blank HERMES_HOME, and the explicit root. - Update the CLI reference, format doc, and changelog.
The adapter work in this branch (Hermes 0.22.2 / schema 31, the Windows runtime and home resolution, the UTF-8 import bundle, the duplicate-title rule, and the signed stat identity fix) plus the Antigravity Desktop Windows fixes are the 0.12.0 release, so stop reporting 0.11.0 from a checkout that behaves differently. - pyproject.toml, __init__.__version__, uv.lock, and the CLI assertion move to 0.12.0. - Fold the changelog's Unreleased section into "0.12.0 - 2026-10-16". - Point the CLI reference and the specification at 0.12.0. The website/ npm package keeps its own 0.11.0 version field; nothing reads it.
A real 73 MB transcript with two compactions failed with "Claude active graph contains an ancestry cycle" even though its active graph is intact. The declaration is the same as the one the validator was written for, but the layout under it moved: the boundary is now followed by attachment records, then the compact summary, then the preserved tail — so the summary is no longer the boundary's direct child — and preservedMessages also names ids from the summarized branch that are no longer on the active path. Validate the loop the way the declaration describes it rather than by the shape one build happened to use: the tail must walk up to the summary and the summary up to the boundary, with every hop already on the walked path; head/tail must still appear in preservedMessages, and a boundary whose declaration does not close over the walk still raises. Requiring every declared id to be on the path rejected readable transcripts, because the declared set spans branches the walk never enters. - Test the 2.1.300 layout (boundary -> attachment -> summary -> tail) and the still-fatal unclosed declaration.
The two boundaries in one real session carry different shapes under the same declaration — one is the summary's direct parent, the other sits four hops below it, with the attachments the CLI interleaves in between. Attribute the variance to the interleaved records rather than to a build change, and say what the validator actually requires.
The store's CLI import refuses any one session above 5 MiB while its own web import accepts 25 MiB, so a portable session carrying a pasted screenshot (inline base64 image) was rejected as "session exceeds the import size limit" although nothing about the bundle is invalid — a real 73 MB Claude transcript produced a 6.88 MB bundle whose rows are 4.5 MB of user-pasted PNGs. - Size the bundle exactly the way the store does (`measured_session_bytes`, ignoring the derived `timings`) and request `max(5 MiB, min(measured, 25 MiB))` from the import helper, which applies it to `SessionDB._IMPORT_MAX_SESSION_BYTES` before opening the database. The vendor ceiling is the same one its web import uses, so the migrator never exceeds what Hermes accepts elsewhere. - Report `target_import_budget_raised` (bundle bytes, default, applied budget) in the manifest whenever the budget moves, so a raised cap is never silent. - Carry the session payload on `ParsedHermesBundle` instead of re-parsing it.
Both changes after 0.12.0 are backwards-compatible bug fixes: a readable Claude transcript refused as an ancestry cycle, and a valid bundle refused by the store's 5 MiB import cap. No CLI surface changed and the manifest schema stays at 2; the only additions are a keyword-only `platform` override and the `target_import_budget_raised` warning. - pyproject.toml, __init__.__version__, uv.lock, and the CLI assertion move to 0.12.1. - Add the 0.12.1 changelog section covering both fixes. - Point the CLI reference and the specification at 0.12.1.
|
🟢 Contributor eligibility requirements met for @gabszap.
|
The new Hermes code carried five over-length lines and three files the pinned ruff 0.16.10 would reformat, so the python matrix jobs failed at the first gate before any test ran.
The adapter pin moved to 0.22.2 (v2026.9.24, d923a5eb) but the credential-free gate still installed 0.20.6 (5fc308a7), so the exact-client check compared the installed revision against the new pin and failed. - install-native-test-clis.sh: tarball url, recorded revision and the expected archive hash for the 0.22.2 source tree. - rename the native trajectory test and the corpus skip messages to the new release.
gabszap
force-pushed
the
feat/opencode-v2
branch
from
October 16, 2026 23:37
4e7d11d to
5b12afd
Compare
0.22.2 adds auto_archived, compression_overload_streak, compression_recovery_deadline, created_source, timings, tool_names and transport_profile to the official session export, plus absorbed_message_uids, message_uid, tool_call_uid and tool_call_uids on each message. tool_names carries the runtime tool catalog, so join system_prompt in the private-field scrub. Also scrub the JSON-escaped variant of the private cwd: on Windows the raw cwd contains backslash separators, which json.dumps escapes in tool_calls arguments, defeating the plain-text replace.
Recaptured portable-rich against the pinned v0.22.2 build (d923a5eb) on linux-x86_64 through the loopback provider: schema 31 state.db, 8 messages, 2 tool calls/results, user image accepted (textual fallback), document, audio and video rejected by the CLI image surface. The sanitizer run lists schema-driven session and message scrubs plus runtime-ownership removal. Route and source consumers now load the hermes fixture from sources/hermes/0.22.2/portable-rich; the loopback context in capture-hermes.py and test_hermes_native.py rises to 131072 tokens so the 0.22.2 client (which requires a 64k minimum) accepts the scenario.
gabszap
force-pushed
the
feat/opencode-v2
branch
from
October 17, 2026 02:55
705090e to
20d715f
Compare
…0.22.2 install-native-test-clis.sh synced the pinned Hermes tree with Python 3.13, but the d923a5eb lockfile's supported environments require at least 3.14, so the exact-client gate aborted before running any Hermes tests. And the central catalog test fixture hand-wrote a schema_version 26 store, which the 0.22.2 adapter now refuses, so its shared-database assertions saw a root error. Raise both: sync with 3.14 and stamp the fixture store as 31.
The exact-client gate pins Hermes Agent 0.22.2, but the installed tree is a gitless tarball extraction, so the client resolves its own version report as "Hermes Agent vunknown (2026.9.24)" and verify_client refuses it before any Hermes probe runs. Recreate a minimal checkout in the install directory with the pinned release tag so the runtime reports Hermes Agent v0.22.2 (the emulated CI flow was validated on WSL: reported version, wrapper shape, and uv sync with Python 3.14 all match the pinned environment).
Hermes 0.22.2 names a session that closes without a title, so the request logged last after a resumed CLI run is the title-generation call; the test read the wrong provider request and failed against the literal chat call. Select the last chat request (skipping the "You name chat sessions" system prompt) as the replay target. Validated on WSL against the emulated CI install: the exact 0.22.2 client runs the resumed flow and the markers land in the chat request as before.
Address xhluca's PR xhluca#17 findings with ordered portable tool-result blocks, explicit structured-error omissions and native validation before projection. Incorporate read-only v2 catalog discovery and Kilo legacy-schema separation, while retaining upstream contributor-policy checks. Adapted from TheViniAlmeida's PR xhluca#17 commits 662976e and 2e3671d, which build on our focused PR xhluca#15. Credit and reuse history are documented in docs/opencode-v2.md. Validation: Linux Python 3.12.3; focused v2/native/catalog/CLI/conversion/inspection/target gate 335 passed, 1 intentional private-store skip with stock OpenCode 2.0.23. Ruff, formatting, contributor-policy tests and whitespace checks pass. No full-suite or Windows-native claim. Co-authored-by: TheViniAlmeida <eu@thevinialmeida.com>
…ection The cherry-picked OpenCode v2 work landed its changelog bullet inside the released 0.12.0 section, because its branch was cut from the v0.11.0-era file. The work is not released yet, so it belongs at the top.
… unlink The v2 OpenCode and Kilo install paths close the identity guard only in `finally` while the rollback unlinks the manifest inside `except`: on Windows the unlink then fails with WinError 32, and that cleanup error replaces the failure that triggered the rollback — a real import into a live 2 GB store reported "o arquivo já está sendo usado por outro processo" instead of naming what stopped finalization, while the session was already inside the store. Same ordering the other six install paths already use.
Routers such as 9router serve OpenCode's own catalogue under an `oc/` prefix while keeping their own provider id, so a session imported from a Hermes install that ran through the router recorded `9router/oc/deepseek-v4.1-flash-free` on its messages. OpenCode resolves a turn from the reference the last message carries, so the imported session kept routing through the router, and OpenCode's free tier refuses mirrored traffic with "free tier can only be used from within OpenCode" - the session could not be continued at all, while a fresh session using the same model worked. A reference that names the target's own namespace now resolves to the target provider (`opencode/deepseek-v4.1-flash-free`) for the session header and for every message. References that do not claim the namespace are written unchanged. - map `oc/<model>` references to the `opencode` provider when building the portable v2 bundle, for the session header, assistant messages and compaction entries; - cover both directions: the mirrored reference is rewritten, an unrelated provider reference is left alone.
gabszap
force-pushed
the
feat/opencode-v2
branch
from
October 17, 2026 03:41
5b9b14a to
88f452a
Compare
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 3 de 3 — empilhado: merge por último (depois do PR 1 e do PR 2).
O que entra
OpenCode v2 (2.0.x) como fonte e alvo. O adapter estava pinado em 1.17.20 e falhava fechado no OpenCode atual:
expected 1.17.20, observed opencode v2.0.30. Os 5 commits base são do @tudstudent (a branchopencode-v2-transfer, base do PR feat(opencode): support v2 native transfer and pinned source-target validation xhluca/session-migrate#15 e do PR feat(opencode): support v2 transfers and catalog discovery xhluca/session-migrate#17 no upstream), preservando autoria: seleção da família de schema pela CLI instalada, import/export pelo CLI público (opencode session import/export --standalone, sem escrever tabelas privadas), descoberta de catálogo viasession_v2, preservação de texto ordenado, tools vinculadas, mídia inline e resumos de compaction, com conta explícita do que é omitido.Meu ajuste de merge: a entrada de changelog deles caiu dentro de uma seção já lançada (a base era antiga) — movida para
## Unreleased.fix(windows): release the opencode manifest guard before the rollback unlink(nosso). Nos caminhos de install do OpenCode e do Kilo a guarda de identidade do manifesto era fechada só nofinally, mas o rollback deslinkava o manifesto dentro doexcept: no Windows o unlink falha comWinError 32e essa mensagem de limpeza substitui a falha que causou o rollback. É a mesma classe que o PR fix(windows): close native transfer guards before rollback cleanup xhluca/session-migrate#14 (fechado) do @tudstudent tentava resolver, aplicada aos dois caminhos que faltavam. Sem isso, uma migração real de 2.017 registros para um store de 2 GB terminou com erro de "arquivo em uso" enquanto a sessão já estava importada.Validação real (2.0.30 stock, Windows)
opencode v2 → hermesopencode v2 → claudeunvalidated_source_version observed 2.0.30 / validated 2.0.23(aviso, não bloqueia)claude → opencode v2session_v2com título acentuado, 1065session_message, 31 linhas de mídia inlinehermes → opencode v2session_messagetest_opencode_v2*.py→ 177 passed, 8 skipped (os skips são os gates nativos que aceitam só 2.0.22/2.0.23 e exigem Linux)Nenhuma escrita no store real durante os testes: tudo com
XDG_*/HOMEisolados e--standalone.