Skip to content

feat(session): cross-platform session migration and team session archive - #593

Open
lurkacai0831 wants to merge 8 commits into
Tencent:mainfrom
lurkacai0831:feat/session-user-repo
Open

lurkacai0831 wants to merge 8 commits into
Tencent:mainfrom
lurkacai0831:feat/session-user-repo

Conversation

@lurkacai0831

@lurkacai0831 lurkacai0831 commented Sep 16, 2026

Copy link
Copy Markdown

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

Command Purpose
session platforms List supported/installed agents
session migrate <id> -s <src> -t <dst> Migrate one session; --all for the recent few
session rollback <id> --platform <dst> Undo — deletes only what was written on the target side

Team archive

Command Purpose
session push --source <agent> Archive the current project's sessions; --all covers every workspace of that agent
session pull Pull and rebuild indexes
session list / list --all Current project / all projects (--all adds a SOURCE column)
session search <kw> --all Full-text search across archived projects
session resume <name> --platform <agent> Restore into a local agent and continue

Platforms: claude-code, codebuddy (CLI), codebuddy-ide (IDE sidebar), codex, cursor, workbuddy, plus the claude-internal / tclaude / codex-internal / tcodex variants.

Design notes

  • IR + adapters (2N, not N²) — one adapter per platform instead of a converter per pair.
  • Archive key comes from the session, not the shell. 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 _unattributed with an English warning. Running push from another directory still archives under the right project.
  • Migration is reversible. Without rollback, nobody dares migrate anything real.
  • Project-level by default, user-level with --all — a project team repo shows only its own sessions; a personal repo gets the cross-project view.
  • Complements 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 that fidelityScore is a proxy for IR-block degradation, not a byte-equality guarantee.

Test plan (all executed against the built CLI)

  1. npx tsc --noEmit → 0 errors ✅
  2. npx vitest run src/__tests__/session-{sync,cmd}.test.ts src/__tests__/{codebuddy-ide-adapter,session-title,fidelity-sweep}.test.ts50/50 passed
  3. npm run build → success (ESM 1.72 MB) ✅
  4. session platforms → all 6 platforms listed, codebuddy + codebuddy-ide both ✓ installed
  5. Migrate a real CodeBuddy session to claude-code → Fidelity: 100.0%, target id printed; claude --resume <id> picks it up with the session title ✅
  6. session rollback → only the target copy removed, source session intact ✅
  7. Archive into a fresh team repo from two projects (different git remotes) → lands in sessions/repos/github.com_org_beta/ and sessions/repos/gitlab.com_team_alpha/
  8. Non-git directory → archived under _unattributed with an English warning ✅
  9. session list --all → both projects listed with their SOURCE identity; session list from a project shows only that project's ✅
  10. session search <kw> --all → hits sessions across two repo identities ✅
  11. Re-push the same session → entry updated in place, no xxx_1 duplicate ✅
  12. session resume from the wrong project → English error naming search --all / --cwd, exit 1 ✅
  13. session push into a non-git repo root / no remote / concurrent index.lock → one-line English error + exit 1, no stack trace ✅
  14. Full 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 import session-flow
  15. fidelity-sweep.test.ts roundtrips 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-evidence branch 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 / --all variants.

IDE relay guide 1

IDE relay guide 2

Step 2 — run the migration

migrate

  • A real 47.5 MB session (2661 messages) from the CodeBuddy IDE sidebar store.
  • Preview before anything is written: Source / Target / session title / CWD / message count.
  • Fidelity: 100.0% (Mode A) and Preserved: 5816/5816 blocks.
  • The 12 tool_not_in_target warnings 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.
  • The target path is printed explicitly (~/.claude/projects/-Users-...-teamai-cli/<uuid>.jsonl).

Step 3 — resume in Claude Code: the relay completes

resume

Claude Code's /resume picker, 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 the type:"summary" record fix, this picker showed a bare session id (824ff784).

Same session, before and after

Source (codebuddy-ide) Target (claude-code)
Session id 1f02805a… ecb55203-1775-4aff-9fc0-6870145e5488
Title 完整的分析一下 seeeionflow ts版本的能力和 tea same (shown in /resume)
Messages 2661 2661 (1337 user + 1324 assistant)
Content blocks 5816 5816
On-disk size 47.5 MB 21.2 MB
Title record IDE index.json conversations[].name type:"summary" record

Notes on the numbers:

  • The screenshots are the snapshot at migration time (2661 / 5816). The source session is the very one this workflow runs in and kept growing afterwards (a fresh preview now reports 2675 / 5837) — the source grew, nothing was lost in transfer.
  • The size difference (47.5 MB → 21.2 MB) comes from the two stores using different JSONL envelopes (IDE message files carry extra / requests metadata; Claude records are flatter).
  • The 12 tool_not_in_target warnings degrade CodeBuddy-only tools to text blocks: content preserved, tool structure not replayable.

Notes for reviewers

  • Built on origin/main (8ea0612) — rebased, CHANGELOG conflicts resolved by keeping both sides.
  • Happy to split: (1) adapters + migrate/rollback, (2) team archive + --all, (3) the learnings loop.
  • Open question from the issue: archived sessions are not scrubbed (unlike 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.

lurkacai added 8 commits September 16, 2026 18:39
- new codebuddy-ide adapter: list/read/write/delete for the IDE sidebar
  history store (previously write-only via the cli adapter's implicit
  double-write, so IDE sessions could neither be listed nor migrated)
- cli adapter no longer writes/deletes the IDE store; read/write symmetry
- detect both on-disk history layouts (default instance stores history
  one level higher) so its conversations are no longer skipped
- reuse 32-hex conversation ids as-is instead of hashing twice, fixing
  rollback reporting success while leaving sessions in place
- encode cli project dirs with CodeBuddy's own rule (spaces kept), so
  workspaces like 'teamai cli' can be listed and read
- resolve symlinks before hashing workspaces (/tmp vs /private/tmp)
- shared title cleaning: skip <system-reminder>-style injected first
  messages instead of leaking prompt text into listings
- user-facing errors and test assertions in English
…rectness fixes

M1 correctness:
- migrate --push re-reads exactly the migrated target session ids
  instead of 'the N most recent', which could push unrelated sessions
- meta no longer lies: fidelityScore uses the real preview score and
  createdAt records the session's own creation time, not push time
- resume failures hint that the session may be archived under another
  project identity (search --all / --cwd)
- empty pushes no longer report success when nothing was committed
- 14 Chinese user-facing strings converted to English

M2 user-repo capability:
- SyncManager: listAllRepoIdentities() reverse-maps canonical identities
  from each repo's _index.json; listSessionsAcrossRepos() merges entries
- list --all / pull --all / search --all give the cross-project view
  (search --all previously had a dead loop and never searched other repos)
- push --all archives every workspace of one platform (--source),
  confirming before pushing more than 5 sessions (-y skips)
- archive key derives from the session's native cwd instead of the
  directory the command ran in; unknowable workspaces (codebuddy-ide
  md5 placeholders) archive under _unattributed with a warning
- claude-code / codebuddy / cursor readSession recover the native cwd
  from the first JSONL record instead of lossy directory-name decoding
- pushing the same session twice updates the entry instead of creating
  _1 duplicates (dedup key: origin sessionId + author)
…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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Cross-platform session migration and team session archive

1 participant