Skip to content

feat(hub,web): cursor flavor for multi-agent session import (#732)#945

Draft
heavygee wants to merge 9 commits into
tiann:mainfrom
heavygee:feat/agent-session-import-picker
Draft

feat(hub,web): cursor flavor for multi-agent session import (#732)#945
heavygee wants to merge 9 commits into
tiann:mainfrom
heavygee:feat/agent-session-import-picker

Conversation

@heavygee

Copy link
Copy Markdown
Collaborator

Summary

Adds the Cursor flavor to the multi-agent session import surface (Fixes #732).

Blocked on #942 (feat/claude-session-import). Do not merge until #942 lands on main; then rebase this branch onto main (or retarget the PR) before merge.

Review the cursor-only delta now:

heavygee/hapi@dzshzx:feat/claude-session-import...heavygee:feat/agent-session-import-picker

Cursor delta (3 commits on top of #942)

  • chore(scripts): cursor-acp verify-probe audit harness
  • feat(hub): /api/cursor/* import routes + strict ACP verify-probe
  • feat(web): AgentSessionImportDialog codex|cursor tabs on shared SessionImportPicker

Claude import keeps its separate toolbar icon from #942.

Not in this PR: MCP stateful transport — #944 (land separately; more urgent for live display_image).

Test plan

Issues

Fixes #732

Made with Cursor

dzshzx and others added 7 commits June 17, 2026 14:46
Mirror the Codex session importer for Claude Code: scan ~/.claude/projects
transcripts, map user/assistant(text/thinking/tool_use)/tool_result records
to Hapi imported messages, persist via the shared store/sync engine, and
expose /api/claude/status|sessions|sync-session plus a web import dialog.

Extract the flavor-agnostic import engine into transcriptImport.ts so Codex
and Claude share session-target selection, dedupe, persistence and event
emission (single source of truth); each flavor keeps only its scanner,
parser and adapter. Codex behavior and routes are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When resuming an imported Claude session still held open by a running background/interactive agent, 'claude --resume' is rejected (currently running as a background agent), the error was swallowed to {}, and the launcher retried forever. Now: detect liveness via 'claude agents --json' and branch a copy with --fork-session (recording forkedFrom) instead of taking over; logger serializes Error to {name,message,stack}; launcher classifies unrecoverable resume errors and stops retry (MAX_LAUNCH_RETRIES=3), surfacing the real reason. Dead sessions fall through to plain --resume (unchanged); codex and other flavors untouched.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The direct-import path (importSingleSession) wrote rows via addMessage, stamping created_at/invoked_at with import time, so imported Claude/Codex sessions sorted as "active today" and lost their original message timeline (review finding on tiann#942).

- Carry each JSONL record's top-level timestamp through import data (new ImportedMessage wrapper + shared parseImportedTimestamp) for both Claude and Codex parsers.
- Insert via copyMessageToSession with createdAt/invokedAt = original timestamp (fallback to file mtime).
- Back-fill the newly-created imported session's updatedAt to real last activity (setImportedSessionActivity, new-session path only); existing/online sessions keep forward-only activity semantics untouched.
- New unit tests (Claude + Codex): timestamp preservation + mtime fallback.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getLiveAgentKind() runs 'claude agents --json', which honors CLAUDE_CONFIG_DIR / HAPI_CLAUDE_PATH. It ran before opts.claudeEnvVars were applied to process.env, so when a session supplies those overrides the liveness probe queried the default Claude config, missed the live agent, and the launch then resumed against the overridden config and hit the occupied-session path instead of forking (review finding on tiann#942).

Move the env application above the fork decision so probe and launch share the same Claude env. Adds a regression test asserting the override is visible to getLiveAgentKind.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Standalone bun script that walks every legacy chat at
~/.cursor/chats/<wsh>/<uuid>/store.db, stages an isolated $HOME/$HAPI_HOME,
copies the store + synthesizes meta.json, and drives agent acp
initialize + session/load. Surfaces a per-chat outcome class matching the
upstream Cursor import refusal contract (verify_load_failed, verify_timeout,
corrupted_store, ...).

Doubles as a regression harness operators can re-run when cursor-agent
updates its on-disk schema. Produces CSV at the configured path.

Probe shape mirrors hub/src/cursor/acpVerifyProbe.ts (introduced by tiann#844).
Script is intentionally self-contained so it remains runnable even before
the import endpoints land.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds /api/cursor/importable-sessions + /api/cursor/import endpoints
mirroring the codex import shape from tiann#796, with strict ACP-only
refusal contract enforced before any HAPI row is created. A
maintainer-side audit ran 391/391 = 100% pass on a real chat library
before this code shipped; the strictness is theoretical-cost-only in
practice.

Architecture: parallel /cursor/* endpoints alongside /codex/*,
shared types under hub/src/web/routes/_agentImport/types.ts. The
cursorImporter wraps the existing cursorLegacyMigrator verify-probe
pattern. Legacy stream-json stores are transplanted to the ACP
location only after agent acp initialize + session/load both
succeed. ACP-format stores are imported as-is. Refusal cases
(verify_load_failed, missing_on_disk_store, target_already_exists,
already_imported, agent_binary_not_found, verify_timeout,
corrupted_store, ambiguous_legacy_store, internal_error) leave disk
and DB state untouched.

20 unit tests cover discovery, every refusal branch, both happy
paths, and route shape. Real agent acp integration coverage lives
in the existing cursorLegacyMigratorIntegration harness.

Refs tiann#732

Co-authored-by: Cursor <cursoragent@cursor.com>
Stacks on tiann#942 SessionImportPicker: AgentSessionImportDialog with codex|cursor
tabs, cursor row metadata + refusal UX, unified toolbar import affordance.
Claude import keeps its separate icon from tiann#942.

Co-authored-by: Cursor <cursoragent@cursor.com>
Single toolbar import affordance with Codex | Cursor | Claude tabs;
remove separate Claude toolbar button and router wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>
heavygee added a commit to heavygee/hapi that referenced this pull request Jun 25, 2026
Handle PR tiann#944/tiann#945 slash and space-separated formats, malformed PR: N
titles, and strip leading emoji before rebuilding session names so sweeps
do not stack prefixes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Reproducible PNG for operator demo (tiann#732); MCP/display_image stash
content was already shipped via tiann#944 upstream.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

RFC: Import external agent sessions + hub-scoped operator context

1 participant