Skip to content

feat: import GitHub Copilot CLI and Kimi Code session histories - #569

Open
Harry19081 wants to merge 1471 commits into
feat/external-session-cli-resumefrom
feat/import-copilot-kimi
Open

feat: import GitHub Copilot CLI and Kimi Code session histories#569
Harry19081 wants to merge 1471 commits into
feat/external-session-cli-resumefrom
feat/import-copilot-kimi

Conversation

@Harry19081

Copy link
Copy Markdown
Member

What

Stacked on #560 (retarget to develop after it merges). Adds two new imported-history sources, fully wired end to end — reader → registry → replay router → app commands → sidebar/kanban → Continue-in-CLI:

copilot (GitHub Copilot CLI 1.x)

  • Store: one ~/.copilot/session-state/<uuid>/ per session — events.jsonl event stream + workspace.yaml sidecar (hand-parsed, no yaml dep). Strict uuid-shape gate excludes optimistic-chat-* / pending-session:draft:* junk and 1.0.8-era metadata-only shells.
  • Transcript: user/assistant messages + tool calls paired by toolCallId (bash → run_command_line, str_replace → edit_file_by_replace feeding impact stats); unknown event types tolerated.
  • Usage/repo enrichment from session-store.db, best-effort with lock-safe degradation. Verified against the live db: input_tokens is already cache-inclusive (fresh + cache_read = column; cross-checked via token_details_json) and output_tokens already includes reasoning — both documented in-module. Per-round RoundUsage carries fresh input.
  • Resume: copilot --resume=<id> (attached form — the flag's value is optional, a detached id would parse as a prompt; Copilot itself prints this exact command).
  • Live-verified end to end: real sessions list with names/model/tokens matching Copilot's own printout (26469 / 11772), replay shows user → assistant → tool → assistant, orgtrack resume --print emits the right cd … && copilot --resume=….

kimi (Kimi Code CLI)

  • Store: $KIMI_CODE_HOME/sessions/<wd_key>/<sessionId>/ with state.json + agents/main/wire.jsonl — a persisted op journal (metadata first line, then {type, …payload, time}), format pinned against upstream MoonshotAI/kimi-code (wire protocol 1.5).
  • Transcript from context.append_message (origin-filtered; think parts → thinking chunks; toolCalls paired with tool-role results), cache-aware usage from usage.record (inputOther + cacheRead + cacheCreation → cache-inclusive input), cwd from config.update. Torn final lines and unknown ops tolerated exactly like the CLI's own replay.
  • Resume: kimi --session <id> from the recorded workspace (sessions are cwd-bucketed).
  • Fixture-tested (17 tests incl. multi-workdir discovery, torn-line tolerance, managed-mirror hiding); no local sessions exist on this machine (CLI not logged in) — flips to live data the moment one does.

Deferred: antigravity

agy resumes via --conversation <id> and keeps ~/.gemini/antigravity-cli/ (conversations/ + conversation_summaries.db, schema recorded in docs), but both stores are empty here and seeding needs an interactive Google login — importer deferred, findings in docs/orgtrack-cli.md.

Testing

  • cargo test -p orgtrack_core: 443 passed (13+1 copilot, 17 kimi new), clippy clean for new code, app lib compiles
  • pnpm typecheck clean; frontend suites 1063/1063 (source-registry, kanban filters, ChatPanel)
  • Live: orgtrack sources lists 17 providers; copilot scan/list/show/resume verified against the real store; kimi scans an empty store cleanly
  • Managed-mirror dedup wired for both (copilot/copilot, kimi_cli/kimi) so GUI-launched TUI sessions won't double-list

Harry19081 and others added 30 commits July 22, 2026 13:50
Pre-commit hook ran. Total eslint: 0, total circular: 1
Pre-commit hook ran. Total eslint: 0, total circular: 1
WKWebView reuses pooled keep-alive sockets that the server may have
silently closed, and CFNetwork never auto-retries a POST on a fresh
connection — so the first cloud mutation after idle (create invite,
change role, set floor) deterministically failed with a raw WebKit
"Load failed". Retry such transport-level TypeErrors exactly once
across all org2 cloud RPC clients and the GoTrue token refresh, and
translate the residual failure instead of surfacing the raw message.

Pre-commit hook ran. Total eslint: 0, total circular: 1
Pre-commit hook ran. Total eslint: 0, total circular: 1
Carry the turn source from every frontend dispatch boundary through the
Rust agent wire so Send Now remains a fresh turn even when the cancelled
provider turn is still draining. Only ordinary user submissions may enter
mid-turn steering.

Require and validate the source at the Tauri boundary, reject unknown
persisted values instead of silently coercing them to user_submit, and add
wire, steering, queue, and persistence regression coverage.

Verification:
- pnpm exec tsc --noEmit --pretty false
- pnpm exec vitest run src/store/ui/__tests__/messageQueueAtom.test.ts src/engines/SessionCore/hooks/session/__tests__/useQueueDispatch.classify.test.ts src/engines/SessionCore/hooks/session/__tests__/useQueueDispatch.intervention.test.ts src/engines/SessionCore/sync/adapters/__tests__/rustAgentSendPayload.test.ts src/engines/ChatPanel/hooks/useWorkspaceChat/useUserIntentSubmit.intervention.test.ts
- cargo check -p agent_core
- cargo check -p session_persistence
- cargo test -p agent_core force_send_never_enters_mid_turn_steering --lib
- cargo test -p session_persistence --lib -- --test-threads=1

Pre-commit hook ran. Total eslint: 0, total circular: 1
Pre-commit hook ran. Total eslint: 0, total circular: 1
Pre-commit hook ran. Total eslint: 0, total circular: 2
Pre-commit hook ran. Total eslint: 0, total circular: 2
…-followups

fix(workstation): finish session workspace isolation
Pre-commit hook ran. Total eslint: 0, total circular: 1
feat(sessions): add human work logs
docs(agent): add React performance guidance
Pre-commit hook ran. Total eslint: 0, total circular: 2
Pre-commit hook ran. Total eslint: 0, total circular: 2
Pre-commit hook ran. Total eslint: 0, total circular: 2
fix(worktree): unify session workspace lifecycle
fix(codex): add reconnect action to account cards
fix(cloud): retry RPC posts once on webview transport failures
…s-clean

fix(session): align installing runtime status
Pre-commit hook ran. Total eslint: 0, total circular: 1
Pre-commit hook ran. Total eslint: 0, total circular: 2
Pre-commit hook ran. Total eslint: 3, total circular: 2
Pre-commit hook ran. Total eslint: 3, total circular: 2
Pre-commit hook ran. Total eslint: 0, total circular: 2
Harry19081 and others added 28 commits July 28, 2026 13:49
The panel body ran on the 900px content token while the tab header above it
uses 932, so every section sat 16px narrower on each side. Both now use
DETAIL_PANEL_TOKENS.headerWidth.

Highlight tiles were a card grid inside a bordered SectionContainer — a card
within a card. They now carry the Usage page's stat-tile surface and sit
directly in the flow, which is how UsageStatCards is placed.

Every section is collapsible. CollapsibleSection draws no surface of its own,
so wrapping a SectionContainer in one still yields a single card layer, with
the heading gaining a chevron. Highlights keeps its title but needs no inner
container, since the tiles are already the card layer.

Pre-commit hook ran. Total eslint: 2, total circular: 0
The Over time table showed a sessions column that read 400 on every row —
the drift window is a fixed 400 sessions, so the count was a constant, not
data. What varies is how long each window took to accumulate, so the row now
shows that span; four days in July against eleven in June says something the
constant never could.

Work hour now renders as 12-hour AM/PM everywhere rather than deferring to
the locale. `toLocaleTimeString` gave zh "15时", which reads as a timestamp
rather than as a time of day, which is what the card is asking about.

Card copy: trailing full stops removed from every detail line across all 13
locales (273 strings), the Highlights blurb is gone, and the deck now opens
on the streak — the longest span it can show.

Pre-commit hook ran. Total eslint: 2, total circular: 0
Pre-commit hook ran. Total eslint: 2, total circular: 0
fix(dev): Linux tauri:dev startup failures (WebKitGTK load + i18n mount race)
Resolve loaders conflict with human sessions, keep agent+os fetch for
channel rows, and fix prettier import placement for the refresh poller.

Pre-commit hook ran. Total eslint: 2, total circular: 0
fix(sidebar): refresh and load channel-created sessions
Clicking a Team Sessions card handed the replay to a Chat Pane session
tab. That tab switch unmounts Work Management (UnifiedChatPanelTabContent
mounts it only while its tab is active), which unmounts TaskKanban, which
runs the cleanup in useCloudSessionActions that aborts the in-flight
import. The click cancelled its own replay: the abort path returns "noop"
without a toast, so the new tab sat on the empty-history placeholder
forever. The sidebar never unmounts itself, which is why the identical
call works there.

Team sessions now replay into the board's own draggable preview window —
the surface the board already uses for local cards — via a new
`openSurface` option on `replaySession`. Callers a tab switch would
unmount pass their own in-place surface; the sidebar and fork-header
callers keep the default tab behavior.

While the import runs, the preview shows the cloud card with the pending
imported session id grafted on; once the local copy lands on the board
its own task takes over (the cloud row is then dropped as a duplicate).

Also: the provider mark now follows the model when a row carries no agent
identity. ORG2-native sessions publish no cliAgentType — the Rust session
categories store NULL — so a shared Claude or GPT session used to render
under the generic ORG2 glyph. Only the icon follows the model; agentLabel
(and with it the Agent filter) keeps naming the runtime. The replay
hydration placeholder now shares the row's own projection, so legacy
`*_cli` wire aliases stop falling through to the Bot mark.

Pre-commit hook ran. Total eslint: 2, total circular: 0
Pre-commit hook ran. Total eslint: 2, total circular: 0
Real-machine testing of the reference chips surfaced three dead-click
modes on the auto-replay consumer:

- An own-session chip only wrote a sidebar reveal request, which
  highlights nothing unless the session is already active. Reveal-local
  now opens the live local transcript via the chat panel tab.
- The reveal-local decision trusts persisted push markers, which can
  outlive the local session until the vanished sweep. Confirm via
  demand-hydration before repointing any tab; a stale marker gets the
  same not-found refusal a missing cloud row would.
- Skip toasts (not-found / not-published) used the 1s default duration,
  which polling misses and users read as a broken chip. They now use the
  same extended duration as admission refusals, for the same reason.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Pre-commit hook ran. Total eslint: 20, total circular: 0
A dragged session lands in the issue comment box and create-issue body
as a bare orgii://cloud/session/ref URL; the chip only appeared after
publishing. A native textarea cannot render inline pills, so a preview
strip under the editor now shows the same chips the rendered body will.

- Extract the candidate scan out of the remark plugin into a shared
  scanCloudSessionReferences, so the preview and the rendered body can
  never disagree; code spans/fences are excluded to match the renderer,
  and a trailing backtick no longer leaks into the session id.
- One walk per whitespace-free run makes the scan linear regardless of
  scheme-hit density (measured: adversarial 100KB pastes now cost the
  same as reference-free text; realistic typing shows zero long tasks).
- Preview chips are passive and identity-stable across keystrokes; the
  chip resolves its local title through sessionByIdAtom instead of
  subscribing to the whole sessions array.
- Point the create-issue flow at the extracted CreateIssueModal and drop
  the inline duplicate in GitHubWorkItemControls; the duplicate was the
  live one, so the reference drop target never worked there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Pre-commit hook ran. Total eslint: 20, total circular: 0
Pre-commit hook ran. Total eslint: 2, total circular: 0
fix(key-vault): prevent CLI discovery from hanging packaged app
Pre-commit hook ran. Total eslint: 2, total circular: 0
Pre-commit hook ran. Total eslint: 51, total circular: 0
The chat pane could only render a session as its GUI transcript; the raw
JSON was reachable only through a modal, and neither host could answer
"where did the time go" or "what did this session touch".

Adds a ghost select (Chat / Timeline / Changes / Raw) to the session
published header, shared by the chat pane and the Workstation
chat-session tab so both hosts render one identical control instead of
duplicating the switcher.

- Timeline: per-turn gantt over the session's own wall-clock span, with
  failed / interrupted turns toned apart.
- Changes: modifiedFiles aggregated across turns, busiest first. A path
  created then modified stays "created"; a delete anywhere wins.
- Raw: the existing transcript view, now embedded instead of modal.

Both derived views read one loadTurnIndex call (metadata only, no event
bodies), so a whole-session view costs a single read regardless of
transcript size.

Virtualization: the GUI subtree is hidden, never unmounted, so switching
views cannot drop the chat list's measurement cache and force a
re-measure of every turn on the way back. The alternate views mount only
while active, and each windows its own rows. The raw transcript's merge
and JSON.stringify are now gated on `enabled` so a session that loaded
Raw once no longer re-serializes on every streaming event.

Pre-commit hook ran. Total eslint: 2, total circular: 0
feat(cloud): session reference chips in issue text
feat(chat): add per-session timeline, changes, and raw views
Pre-commit hook ran. Total eslint: 2, total circular: 0
Pre-commit hook ran. Total eslint: 2, total circular: 0
style(profile): pair builder type avatars
New orgtrack_core::sources::cli_resume maps an imported session (claude_code /
codex_app / cursor_cli) to the invocation its owning CLI accepts:
claude --resume <uuid> (original cwd required — Claude keys session storage
on the project path), codex resume <thread-uuid> (extracted from the rollout
stem), cursor-agent --resume <chat-id>. Cursor IDE composers are deliberately
unsupported: their ids share no space with cursor-agent's chat store.

The desktop app exposes it as external_history_cli_resume_plan, folding in
the host-only freshness checks (workspace dir still exists, source store
still on disk).
Imported Claude Code / Codex / Cursor CLI sessions get a 'Continue in
<CLI>' header action next to the fork button. It reuses the existing
chat-panel TUI launch flow end to end: launch-profile command resolution,
a managed code_sessions row (runner='tui') for ORGII_SESSION_ID lifecycle
attribution and managed-mirror dedup, and the session's recorded workspace
as cwd. Disabled states explain exactly why (CLI not installed, original
folder gone for Claude's cwd-scoped resume, source store deleted).

Sources with no CLI resume path (Cursor IDE composers and the rest) simply
don't render the button — the backend plan is the single source of truth.
orgtrack resume <session-id> scans only the provider that owns the id,
plans the invocation via core's cli_resume, and execs it from the
session's recorded workspace (--print emits the cd-prefixed command line
instead). Verified against real local history: claude/codex/cursor-agent
command lines all resolve, and Claude's cwd requirement is enforced —
resuming from another directory provably fails upstream.
Verified against the installed binaries and their own stores:
- opencode --session <ses_id> — central db, ids match session.id exactly
- mimo --session <ses_id> — OpenCode fork, identical flag surface
- cline --id <id> — global sessions.db, ids match sessions.session_id
- omp --session <transcript-path> — pi-family id lookup is scoped to the
  current project's session dir, so the plan addresses the session file by
  absolute path instead (works from anywhere)

Still out, with reasons in the module doc: cursor_ide/windsurf/warp/trae/
qoder (app-bound, no CLI surface) and zcode/qoder_cli/workbuddy (no binary
present to verify the flag shape).
…oser copy, source-model preselect

Three imported-session UX fixes around continuation:

- Sources no CLI can reopen (Cursor IDE, Windsurf, Trae, Warp, Qoder,
  ZCode, WorkBuddy, Qoder CLI) no longer render the chat composer — they
  are pure read-only replays. The capability lives on the source
  descriptor (cliResume {agentType, displayName}) so the composer gate and
  the header continue-button share one sync source of truth, and the
  header skips the backend plan round-trip for never-resumable sources.

- The composer placeholder now says what typing actually does — fork into
  a new ORGII session — and names the native alternative per source
  ('…or resume in Claude Code from the header'), in all 13 locales.

- The fork setup dialog preselects the imported session's own model: when
  no agent was explicitly chosen (user pick or collab agent hint), the
  source session's model outranks the fallback agent's configured model
  whenever the selected account offers it. Extracted as a pure helper with
  unit tests; collab forks with agent hints keep their existing order.
…ries

Two new imported-history sources, end to end (reader → registry → replay
router → app commands → sidebar descriptors → CLI resume):

- copilot — Copilot CLI 1.x stores one ~/.copilot/session-state/<uuid>/
  per session (events.jsonl stream + workspace.yaml sidecar). The reader
  walks those dirs (strict uuid gate skips optimistic-chat/pending-draft
  junk and 1.0.8-era metadata-only shells), converts user/assistant/tool
  events into chunks pairing results by toolCallId, and enriches
  repository/branch/tokens from session-store.db best-effort — verified
  against the live db: input_tokens is already cache-inclusive and
  output_tokens already includes reasoning (documented in-module).
  Resume: copilot --resume=<id> (attached form; the flag's value is
  optional so a detached id would parse as a prompt).

- kimi — Kimi Code CLI stores $KIMI_CODE_HOME/sessions/<wd_key>/<id>/
  with state.json + agents/main/wire.jsonl, a persisted op journal
  (metadata first line, then {type, ...payload, time}). Transcript from
  context.append_message (origin-filtered, think parts → thinking,
  toolCalls paired with tool-role results), cache-aware usage from
  usage.record, cwd from config.update. Format pinned against upstream
  MoonshotAI/kimi-code (wire protocol 1.5); fixture-tested — no local
  sessions exist on this machine (CLI not logged in). Torn final lines
  and unknown ops are tolerated like the CLI itself does.
  Resume: kimi --session <id> from the recorded workspace (sessions are
  cwd-bucketed).

Both are detected as history-importable in the Data Sources panel, join
the kanban source filters, and the chat pane's Continue-in-CLI button.
Antigravity (agy) was probed too — conversations/ +
conversation_summaries.db exist but are empty and seeding needs an
interactive Google login, so its importer is deferred; findings are in
docs/orgtrack-cli.md.
@Neonforge98
Neonforge98 force-pushed the feat/external-session-cli-resume branch 2 times, most recently from f6b45ab to 9880eb7 Compare July 30, 2026 08:37
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.

7 participants