feat(session): cross-platform session migration and team session archive - #593
lurkacai0831 wants to merge 29 commits into
Conversation
|
Reviewed the full branch (type-check + 50/50 tests green, built, smoke-tested, merges cleanly onto 1. Delete 2. 3. Archived sessions are unscrubbed — needs gating before merge. Unlike |
|
Findings
Per request, I only inspected the specified diff and did not run or build PR code. |
|
Blocking Findings
The base |
|
Blocking Findings
Other Findings
|
|
Findings
|
|
Findings
Blocking Process Issues
No commands from the PR were executed; review was diff/read-only. |
|
Thanks for the thorough review. All findings are addressed in Findings[P1] SQL injection in codex rollback — fixed. The id now must match the Codex v7 shape, and quotes are escaped on top. Verified: [P1] [P1] archive commit includes unrelated staged changes — fixed. [P1] failed commit reported as a successful push — fixed. The commit now throws on non-zero exit (hooks/gpg/identity), and [P1] repo identity encoding collision — fixed. Encoding is now reversible: [P2] [P2] un-sanitized git author name as a path — fixed. Author names are sanitized as path segments ( [P2] archived sessions lose their original title — fixed. The title is persisted verbatim in [P1] missing end-to-end matrix — added below. (See matrix.) [P2] Chinese comments in production code — in progress. The new modules ( Also fixed while here: codex End-to-end matrix (executed against the built CLI, isolated HOME sandboxes)
Full transcript of the multi-agent verification run: 4 sandboxes (one per member), each with an isolated |
|
Blocking Findings
|
|
Status update on [P2] Chinese comments: the five new modules ( |
|
Blocking Findings
Rule/Documentation Issues
|
|
This PR currently has merge conflicts with |
|
Blocking Findings
Other Findings
Resolved From Earlier Reviews
I only inspected the specified diff and did not run, build, or install PR code. |
Blocking Findings
Other Findings
Resolved From Earlier Reviews
I inspected only the specified diff and did not run, build, install, or execute PR code. |
Blocking Findings
Other Findings
Resolved From Earlier Reviews
I inspected only the specified diff using read-only Git commands and did not run, build, install, or execute PR code. |
Blocking Findings
Other Findings
Resolved From Earlier Reviews
I inspected only the specified diff using read-only Git commands and did not run, build, install, or execute PR code. |
|
Rebased onto @jeff-r2026 — your three:
Blocking findings from the codex review, all fixed:
From the P2 list: Verification: End-to-end on the built CLI (temp HOME + temp team repo): migrate claude-code → codebuddy, push (archived under One item deliberately left open: the Chinese in-code comments (the P2 English-only note). User-facing strings were already converted; roughly 980 comment lines remain. I kept that out of this push so the security changes stay reviewable on their own — say the word and I will convert them before merge. |
|
Blocking Findings
Other Findings
Resolved From Earlier Reviews
I inspected only the specified diff using read-only Git commands; I did not run, build, install, or execute PR code. |
22fa235 to
d813776
Compare
…ck scoping Review follow-up on Tencent#593: - Sessions loaded from a team archive are marked untrusted and no longer read local image filePaths (`mayReadLocalImageFile`); a crafted archive could otherwise pull any readable file into a restored session. IDE asset and message file names are reduced to a bare name, so archive-controlled labels/ids cannot write outside assets/ or messages/. - Refuse writes and deletes through symlinked archive paths (a team repo checkout can carry symlinks out of sessions/). - canonicalizeRemote strips userinfo, so a token in the remote never reaches archive metadata, indexes or list output; ssh:// and scp forms now match the https identity. - Repo directory names are encoded injectively (%XX) and author names escape the Windows separator. - gitCommit reports committed / no-changes / failed separately, stages only files this run wrote, and a failed remote push exits 1 instead of printing a success line. - rollback: every adapter returns whether it deleted anything, rollback --cwd no longer falls back to a global delete, and Codex/Cursor/WorkBuddy unregister only after the scoped transcript is found. - migrate: same-platform overwrite needs --target-cwd or -y; an ambiguous session-id prefix is rejected; untrusted archive text is stripped of terminal control sequences before printing; each session is attributed to its own workspace git identity. - rebuildIndex keeps origin title/author; pull --all rebuilds by directory so repos with a missing index are repaired; indexes are written atomically. - --scrub drops image filePath (local home paths leaked into the archive); cursor temp SQL files are created 0600.
…L, image paths Team review of Tencent#593 found two blocking issues and a set of smaller ones: - A corrupted _index.json was read as empty, so the next upsert overwrote it with the single new entry and every other session of that repo disappeared from list (files still on disk). Write paths -- including the dedup lookup, which otherwise wrote an extra _1 copy first -- now rebuild from disk and stop the push if that fails. rebuildAllIndexes moves a legacy '_'-folded directory onto the encoded name, so reads (which resolve that name) see its sessions again instead of an index nothing reads. - deriveTargetSessionId hashes the resolved cwd, Codex rollback --cwd compares rollout cwd after resolving, and Cursor's composerId does the same: /tmp/x and /private/tmp/x are one workspace on macOS, and the mismatch made rollback report 'not found' and delete nothing, or registered two Cursor entries for one session. claude-code and codebuddy keep the cwd out of the hash on purpose (per-project storage); the comment now says why. - canonicalizeRemote also strips a password that contains '/', which the old rule could not match and which therefore reached meta, _index.json and the SOURCE column of list --all. - Cursor/WorkBuddy SQL scripts are piped to sqlite3 instead of written to a predictable temp file in shared /tmp (symlink-plantable, world-listed). - Identity encoding is byte-wise (non-ASCII round-trips), git hosts are lower-cased, the symlink guard runs before mkdir, and an archived session name must be a plain file name before it reaches the filesystem. - migrate --scrub keeps an image block's local filePath (only archiving drops it) and runs before the thinking-block downgrade, so fidelity is computed from the scrubbed but not-yet-downgraded session: blocks lost to scrub count as degraded, and thinking blocks still count, which keeps the score and the degradedBlocks warning in line with --dry-run's preview. - rollback without --cwd deletes every workspace copy, as its help promises, instead of only the first match. Confirmations use isInteractive(); migrate --all refuses with a non-zero exit in a non-interactive run instead of printing Cancelled. and exiting 0; rollback failures print one English line. Tests: 75 passing across the session suites, including new regressions for corrupted-index repair, legacy directory repair (now asserted through listSessions), credential stripping with a slash in the password, image embedding on migration (and its untrusted counterpart), one Cursor id per workspace, failed commit and failed remote push (both exit non-zero). Known limitation left alone: two concurrent pushes can lose one index upsert; rename keeps the file itself intact and 'pull --all' rebuilds from disk.
|
Pushed: rebased onto Rebase. Used Review pass. I ran a three-person review over the diff — migration core, archive/security, CLI/docs. Four blocking findings and seventeen smaller ones; all fixed except the three listed at the end. Fixed (blocking):
Also fixed: credential stripping when the password contains Left alone, deliberately:
Verification: One behaviour change worth knowing: for the global-key platforms ( |
|
Blocking Findings
Other Findings
The PR description includes representative built-CLI E2E evidence, so I found no missing-testing-record violation. Earlier image exfiltration, IDE traversal, symlink-write, credential, Git failure-reporting, scoped rollback, index-repair, temporary-SQL, metadata-rebuild, and ambiguous-prefix findings are resolved. I only used read-only Git inspection; the requested three-dot range had no merge base in this shallow checkout, so I reconstructed the PR from its available feature parent |
|
Blocking Findings
Other Findings
The PR description includes representative built-CLI E2E evidence, so there is no missing-testing-record finding. Earlier image-file exfiltration, image/message path traversal, symlink-write, credential leakage, Git failure reporting, scoped rollback, index repair, temporary-SQL permissions, metadata rebuilding, and ambiguous-prefix findings are resolved. I used read-only Git inspection only. The requested three-dot range has no merge base in this shallow checkout, so I reconstructed the available PR feature series from |
…red fixes Tests (29 new cases): - session-sync.test.ts: identity reverse-mapping, cross-repo listing, dedup by origin sessionId, index rebuild round-trip - session-cmd.test.ts: list/pull/search --all, push --all confirmation, native archive key, English output assertions Docs: - usage-guide (en/zh): new Session Sync & Migration section - README (en/zh): capability row and command cheat-sheet entries - CHANGELOG: M1/M2 entries plus the fixes below Fixes found by the real-CLI E2E run: - encode symlink-resolved cwds into project directory names for claude-code / codebuddy / cursor / workbuddy (writing /tmp/x used to create a directory listing from /private/tmp/x could never see) - keep the local commit and print a warning when the remote push fails instead of crashing after a successful save - apply the shared injected-title cleaning to claude-code / workbuddy / cursor (their first 'user message' is often a system-reminder wrapper, which used to become the archived session name)
Claude Code's /resume picker shows the bare session id (e.g. 824ff784) for sessions without a type:"summary" record, so every migrated session appeared untitled. Carry the IR session title — already cleaned of injected wrappers by the source adapter — into the target JSONL.
…ommands From the three-way QA sweep (adapters / command layer / fidelity): P0 crashes fixed (found by real-CLI execution): - git add/commit/pull in a non-git or missing --repo-root dumped a full stack trace with internal paths; now a one-line error + exit 1 - concurrent pushes hitting git index.lock crashed the same way - pull with no origin remote / nonexistent repo root reported a misleading ENOENT instead of the actual cause Correctness: - session archive dedup key now includes platform: a session pushed as codebuddy and re-archived after migrating to claude-code are two artifacts, not an update of each other - codex keeps per-message timestamps (read response_item.timestamp, stamp records with the message's own time) — roundtrips no longer collapse the timeline - codex session lookup matches whole ids; a 4-char prefix could resolve to someone else's session file - cursor writeSession is idempotent again (malformed UUID regex minted a new id per write, piling up copies) - claude-code readSession honors the type:"summary" record it writes - workbuddy/claude-code/cursor titles skip injected ai-title/name wrappers and tool-output snippets; extractMeta no longer stops at the first injected block (real question after a system-reminder wrapper becomes the title) - codex writeSession survives an invalid session.createdAt instead of crashing with RangeError - a corrupted sessions/**/_index.json warns with the rebuild command instead of silently emptying the dedup key Robustness: - interactive prompts treat EOF like "n" (Cancelled., exit 0) instead of a silent success; --limit rejects non-positive values; a closed output pipe exits cleanly instead of an EPIPE stack - remote push failures report git's actual fatal line, keeping the local commit Docs: - design doc gains a Known limitations section (fidelityScore is a proxy metric; codex splitting; sessionId is platform-native; flattenDag) - src/__tests__/fidelity-sweep.test.ts joins the suite as the fidelity regression tool (roundtrip matrix over 5 platform routes)
…dempotent Follow-up to the migration work in this PR, driven by real-client verification (Codex Desktop, CodeBuddy IDE, WorkBuddy, Cursor). Every fix below was reproduced against a real client before/after. Visibility: migrated sessions existed on disk but never showed up - codex: sessions are listed from state_5.sqlite, not by scanning rollouts. Write model_provider (buckets the list), keep rollouts legacy so `codex migrate-rollouts` builds the items projection (title/preview/content all come from it), and run it right after writing; if the new file is not indexed yet, start a temporary app-server and call thread/list (the official indexing path). Also match the 0.155 item_completed shape exactly: no client_id on UserMessage (it breaks parsing) and add started/completed_at_ms. - workbuddy: register into workbuddy.db (sessions + workspaces); user_id is discovered from existing rows, connectors/<uuid> or app/sessions.json -- never from device-id, which is a different id and would leave the session invisible behind a user filter. - workbuddy read path used encodeCwdGeneric while writes used the space-preserving rule, so listing by cwd returned 0 sessions. - cursor/workbuddy/codebuddy-ide: keep list registration best-effort but never silent -- warn that the session may stay invisible. Idempotency: repeat migrations no longer duplicate sessions - target ids are derived deterministically from (platform, source id) instead of minting a random uuid, for every adapter. Derived ids are v7-shaped so a re-migration of an already-migrated session reuses the id instead of deriving a new one. - rollback deletes the Codex index rows too (threads, items, turns, projection watermark); previously only the rollout file was removed, leaving an entry that was listed but opened blank. Index deletes run statement by statement: one missing table used to roll back the whole transaction and leave threads behind. Workspace isolation - target cwd defaults to the source session's workspace; --target-cwd is the only way to move a session elsewhere. - claude-code records sometimes store the encoded project dir as cwd; decode it back to a real path (verified against disk). - expanding "list all sessions across directories" kept using the shell's cwd to locate sources, so every migration on that path failed. Pass undefined and let adapters search globally. - --push takes the git author from the session's own repo. Fidelity and images - unknown tools are counted as degraded instead of preserved, so the score stops reporting a misleading 100%; workbuddy joins the THINKING_SUPPORT / NATIVE_TOOLS / IMAGE_SUPPORT matrices (review note: it was registered but absent from both tables). - images are a first-class IR block. codebuddy-ide reads assets (codebuddy-asset://, absolute paths, data URIs) and claude-code reads base64/url blocks; claude-code writes native base64 images, codebuddy-ide copies files back into assets/, platforms without image support degrade to a placeholder and say so in the report. Command layer - refuse to migrate into a target that is not installed instead of writing into a directory nobody reads. - exit 1 when any session in a batch fails, so --all is scriptable. - --all migrates everything (it silently capped at 5) with --limit to cap it and a confirmation listing above 10 sessions (-y skips). Titles: extract from content (summary record, then user text with injected wrappers unwrapped) instead of falling back to "Session <id>" or leaking prompt text; share one implementation across adapters. Tests: src/__tests__/migrate-guard.test.ts covers the not-installed guard, the write path, unknown-tool degradation and image accounting.
Migrated sessions are raw transcripts: whatever was pasted into the conversation -- tokens, keys, passwords, internal hosts -- travels with it into the target agent's store, and from there into anything archived later. `session save` already redacts; migration had no equivalent. `session migrate --scrub` runs the whole IR through the existing `utils/redact` (the same rules `session save` uses, plus secrets found in the current environment) before writing: - text and thinking blocks - tool call arguments (serialized, redacted as a whole, then parsed back so the structure stays an object) - tool results - the session title, since it is what shows up in the target's list The report says how many values were replaced, and reminds that redact is best-effort (pattern matching, not a guarantee) -- same caveat as `session save`. Off by default: a local migration should stay lossless unless asked otherwise.
Reviewer note 3: `session push` archives full raw transcripts into a team-readable repo, so anything pasted during a session (tokens, keys, passwords, internal hosts) becomes readable by everyone with access. - `session push --scrub` redacts each session through utils/redact before it is written (same rules as `session save` plus secrets found in the current environment), and reports how many values were replaced. - Without --scrub, the command now says so explicitly: "Archived as-is: full transcripts (possibly secrets/paths) are team-readable. Use --scrub to redact." No more silent full-text archiving. `session migrate --scrub` (previous commit) covers the migration path with the same rules, so redacting at migration time also makes later pushes clean.
Static review found real defects in the migration/archive path. All
reproduced or verified against the built CLI:
- codex rollback built DELETE statements by string interpolation of a
CLI-provided session id: `' OR 1=1; --` would wipe every thread from
state_5. Reject non-v7 ids and escape quotes.
- sync gitCommit committed everything staged in the repo (`git commit
-m` without a pathspec) and treated a failed commit as success
(rev-parse returned the previous HEAD). Now commits only sessions/,
and compares HEAD before/after -- no new commit is an error.
- encodeRepoIdentity mapped `_` and `/` to the same character, so
github.com/org/a_b and github.com/org/a/b shared one archive
directory and mixed sessions. Encoding is now reversible (`_` -> `__`).
- git author names are free-form: sanitize before using as a path
segment (':'/'/'/'..'/trailing dots would escape the author dir).
- archived sessions rebuilt their title from the truncated file-name
slug on load. The title is now persisted in origin.title and used
verbatim on read.
- migrate --all now enumerates every workspace when no --cwd is given
(it used to silently mean "everything in the current directory"),
and the usage guide no longer claims "the 5 most recent".
- push/pull/resume honor --dry-run: list what would happen and stop
before writing, committing, or restoring.
- codex readSession extracts the title from content like the listing
path; push archives no longer inherit "Session <timestamp>".
Let the archive commit throw (hooks, gpg signing, missing identity all exit non-zero) instead of swallowing the failure and reading back the previous HEAD as the new commit.
AGENTS.md: no Chinese in production code. Translates the comments of the new modules (ids / sqlite / scrub / workbuddy-store) added in this PR; behavior unchanged.
Review note 1 asked for this file to be removed before merging: it was an accidental leftover with no imports; the real CLI adapter lives at adapters/codebuddy.ts and the IDE store is handled by adapters/codebuddy-ide.ts.
…der cancelled
Cursor transcripts stop at tool_use -- the export never records tool
outputs. Migrated sessions therefore had tool calls with no paired
result, and CodeBuddy IDE renders every unpaired call as cancelled:
a real 128-message migration showed a wall of cancelled Bash/Grep
bubbles. Pair each unpaired tool_call with an honest placeholder
('[tool output not captured: Cursor transcripts do not record tool
results]').
… skips [Image] Two follow-ups from the cursor -> codebuddy-ide comparison: - migrated user bubbles kept the raw Cursor wrappers (<user_query>/<timestamp>/<image_files> + attachment paths); IDE messages are the display layer, so run user text through visibleUserText before writing. - a session whose messages were only image attachments got titled "[Image] [Image] [Image]"; the title segment filter now skips [image]/[file]/[attachment] placeholders like it already did [tool_result].
…nwrapped titles The comparison team found the placeholder-result pairing was being short-circuited: some Cursor versions export tool_use without an id (observed 173/173 on one transcript), the reader produced empty callIds, and the truthiness guard skipped every placeholder -- so the target still rendered a wall of cancelled tools. Synthesize a stable per-parse id (tool_<n>) when the source has none, mirroring the writeSession fallback. Titles: prefer titleFromUserText over cleanTitleText for non-injected text as well, so '<user_query>' is unwrapped and [Image] segments skipped before the raw first line wins.
…ck scoping Review follow-up on Tencent#593: - Sessions loaded from a team archive are marked untrusted and no longer read local image filePaths (`mayReadLocalImageFile`); a crafted archive could otherwise pull any readable file into a restored session. IDE asset and message file names are reduced to a bare name, so archive-controlled labels/ids cannot write outside assets/ or messages/. - Refuse writes and deletes through symlinked archive paths (a team repo checkout can carry symlinks out of sessions/). - canonicalizeRemote strips userinfo, so a token in the remote never reaches archive metadata, indexes or list output; ssh:// and scp forms now match the https identity. - Repo directory names are encoded injectively (%XX) and author names escape the Windows separator. - gitCommit reports committed / no-changes / failed separately, stages only files this run wrote, and a failed remote push exits 1 instead of printing a success line. - rollback: every adapter returns whether it deleted anything, rollback --cwd no longer falls back to a global delete, and Codex/Cursor/WorkBuddy unregister only after the scoped transcript is found. - migrate: same-platform overwrite needs --target-cwd or -y; an ambiguous session-id prefix is rejected; untrusted archive text is stripped of terminal control sequences before printing; each session is attributed to its own workspace git identity. - rebuildIndex keeps origin title/author; pull --all rebuilds by directory so repos with a missing index are repaired; indexes are written atomically. - --scrub drops image filePath (local home paths leaked into the archive); cursor temp SQL files are created 0600.
…d reference - CHANGELOG entries for the archive trust boundary, git reporting and rollback fixes. - The README capability table moved to docs/product-overview(.zh-CN).md on main, so the Session Sync row lands there instead. - usage-guide.zh-CN: --all migrates every session, matching the English guide and the implementation. - Regenerate skill-data/core/references/commands.md for the session subcommands.
…L, image paths Team review of Tencent#593 found two blocking issues and a set of smaller ones: - A corrupted _index.json was read as empty, so the next upsert overwrote it with the single new entry and every other session of that repo disappeared from list (files still on disk). Write paths -- including the dedup lookup, which otherwise wrote an extra _1 copy first -- now rebuild from disk and stop the push if that fails. rebuildAllIndexes moves a legacy '_'-folded directory onto the encoded name, so reads (which resolve that name) see its sessions again instead of an index nothing reads. - deriveTargetSessionId hashes the resolved cwd, Codex rollback --cwd compares rollout cwd after resolving, and Cursor's composerId does the same: /tmp/x and /private/tmp/x are one workspace on macOS, and the mismatch made rollback report 'not found' and delete nothing, or registered two Cursor entries for one session. claude-code and codebuddy keep the cwd out of the hash on purpose (per-project storage); the comment now says why. - canonicalizeRemote also strips a password that contains '/', which the old rule could not match and which therefore reached meta, _index.json and the SOURCE column of list --all. - Cursor/WorkBuddy SQL scripts are piped to sqlite3 instead of written to a predictable temp file in shared /tmp (symlink-plantable, world-listed). - Identity encoding is byte-wise (non-ASCII round-trips), git hosts are lower-cased, the symlink guard runs before mkdir, and an archived session name must be a plain file name before it reaches the filesystem. - migrate --scrub keeps an image block's local filePath (only archiving drops it) and runs before the thinking-block downgrade, so fidelity is computed from the scrubbed but not-yet-downgraded session: blocks lost to scrub count as degraded, and thinking blocks still count, which keeps the score and the degradedBlocks warning in line with --dry-run's preview. - rollback without --cwd deletes every workspace copy, as its help promises, instead of only the first match. Confirmations use isInteractive(); migrate --all refuses with a non-zero exit in a non-interactive run instead of printing Cancelled. and exiting 0; rollback failures print one English line. Tests: 75 passing across the session suites, including new regressions for corrupted-index repair, legacy directory repair (now asserted through listSessions), credential stripping with a slash in the password, image embedding on migration (and its untrusted counterpart), one Cursor id per workspace, failed commit and failed remote push (both exit non-zero). Known limitation left alone: two concurrent pushes can lose one index upsert; rename keeps the file itself intact and 'pull --all' rebuilds from disk.
…an it shows The interactive migrate picker lists the 10 most recent sessions and says nothing else, so a library of 19 reads like a library of 10 and older sessions look unmigratable. When there are more, the picker now says how many it is showing and how to reach the rest: --all (optionally --limit N) or an explicit session-id prefix.
The picker listed the 10 most recent sessions and nothing else, so a library of 19 read like a library of 10 and older sessions looked unreachable. It is now paginated: 10 per screen with a 'page 1/2, 19 total' header, n / p to move between pages, global numbering (page 2 starts at 11), and Enter still cancels -- an empty answer must keep cancelling so a non-interactive run cannot end up paging forever. Tests: two new cases (page to page 2 and pick Tencent#11; empty answer cancels), 48 passing across the session suites. Verified against the built CLI with 13 sessions: page 1 shows 1-10, 'n' shows 11-12, '12' migrates it.
…nly for session A case-insensitive LIKE on cursorDiskKV scanned the whole state database, so registration timed out and the migrated session never appeared. Range deletes use the unique index, and a non-UUID id is rejected before it can act as a LIKE wildcard. Session command registration no longer sits on the startup path of every other command. Co-authored-by: Cursor <cursoragent@cursor.com>
520e566 to
05ad839
Compare
…g tree Same-platform writes now derive a new id instead of replacing the original transcript. Session sync resolves the team repo from teamai config rather than the current directory, and git push/pull always name that repo's branch. CodeBuddy IDE message directories are moved aside before a rewrite, and Codex no longer reindexes the whole store when the CLI returns no JSON. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Blocking Findings
Other Findings
The PR description includes representative built-CLI E2E evidence, so there is no missing-testing-record finding. Previously reported arbitrary image reads, image/message-label traversal, archive symlink writes, credential leakage, Git failure reporting, scoped rollback, index repair, temporary SQL files, metadata rebuilding, and ambiguous-prefix handling are resolved. I inspected only the specified diff with read-only Git commands and did not run, build, install, or execute PR code. |
Bring in the dry-run queue-lock fix so this branch matches main and merges without conflicts.
|
Blocking Findings
Other Findings
The PR description includes a build and representative real-CLI E2E records, so there is no missing-testing-record finding. Earlier arbitrary archive image reads, archive image/message traversal, symlinked archive writes, remote credential leakage, Git failure reporting, scoped rollback, index repair, temporary SQL permissions, metadata rebuilding, and ambiguous-prefix findings are resolved. I inspected only the specified diff using read-only Git commands and did not run, build, install, or execute PR code. |
…lish reports on read teamai --dry-run session and the command-table import never saw migrate or push because registration required argv[2] to be session. Resolving the team repo also created and pushed teamai-reports before a dry-run or list could return. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Blocking Findings
Other Findings
The PR description includes a build and representative real-CLI E2E evidence, so there is no missing-testing-record violation. The previously reported global-option registration bug and reports-branch publication by read-only commands are resolved. I inspected only the specified diff using read-only Git commands and did not run, build, install, or execute PR code. |
|
Reply to the latest Codex review (the run that flagged global-option registration, reports-branch publish on read, path traversal, CodeBuddy IDE overwrite, UUID round-trip, and unredacted push). Fixed in bb1aee5:
Not changing the other four findings. They describe real edges, not broken main paths:
|
Closes #587
Adds two layers to
teamai session: cross-platform session migration (move a full conversation between AI tools, previewable and undoable) and a team session archive (archive into the team repo, search, restore).What ships
Migration
session platformssession migrate <id> -s <src> -t <dst>--allfor the recent fewsession rollback <id> --platform <dst>Team archive
session push --source <agent>--allcovers every workspace of that agentsession pullsession list/list --all--alladds a SOURCE column)session search <kw> --allsession resume <name> --platform <agent>Platforms:
claude-code,codebuddy(CLI),codebuddy-ide(IDE sidebar),codex,cursor,workbuddy, plus theclaude-internal/tclaude/codex-internal/tcodexvariants.Design notes
sessions/repos/<repo>/<author>/is keyed on the git identity of the session's own working directory (recovered from the JSONL record where needed); unknowable workspaces (codebuddy-ide md5 placeholders) land in_unattributedwith an English warning. Runningpushfrom another directory still archives under the right project.rollback, nobody dares migrate anything real.--all— a project team repo shows only its own sessions; a personal repo gets the cross-project view.session save(scrubbed summary → digest) rather than replacing it: summary for trends, full session for resuming.Known limitations are documented in
docs/designs/session-user-repo-sync.md— notably thatfidelityScoreis a proxy for IR-block degradation, not a byte-equality guarantee.Test plan (all executed against the built CLI)
npx tsc --noEmit→ 0 errors ✅npx vitest run src/__tests__/session-{sync,cmd}.test.ts src/__tests__/{codebuddy-ide-adapter,session-title,fidelity-sweep}.test.ts→ 50/50 passed ✅npm run build→ success (ESM 1.72 MB) ✅session platforms→ all 6 platforms listed,codebuddy+codebuddy-ideboth✓ installed✅Fidelity: 100.0%, target id printed;claude --resume <id>picks it up with the session title ✅session rollback→ only the target copy removed, source session intact ✅sessions/repos/github.com_org_beta/andsessions/repos/gitlab.com_team_alpha/✅_unattributedwith an English warning ✅session list --all→ both projects listed with their SOURCE identity;session listfrom a project shows only that project's ✅session search <kw> --all→ hits sessions across two repo identities ✅xxx_1duplicate ✅session resumefrom the wrong project → English error namingsearch --all/--cwd, exit 1 ✅session pushinto a non-git repo root / no remote / concurrentindex.lock→ one-line English error + exit 1, no stack trace ✅npx vitest run→ failed files identical to the pre-change baseline (hook-handlers,dashboard-collector,recall-scope-isolation,contribute-self-learnings,push-team-config); none of them importsession-flow✅fidelity-sweep.test.tsroundtrips 5 platform routes and asserts message count/role/text/thinking/tool pairing/timestamps ✅Evidence: a real session relay — CodeBuddy IDE → claude-code (2661 messages / 47.5 MB)
The screenshots below are one continuous relay, all taken from the actual run. They live on the
session-migration-evidencebranch of this fork (kept out of the PR diff).Step 1 — the session lives in CodeBuddy IDE
Everyday work happens in the IDE. The migration does not require leaving it: the in-IDE agent provides and runs the exact commands, from enabling the new CLI to
migrate/rollback/--allvariants.Step 2 — run the migration
Fidelity: 100.0% (Mode A)andPreserved: 5816/5816 blocks.tool_not_in_targetwarnings are expected: this session used CodeBuddy-specific tools (team_create,send_message,ask_followup_question, …) with no same-named counterpart in claude-code. Their inputs and results are preserved as text blocks — readable after resume, but not replayable as tool calls.~/.claude/projects/-Users-...-teamai-cli/<uuid>.jsonl).Step 3 — resume in Claude Code: the relay completes
Claude Code's
/resumepicker, searched for "teamai cli": the migrated session appears under its original title ("完整的分析一下 seeeionflow ts版本的能力和 tea · 1 minute ago · 21.2 MB"). Selecting it continues the conversation with the full history visible — this is the relay completing. Before thetype:"summary"record fix, this picker showed a bare session id (824ff784).Same session, before and after
/resume)conversations[].nametype:"summary"recordNotes on the numbers:
extra/requestsmetadata; Claude records are flatter).tool_not_in_targetwarnings degrade CodeBuddy-only tools to text blocks: content preserved, tool structure not replayable.Notes for reviewers
origin/main(8ea0612) — rebased, CHANGELOG conflicts resolved by keeping both sides.--all, (3) the learnings loop.session save), so anything archived is team-readable. If that needs redaction or an opt-in gate, say so and I will add it before this lands.