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 +12 -3
- package/VERSION +1 -1
- package/changelogs/CHANGELOGS_0_26_1.md +21 -0
- package/changelogs/CHANGELOGS_UNRELEASED.md +6 -0
- package/checksums/release-archives.json +11 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/commands/conversation.py +1 -1
- package/src/commands/runs.py +2 -4
- package/src/conversation.py +9 -3
- package/src/session_service.py +5 -16
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<br clear="left"/>
|
|
6
6
|
|
|
7
|
-
[](LICENSE) ](LICENSE)  
|
|
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
|
|
248
|
-
|
|
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.
|
|
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
package/pyproject.toml
CHANGED
|
@@ -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
|
package/src/commands/runs.py
CHANGED
|
@@ -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
|
-
#
|
|
767
|
-
#
|
|
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 = {
|
package/src/conversation.py
CHANGED
|
@@ -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,
|
|
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:
|
package/src/session_service.py
CHANGED
|
@@ -689,12 +689,13 @@ def record_launch_history(store, name, payload):
|
|
|
689
689
|
**payload,
|
|
690
690
|
}
|
|
691
691
|
store["append_launch_history"](entry)
|
|
692
|
-
|
|
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
|
|
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
|
-
"""
|
|
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
|
|
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):
|