cdx-manager 0.26.0 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <br clear="left"/>
6
6
 
7
- [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.26.0-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
7
+ [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.26.1-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
8
8
 
9
9
  **The right AI coding account, when you need it.** CDX Manager keeps your Codex and Claude accounts in separate profiles, shows which ones have quota, and helps you pick the next usable session from your terminal.
10
10
 
@@ -78,6 +78,10 @@ cdx run --continue-run RUN_ID --cwd /path/to/repo --prompt "Fix the remaining te
78
78
 
79
79
  `--continue-run` resumes the exact Claude session or Codex thread and pins the previous run's recorded model and effort, even if the profile settings changed. The profile is inferred from the parent; an explicit session or provider must match. An explicit model or effort must also match. Runs and history record `parent_run_id` and `conversation_id`. Detached continuations use the cdx child supervisor with the same pins. Codex continuation usage is measured between exact transcript snapshots before and after the run, since exec resume can report conversation totals. If either boundary is unavailable, per-run usage stays unknown with `usage_attribution: "continuation_usage_unavailable"` in history instead of counting earlier turns again.
80
80
 
81
+ Headless runs, including detached, remote and continued runs, keep their
82
+ conversation identity in run records and history without replacing the profile's
83
+ interactive conversation.
84
+
81
85
  Conservative assumption: a continuation requires a finished Claude or Codex run, a locally available provider transcript, and recorded model and effort settings. Missing runs, profiles, expired sessions, and unavailable settings produce a clear error; the caller can retry without `--continue-run` for a cold run. `--failover` and `--isolate-workspace` cannot be combined with continuation. Capacity recovery and provider model fallback are disabled for continuations so they cannot silently switch model or account. Provider cache retention remains provider-controlled, so resuming does not guarantee a cache hit.
82
86
 
83
87
  ## Run costs and budgets
@@ -244,8 +248,13 @@ for a fresh credential, and that cleanup works after disconnect and SIGTERM.
244
248
 
245
249
  `cdx conversation <name> [--conversation ID] [--cursor CURSOR] [--limit N] --json`
246
250
  returns a read-only JSON page for the profile's current conversation. An explicit
247
- conversation ID uses an exact transcript lookup; a profile without a recorded ID
248
- uses the existing latest-transcript resolver. This fallback is best effort.
251
+ conversation ID uses an exact transcript lookup and never falls back. When the
252
+ profile's recorded ID has no transcript on this host (for example after a remote
253
+ run on another host), or there is no recorded ID, the command uses the existing
254
+ latest local transcript resolver under the profile's auth home. This fallback is
255
+ best effort and does not rewrite the recorded ID. JSON `resolution_method` is
256
+ `conversation_id` for an exact match, `recency` for a local fallback, or `null`
257
+ when no transcript is resolved.
249
258
 
250
259
  The normal cdx JSON envelope includes `status`, `reason`, `provider`, `session`,
251
260
  `entries`, `next_cursor`, and `has_more`. Each entry has exactly `role`
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.26.0
1
+ 0.26.1
@@ -0,0 +1,21 @@
1
+ # CDX Manager 0.26.1
2
+
3
+ Release date: 2026-11-03
4
+
5
+ ## Fixes
6
+
7
+ - Preserve the profile's interactive conversation across local, detached, remote,
8
+ and continued headless runs. Keep headless Claude identities in run records
9
+ and history, and skip interactive Codex identity observation for run history.
10
+ (#103)
11
+ - Let `cdx conversation` use the latest local transcript when the profile's
12
+ recorded conversation ID has no transcript on this host. Explicit
13
+ `--conversation` lookups remain exact. Expose `resolution_method` as
14
+ `conversation_id`, `recency`, or `null`; recency remains best effort. (#103)
15
+
16
+ ## Maintenance
17
+
18
+ - Record tray companion checksums for v0.26.0.
19
+ (`c942c9f6fdcfabd1ad0442e46f978da3b548971f`)
20
+ - Record release archive checksums for v0.26.0.
21
+ (`9a43f53d7294ecb254bd65aea214831bcd1486fb`)
@@ -1,5 +1,11 @@
1
1
  # Unreleased
2
2
 
3
+ ## Preserve interactive conversations across headless runs
4
+
5
+ - Keep fresh Claude run identities in run records and history without changing the profile's interactive conversation. Skip interactive Codex identity observation when recording headless run history, including detached, remote and continued runs.
6
+ - Let `cdx conversation` fall back to the latest local transcript when a profile's recorded ID has no transcript on this host. Explicit `--conversation` lookups remain exact and never fall back. Report `resolution_method` as `conversation_id`, `recency`, or `null`.
7
+ - Cover a remote Claude run followed by local interactive conversation reading, local/continued run identity preservation, missing recorded IDs and missing explicit IDs.
8
+
3
9
  ## Antigravity quota and run usage
4
10
 
5
11
  - Install a local Antigravity statusLine command in new profiles and on interactive launch, merging settings and relaying the original stdin to any existing command so its output is preserved. Store only the reading time, plan tier and allowlisted quota fields in an atomic mode-600 profile file; never access Google internal endpoints or antigravity-oauth-token.
@@ -793,6 +793,17 @@
793
793
  "x86_64-pc-windows-msvc": "32889713859cf23d4d82647638d909d62075b92d8e6571e32118ebfa208d81b2",
794
794
  "x86_64-unknown-linux-musl": "9e94b799fe4b01d95aeb172c1eba28dc011be69cc2b39b5743f0d81986eaa80f"
795
795
  }
796
+ },
797
+ "v0.26.0": {
798
+ "github_tarball_sha256": "09903dc1c2bcf3c35ad0d52d1f5af2aa9f65584c07f508986dbf6829737d5eb2",
799
+ "github_zip_sha256": "e1aa5af189e9591f49aa1873c90cfb3365d3b3e86b4993866599498c21a0f979",
800
+ "tray_assets": {
801
+ "aarch64-apple-darwin": "1ee383b3ba75b9a19fee981d39ae8d26228e855933392ceb3d8fa55498da238b",
802
+ "aarch64-unknown-linux-musl": "832aa41132f900e446adaa8e23072094b6ba42d82315dac9ccd5f75ae623f172",
803
+ "x86_64-apple-darwin": "791898c594fcb2a0009653b9b50732698c0c451169cf1847a613a8d3e3ab3300",
804
+ "x86_64-pc-windows-msvc": "24ba2a8ed13de1bdc0401661a51c105c2c7ed186f3b17a871cd721ac8adf4916",
805
+ "x86_64-unknown-linux-musl": "200c8ab9465625784a32ae326d923bca97b5f9616809c43cb5dc73e0e5d9ca17"
806
+ }
796
807
  }
797
808
  }
798
809
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdx-manager",
3
- "version": "0.26.0",
3
+ "version": "0.26.1",
4
4
  "description": "Terminal session manager for Codex and Claude accounts.",
5
5
  "license": "MIT",
6
6
  "author": "Alexandre Agostini",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdx-manager"
7
- version = "0.26.0"
7
+ version = "0.26.1"
8
8
  description = "Terminal session manager for Codex and Claude accounts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9.2"
@@ -28,7 +28,7 @@ def handle_conversation(rest, ctx):
28
28
  page = read_conversation(
29
29
  session["provider"], _get_auth_home(session),
30
30
  conversation_id=parsed["conversation"] or (session.get("conversation") or {}).get("id"),
31
- cursor=parsed["cursor"], limit=limit,
31
+ cursor=parsed["cursor"], limit=limit, fallback_to_recency=not parsed["conversation"],
32
32
  )
33
33
  _write_json(ctx, _json_success("conversation", "Conversation page loaded.", session=session["name"], **page))
34
34
  return 0
@@ -763,10 +763,8 @@ def handle_run(rest, ctx):
763
763
  # since nothing else frees their slot.
764
764
  _reconcile_background_runs(ctx)
765
765
  _run_registry(ctx).check_capacity(max_parallel)
766
- # Claude needs its conversation id before the spec is built; Codex
767
- # records its own afterwards, from the rollout. Without this a headless
768
- # Claude run left the session with no identity and `cdx resume` fell
769
- # back to recency for Claude while working by id for Codex.
766
+ # Fresh runs carry their own identity; only interactive launches update
767
+ # the profile's current conversation. Continuations use the parent run.
770
768
  if not parsed.get("continue_run"):
771
769
  session = ctx["service"]["begin_conversation"](session["name"])
772
770
  run_session = {
@@ -101,25 +101,31 @@ def _anchor(handle, offset):
101
101
 
102
102
 
103
103
  def read_conversation(provider, auth_home=None, *, conversation_id=None, started_at=None,
104
- terminal_transcript=None, cursor=None, limit=100):
104
+ terminal_transcript=None, cursor=None, limit=100, fallback_to_recency=False):
105
105
  """Return a JSON-serializable page; pass next_cursor back unchanged to poll.
106
106
 
107
107
  Entries follow file/block order. Cursors bind a byte offset and block index
108
108
  to the resolved file; reset_required means discard the old cursor and page
109
109
  again from the beginning. Incomplete trailing lines are retried next time.
110
+ Only implicit session identities may opt into fallback_to_recency.
110
111
  Tool bodies and reasoning blocks are never returned. Ollama PTY captures
111
112
  cannot reliably distinguish roles and are explicitly unsupported.
112
113
  """
113
114
  if not isinstance(limit, int) or isinstance(limit, bool) or not 1 <= limit <= 1000:
114
115
  raise CdxError("Conversation limit must be an integer between 1 and 1000.")
115
- page = {"status": "ok", "reason": None, "provider": provider, "entries": [], "next_cursor": None, "has_more": False}
116
+ page = {"status": "ok", "reason": None, "provider": provider, "entries": [], "next_cursor": None, "has_more": False,
117
+ "resolution_method": None}
116
118
  if provider not in PROVIDERS or provider == "ollama":
117
119
  return {**page, "status": "unsupported", "reason": "no_structured_transcript_reader"}
118
120
  # An explicit conversation must never fall back to an unrelated recent file.
119
121
  if conversation_id:
120
122
  path = conversation_transcript(provider, auth_home, conversation_id)
123
+ if path:
124
+ page["resolution_method"] = "conversation_id"
125
+ elif fallback_to_recency:
126
+ path, page["resolution_method"] = resolve_transcript(provider, auth_home)
121
127
  else:
122
- path, _match = resolve_transcript(provider, auth_home, started_at, terminal_transcript=terminal_transcript)
128
+ path, page["resolution_method"] = resolve_transcript(provider, auth_home, started_at, terminal_transcript=terminal_transcript)
123
129
  if not path:
124
130
  return {**page, "status": "unavailable", "reason": "transcript_not_found"}
125
131
  try:
@@ -689,12 +689,13 @@ def record_launch_history(store, name, payload):
689
689
  **payload,
690
690
  }
691
691
  store["append_launch_history"](entry)
692
- _observe_codex_conversation(store, session)
692
+ if entry.get("action") != "run":
693
+ _observe_codex_conversation(store, session)
693
694
  return entry
694
695
 
695
696
 
696
697
  def _observe_codex_conversation(store, session):
697
- """Read Codex's conversation id back after a run, if it wrote one.
698
+ """Read Codex's conversation id back after an interactive launch.
698
699
 
699
700
  Codex mints the id itself, so unlike Claude this can only be observed once
700
701
  the rollout exists. Failing to find one is normal and leaves the session on
@@ -938,26 +939,14 @@ def _conversation_identity_for_launch(session):
938
939
 
939
940
 
940
941
  def begin_conversation(store, name):
941
- """Mint the conversation id a Claude run is about to carry, and store it.
942
-
943
- `launch_session` does this for interactive launches, but a headless run
944
- never goes through it, so `cdx run` used to leave Claude sessions with no
945
- identity at all - `cdx resume` then fell back to recency for Claude while
946
- working by id for Codex, which records its own after the fact. Same shape
947
- for both providers now: whatever ran last is what resume names.
948
-
949
- Returns the updated session, or the unchanged one for providers that mint
950
- nothing here.
951
- """
942
+ """Return a run-local identity without changing the interactive session."""
952
943
  session = store["get_session"](name)
953
944
  if not session:
954
945
  raise CdxError(f"Unknown session: {name}")
955
946
  conversation = _conversation_identity_for_launch(session)
956
947
  if not conversation:
957
948
  return session
958
- return store["update_session"](name, lambda s: {
959
- **s, "conversation": conversation, "updatedAt": _local_now_iso(),
960
- })
949
+ return {**session, "conversation": conversation}
961
950
 
962
951
 
963
952
  def mark_conversation_rescued(store, name, conversation_id):