agent-session-bridge 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- {agent_session_bridge-0.2.0/src/agent_session_bridge.egg-info → agent_session_bridge-0.2.2}/PKG-INFO +10 -9
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/README.md +6 -5
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/pyproject.toml +4 -4
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2/src/agent_session_bridge.egg-info}/PKG-INFO +10 -9
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/screens.py +21 -2
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/codex.py +23 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/codex_db.py +51 -2
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_codex_db.py +67 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_tui_pilot.py +4 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/LICENSE +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/setup.cfg +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/agent_session_bridge.egg-info/SOURCES.txt +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/agent_session_bridge.egg-info/dependency_links.txt +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/agent_session_bridge.egg-info/entry_points.txt +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/agent_session_bridge.egg-info/requires.txt +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/agent_session_bridge.egg-info/top_level.txt +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/__init__.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/_ids.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/cli.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/convert.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/handshake.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/ir.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/place.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/__init__.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/_content.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/_jsonl.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/_pending.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/claude_code.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/codex.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/hermes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/skill_install.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/skills/session-handoff/SKILL.md +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/__init__.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/actions.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/app.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/discovery.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/options.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/register.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/summary.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/__init__.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/_common.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/claude_code.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/hermes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/hermes_db.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_claude_reader.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_cli_tui.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_codex_reader.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_handshake_convert_cli.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_hermes_db.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_hermes_reader.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_ir.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_place.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round11_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round12_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round13_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round14_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round16_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round17_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round18_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round18b_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round1_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round21_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round22_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round25_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round27_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round28_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round2_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round3_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round4_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round5_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round6_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round7_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round8_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round9_fixes.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_skill_install.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_stub_open_calls.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_tui_actions.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_tui_discovery.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_tui_options.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_tui_register.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_tui_summary.py +0 -0
- {agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_writers.py +0 -0
{agent_session_bridge-0.2.0/src/agent_session_bridge.egg-info → agent_session_bridge-0.2.2}/PKG-INFO
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-session-bridge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Local-first cross-harness agent-session portability: export a live coding-agent session from one harness and resume it in another.
|
|
5
5
|
Author: Prakash Chaudhary
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/connectwithprakash/session-bridge
|
|
8
|
-
Project-URL: Repository, https://github.com/connectwithprakash/session-bridge
|
|
9
|
-
Project-URL: Changelog, https://github.com/connectwithprakash/session-bridge/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: Homepage, https://github.com/connectwithprakash/agent-session-bridge
|
|
8
|
+
Project-URL: Repository, https://github.com/connectwithprakash/agent-session-bridge
|
|
9
|
+
Project-URL: Changelog, https://github.com/connectwithprakash/agent-session-bridge/blob/main/CHANGELOG.md
|
|
10
10
|
Project-URL: Project page, https://www.connectwithprakash.com/project/session-bridge
|
|
11
11
|
Keywords: ai-agents,claude-code,codex,session,cli,tui
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -31,7 +31,7 @@ Dynamic: license-file
|
|
|
31
31
|
|
|
32
32
|
# session-bridge
|
|
33
33
|
|
|
34
|
-
[](https://github.com/connectwithprakash/session-bridge/actions/workflows/test.yml)
|
|
34
|
+
[](https://github.com/connectwithprakash/agent-session-bridge/actions/workflows/test.yml)
|
|
35
35
|
[](https://pypi.org/project/agent-session-bridge/)
|
|
36
36
|
[](https://pypi.org/project/agent-session-bridge/)
|
|
37
37
|
[](LICENSE)
|
|
@@ -60,7 +60,7 @@ and fail closed (no write) when it doesn't match.
|
|
|
60
60
|
| Harness | Last verified against | Verification |
|
|
61
61
|
|---|---|---|
|
|
62
62
|
| Claude Code | 2.1.x transcripts | Round-trip + live `claude --resume` recall of a converted-only fact |
|
|
63
|
-
| Codex | Codex CLI 0.145.0 (`state_5.sqlite`) | Live `codex resume` recall of a converted-only sentinel |
|
|
63
|
+
| Codex | Codex CLI 0.145.0 (`state_5.sqlite`) | Live `codex resume` recall of a converted-only sentinel, and the registered session listed in the resume picker |
|
|
64
64
|
| Hermes | `state.db` schema as of 2026-07 | Live `hermes --resume` replay of a registered session |
|
|
65
65
|
| Python | 3.11 – 3.13 | CI test matrix |
|
|
66
66
|
|
|
@@ -98,9 +98,10 @@ source.jsonl ─▶ reader ─▶ IR (Session: messages, tools, pending) ─▶
|
|
|
98
98
|
## Install
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
-
uv tool install 'agent-session-bridge[tui]'
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
uv tool install 'agent-session-bridge[tui]' # recommended: CLI + TUI, isolated env
|
|
102
|
+
brew install connectwithprakash/tap/session-bridge # or: Homebrew (TUI included)
|
|
103
|
+
uvx agent-session-bridge --help # or: no-install run
|
|
104
|
+
pip install 'agent-session-bridge[tui]' # or: plain pip
|
|
104
105
|
```
|
|
105
106
|
|
|
106
107
|
Installs the `session-bridge` command (docs use that name); an
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# session-bridge
|
|
2
2
|
|
|
3
|
-
[](https://github.com/connectwithprakash/session-bridge/actions/workflows/test.yml)
|
|
3
|
+
[](https://github.com/connectwithprakash/agent-session-bridge/actions/workflows/test.yml)
|
|
4
4
|
[](https://pypi.org/project/agent-session-bridge/)
|
|
5
5
|
[](https://pypi.org/project/agent-session-bridge/)
|
|
6
6
|
[](LICENSE)
|
|
@@ -29,7 +29,7 @@ and fail closed (no write) when it doesn't match.
|
|
|
29
29
|
| Harness | Last verified against | Verification |
|
|
30
30
|
|---|---|---|
|
|
31
31
|
| Claude Code | 2.1.x transcripts | Round-trip + live `claude --resume` recall of a converted-only fact |
|
|
32
|
-
| Codex | Codex CLI 0.145.0 (`state_5.sqlite`) | Live `codex resume` recall of a converted-only sentinel |
|
|
32
|
+
| Codex | Codex CLI 0.145.0 (`state_5.sqlite`) | Live `codex resume` recall of a converted-only sentinel, and the registered session listed in the resume picker |
|
|
33
33
|
| Hermes | `state.db` schema as of 2026-07 | Live `hermes --resume` replay of a registered session |
|
|
34
34
|
| Python | 3.11 – 3.13 | CI test matrix |
|
|
35
35
|
|
|
@@ -67,9 +67,10 @@ source.jsonl ─▶ reader ─▶ IR (Session: messages, tools, pending) ─▶
|
|
|
67
67
|
## Install
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
uv tool install 'agent-session-bridge[tui]'
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
uv tool install 'agent-session-bridge[tui]' # recommended: CLI + TUI, isolated env
|
|
71
|
+
brew install connectwithprakash/tap/session-bridge # or: Homebrew (TUI included)
|
|
72
|
+
uvx agent-session-bridge --help # or: no-install run
|
|
73
|
+
pip install 'agent-session-bridge[tui]' # or: plain pip
|
|
73
74
|
```
|
|
74
75
|
|
|
75
76
|
Installs the `session-bridge` command (docs use that name); an
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agent-session-bridge"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Local-first cross-harness agent-session portability: export a live coding-agent session from one harness and resume it in another."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -27,9 +27,9 @@ classifiers = [
|
|
|
27
27
|
dependencies = []
|
|
28
28
|
|
|
29
29
|
[project.urls]
|
|
30
|
-
Homepage = "https://github.com/connectwithprakash/session-bridge"
|
|
31
|
-
Repository = "https://github.com/connectwithprakash/session-bridge"
|
|
32
|
-
Changelog = "https://github.com/connectwithprakash/session-bridge/blob/main/CHANGELOG.md"
|
|
30
|
+
Homepage = "https://github.com/connectwithprakash/agent-session-bridge"
|
|
31
|
+
Repository = "https://github.com/connectwithprakash/agent-session-bridge"
|
|
32
|
+
Changelog = "https://github.com/connectwithprakash/agent-session-bridge/blob/main/CHANGELOG.md"
|
|
33
33
|
"Project page" = "https://www.connectwithprakash.com/project/session-bridge"
|
|
34
34
|
|
|
35
35
|
[project.optional-dependencies]
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2/src/agent_session_bridge.egg-info}/PKG-INFO
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-session-bridge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Local-first cross-harness agent-session portability: export a live coding-agent session from one harness and resume it in another.
|
|
5
5
|
Author: Prakash Chaudhary
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/connectwithprakash/session-bridge
|
|
8
|
-
Project-URL: Repository, https://github.com/connectwithprakash/session-bridge
|
|
9
|
-
Project-URL: Changelog, https://github.com/connectwithprakash/session-bridge/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: Homepage, https://github.com/connectwithprakash/agent-session-bridge
|
|
8
|
+
Project-URL: Repository, https://github.com/connectwithprakash/agent-session-bridge
|
|
9
|
+
Project-URL: Changelog, https://github.com/connectwithprakash/agent-session-bridge/blob/main/CHANGELOG.md
|
|
10
10
|
Project-URL: Project page, https://www.connectwithprakash.com/project/session-bridge
|
|
11
11
|
Keywords: ai-agents,claude-code,codex,session,cli,tui
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -31,7 +31,7 @@ Dynamic: license-file
|
|
|
31
31
|
|
|
32
32
|
# session-bridge
|
|
33
33
|
|
|
34
|
-
[](https://github.com/connectwithprakash/session-bridge/actions/workflows/test.yml)
|
|
34
|
+
[](https://github.com/connectwithprakash/agent-session-bridge/actions/workflows/test.yml)
|
|
35
35
|
[](https://pypi.org/project/agent-session-bridge/)
|
|
36
36
|
[](https://pypi.org/project/agent-session-bridge/)
|
|
37
37
|
[](LICENSE)
|
|
@@ -60,7 +60,7 @@ and fail closed (no write) when it doesn't match.
|
|
|
60
60
|
| Harness | Last verified against | Verification |
|
|
61
61
|
|---|---|---|
|
|
62
62
|
| Claude Code | 2.1.x transcripts | Round-trip + live `claude --resume` recall of a converted-only fact |
|
|
63
|
-
| Codex | Codex CLI 0.145.0 (`state_5.sqlite`) | Live `codex resume` recall of a converted-only sentinel |
|
|
63
|
+
| Codex | Codex CLI 0.145.0 (`state_5.sqlite`) | Live `codex resume` recall of a converted-only sentinel, and the registered session listed in the resume picker |
|
|
64
64
|
| Hermes | `state.db` schema as of 2026-07 | Live `hermes --resume` replay of a registered session |
|
|
65
65
|
| Python | 3.11 – 3.13 | CI test matrix |
|
|
66
66
|
|
|
@@ -98,9 +98,10 @@ source.jsonl ─▶ reader ─▶ IR (Session: messages, tools, pending) ─▶
|
|
|
98
98
|
## Install
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
-
uv tool install 'agent-session-bridge[tui]'
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
uv tool install 'agent-session-bridge[tui]' # recommended: CLI + TUI, isolated env
|
|
102
|
+
brew install connectwithprakash/tap/session-bridge # or: Homebrew (TUI included)
|
|
103
|
+
uvx agent-session-bridge --help # or: no-install run
|
|
104
|
+
pip install 'agent-session-bridge[tui]' # or: plain pip
|
|
104
105
|
```
|
|
105
106
|
|
|
106
107
|
Installs the `session-bridge` command (docs use that name); an
|
|
@@ -399,7 +399,9 @@ class DryRunScreen(Screen):
|
|
|
399
399
|
self.opts,
|
|
400
400
|
claude_home=getattr(self.app, "claude_home", None),
|
|
401
401
|
)
|
|
402
|
-
self.app.call_from_thread(
|
|
402
|
+
self.app.call_from_thread(
|
|
403
|
+
self.app.push_screen, ResultScreen(outcome, target=self.opts.target)
|
|
404
|
+
)
|
|
403
405
|
|
|
404
406
|
|
|
405
407
|
class ResultScreen(Screen):
|
|
@@ -410,9 +412,10 @@ class ResultScreen(Screen):
|
|
|
410
412
|
("q", "quit", "Quit"),
|
|
411
413
|
]
|
|
412
414
|
|
|
413
|
-
def __init__(self, outcome) -> None:
|
|
415
|
+
def __init__(self, outcome, target: str | None = None) -> None:
|
|
414
416
|
super().__init__()
|
|
415
417
|
self.outcome = outcome
|
|
418
|
+
self.target = target
|
|
416
419
|
|
|
417
420
|
def compose(self) -> ComposeResult:
|
|
418
421
|
yield Header()
|
|
@@ -435,6 +438,22 @@ class ResultScreen(Screen):
|
|
|
435
438
|
lines.append(f"\n[b]resume with:[/] {escape(o.resume_hint)}")
|
|
436
439
|
if o.error:
|
|
437
440
|
lines.append(f"\n[b red]error:[/] {escape(o.error)}")
|
|
441
|
+
# A converted file alone is not resumable in every harness; say so
|
|
442
|
+
# here, where the user is about to go looking for their session.
|
|
443
|
+
if not o.error and not o.placed_path:
|
|
444
|
+
if self.target in ("codex", "hermes"):
|
|
445
|
+
lines.append(
|
|
446
|
+
f"\n[yellow]heads up:[/] {self.target} lists sessions from its own "
|
|
447
|
+
f"store, so this file will NOT appear in its resume picker. "
|
|
448
|
+
f"To make it resumable, use Register ([b]g[/] on the summary "
|
|
449
|
+
f"screen) instead."
|
|
450
|
+
)
|
|
451
|
+
elif self.target == "claude-code":
|
|
452
|
+
lines.append(
|
|
453
|
+
"\n[yellow]heads up:[/] `claude --resume` only finds sessions "
|
|
454
|
+
"under ~/.claude/projects. Enable placement on the options "
|
|
455
|
+
"screen to make this resumable."
|
|
456
|
+
)
|
|
438
457
|
lines.append("\n[dim]n for another conversion, q to quit[/]")
|
|
439
458
|
return "\n".join(lines)
|
|
440
459
|
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/codex.py
RENAMED
|
@@ -150,5 +150,28 @@ def write_codex(
|
|
|
150
150
|
# Preserve an otherwise-empty message so message count survives the round trip.
|
|
151
151
|
if not emitted and msg.role in (Role.USER, Role.ASSISTANT, Role.SYSTEM):
|
|
152
152
|
add(_msg_payload(role, ""), ts)
|
|
153
|
+
# Codex writes an event_msg twin for every user turn, and its resume
|
|
154
|
+
# picker only lists sessions whose rollout contains at least one
|
|
155
|
+
# user_message event — without this the imported session resumes by
|
|
156
|
+
# id but never appears in the picker. The reader ignores event_msg
|
|
157
|
+
# as a response_item duplicate, so round-trips are unaffected.
|
|
158
|
+
if msg.role is Role.USER:
|
|
159
|
+
user_text = msg.text()
|
|
160
|
+
if user_text.strip():
|
|
161
|
+
records.append(
|
|
162
|
+
{
|
|
163
|
+
"timestamp": ts,
|
|
164
|
+
"type": "event_msg",
|
|
165
|
+
"payload": {
|
|
166
|
+
"type": "user_message",
|
|
167
|
+
"message": user_text,
|
|
168
|
+
"images": [],
|
|
169
|
+
"local_images": [],
|
|
170
|
+
"audio": [],
|
|
171
|
+
"local_audio": [],
|
|
172
|
+
"text_elements": [],
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
)
|
|
153
176
|
|
|
154
177
|
return records, report
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/codex_db.py
RENAMED
|
@@ -16,7 +16,9 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import os
|
|
19
|
+
import shutil
|
|
19
20
|
import sqlite3
|
|
21
|
+
import subprocess
|
|
20
22
|
import tempfile
|
|
21
23
|
import time
|
|
22
24
|
import uuid
|
|
@@ -81,6 +83,43 @@ def validate_codex_store(codex_home: str | Path) -> Path:
|
|
|
81
83
|
return db_path
|
|
82
84
|
|
|
83
85
|
|
|
86
|
+
def infer_codex_cli_version(codex_home: str | Path) -> str:
|
|
87
|
+
"""The installed Codex CLI's version, for stamping imported sessions.
|
|
88
|
+
|
|
89
|
+
Asks the binary first (`codex --version` -> "codex-cli 0.145.0"): the
|
|
90
|
+
store's rows are not a trustworthy source because previously imported
|
|
91
|
+
sessions land there too, so inferring from rows can echo an earlier
|
|
92
|
+
import's value back. The newest store row is only the fallback when no
|
|
93
|
+
codex binary is on PATH; empty string for a fresh store, which Codex
|
|
94
|
+
tolerates (the column defaults to '' for pre-column rows).
|
|
95
|
+
"""
|
|
96
|
+
exe = shutil.which("codex")
|
|
97
|
+
if exe:
|
|
98
|
+
try:
|
|
99
|
+
out = subprocess.run(
|
|
100
|
+
[exe, "--version"], capture_output=True, text=True, timeout=10
|
|
101
|
+
).stdout.strip()
|
|
102
|
+
if out:
|
|
103
|
+
return out.split()[-1]
|
|
104
|
+
except (OSError, subprocess.SubprocessError):
|
|
105
|
+
pass
|
|
106
|
+
db_path = validate_codex_store(codex_home)
|
|
107
|
+
conn = sqlite3.connect(db_path.resolve().as_uri() + "?mode=ro", uri=True)
|
|
108
|
+
try:
|
|
109
|
+
row = conn.execute(
|
|
110
|
+
"""
|
|
111
|
+
SELECT cli_version
|
|
112
|
+
FROM threads
|
|
113
|
+
WHERE cli_version IS NOT NULL AND cli_version != ''
|
|
114
|
+
ORDER BY recency_at_ms DESC, updated_at_ms DESC, updated_at DESC
|
|
115
|
+
LIMIT 1
|
|
116
|
+
"""
|
|
117
|
+
).fetchone()
|
|
118
|
+
finally:
|
|
119
|
+
conn.close()
|
|
120
|
+
return row[0] if row else ""
|
|
121
|
+
|
|
122
|
+
|
|
84
123
|
def infer_codex_model(codex_home: str | Path, model_provider: str = "openai") -> str | None:
|
|
85
124
|
"""Return the most recently used model for a configured Codex provider."""
|
|
86
125
|
db_path = validate_codex_store(codex_home)
|
|
@@ -161,6 +200,8 @@ def _thread_values(
|
|
|
161
200
|
"tokens_used": 0,
|
|
162
201
|
"has_user_event": int(bool(first_user_message)),
|
|
163
202
|
"archived": 0,
|
|
203
|
+
# meta.version here is the TARGET Codex version (the registrar
|
|
204
|
+
# replaces the source harness's with the installed Codex's).
|
|
164
205
|
"cli_version": session.meta.version or "",
|
|
165
206
|
"first_user_message": first_user_message,
|
|
166
207
|
"memory_mode": "enabled",
|
|
@@ -221,8 +262,13 @@ def register_codex_session(
|
|
|
221
262
|
|
|
222
263
|
timestamp = started_at if started_at is not None else time.time()
|
|
223
264
|
iso_timestamp = datetime.fromtimestamp(timestamp, UTC).strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
|
224
|
-
|
|
225
|
-
|
|
265
|
+
# The resume picker parses rollout filenames strictly: local time,
|
|
266
|
+
# second precision, no timezone suffix — matching what Codex itself
|
|
267
|
+
# writes. Record timestamps inside the file stay UTC.
|
|
268
|
+
local = datetime.fromtimestamp(timestamp)
|
|
269
|
+
day_path = local.strftime("%Y/%m/%d")
|
|
270
|
+
file_stamp = local.strftime("%Y-%m-%dT%H-%M-%S")
|
|
271
|
+
rollout_path = home / "sessions" / day_path / f"rollout-{file_stamp}-{session_id}.jsonl"
|
|
226
272
|
if rollout_path.exists():
|
|
227
273
|
raise CodexRegistrationError(f"Codex rollout already exists: {rollout_path}")
|
|
228
274
|
|
|
@@ -232,6 +278,9 @@ def register_codex_session(
|
|
|
232
278
|
cwd=resolved_cwd,
|
|
233
279
|
model=model,
|
|
234
280
|
model_provider=model_provider,
|
|
281
|
+
# The source harness's version must not masquerade as Codex's;
|
|
282
|
+
# stamp the installed Codex's version observed from its own store.
|
|
283
|
+
version=infer_codex_cli_version(home),
|
|
235
284
|
)
|
|
236
285
|
target_session = replace(session, meta=target_meta)
|
|
237
286
|
transcript_sandbox, index_sandbox = workspace_write_policies(resolved_cwd)
|
|
@@ -315,3 +315,70 @@ def test_cli_register_codex_backs_up_and_adds_handshake(tmp_path):
|
|
|
315
315
|
]
|
|
316
316
|
) == 0
|
|
317
317
|
assert len(list(home.glob("state_5.sqlite.session-bridge-backup-*"))) == 2
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def test_registration_matches_native_picker_requirements(tmp_path, monkeypatch):
|
|
321
|
+
"""The resume picker only lists sessions whose rollout carries an
|
|
322
|
+
event_msg user_message and whose filename matches the native pattern."""
|
|
323
|
+
import re
|
|
324
|
+
|
|
325
|
+
monkeypatch.setattr('session_bridge.writers.codex_db.shutil.which', lambda _: None)
|
|
326
|
+
|
|
327
|
+
home = tmp_path / "codex"
|
|
328
|
+
_make_codex_home(home)
|
|
329
|
+
session = _sample_session()
|
|
330
|
+
rollout = register_codex_session(
|
|
331
|
+
session, home, "1e8400e2-9b1d-4716-a4f6-426655440000",
|
|
332
|
+
cwd=str(tmp_path), title="picker check", model="gpt-5", model_provider="openai",
|
|
333
|
+
)
|
|
334
|
+
# Native filename: local time, no milliseconds/Z suffix.
|
|
335
|
+
assert re.fullmatch(
|
|
336
|
+
r"rollout-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-[0-9a-f-]{36}\.jsonl",
|
|
337
|
+
rollout.name,
|
|
338
|
+
), rollout.name
|
|
339
|
+
records = [json.loads(line) for line in rollout.read_text().splitlines()]
|
|
340
|
+
user_events = [
|
|
341
|
+
r for r in records
|
|
342
|
+
if r.get("type") == "event_msg" and r["payload"].get("type") == "user_message"
|
|
343
|
+
]
|
|
344
|
+
assert user_events, "picker requires an event_msg user_message in the rollout"
|
|
345
|
+
assert user_events[0]["payload"]["message"].strip()
|
|
346
|
+
conn = sqlite3.connect(home / "state_5.sqlite")
|
|
347
|
+
row = dict(zip([c[0] for c in conn.execute("SELECT model, first_user_message, preview, cli_version FROM threads").description],
|
|
348
|
+
conn.execute("SELECT model, first_user_message, preview, cli_version FROM threads").fetchone()))
|
|
349
|
+
conn.close()
|
|
350
|
+
assert row["model"] == "gpt-5"
|
|
351
|
+
assert row["first_user_message"].strip()
|
|
352
|
+
assert row["preview"].strip()
|
|
353
|
+
# The source harness's version must not masquerade as Codex's
|
|
354
|
+
# cli_version; a fresh isolated store has no native row to infer from.
|
|
355
|
+
assert row["cli_version"] == ""
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def test_cli_version_inferred_from_native_rows(tmp_path, monkeypatch):
|
|
359
|
+
monkeypatch.setattr('session_bridge.writers.codex_db.shutil.which', lambda _: None)
|
|
360
|
+
home = tmp_path / "codex"
|
|
361
|
+
_make_codex_home(home)
|
|
362
|
+
conn = sqlite3.connect(home / "state_5.sqlite")
|
|
363
|
+
conn.execute(
|
|
364
|
+
"INSERT INTO threads (id, rollout_path, created_at, updated_at, source, "
|
|
365
|
+
"model_provider, cwd, title, sandbox_policy, approval_mode, cli_version, "
|
|
366
|
+
"recency_at_ms, updated_at_ms) "
|
|
367
|
+
"VALUES (?, ?, 1, 1, 'cli', 'openai', '/tmp', 'native', '{}', 'on-request', "
|
|
368
|
+
"'0.145.0', 1, 1)",
|
|
369
|
+
(str(uuid.uuid4()), "/tmp/native.jsonl"),
|
|
370
|
+
)
|
|
371
|
+
conn.commit()
|
|
372
|
+
conn.close()
|
|
373
|
+
rollout = register_codex_session(
|
|
374
|
+
_sample_session(), home, "2e8400e2-9b1d-4716-a4f6-426655440000",
|
|
375
|
+
cwd=str(tmp_path), title="version check", model="gpt-5", model_provider="openai",
|
|
376
|
+
)
|
|
377
|
+
conn = sqlite3.connect(home / "state_5.sqlite")
|
|
378
|
+
got = conn.execute(
|
|
379
|
+
"SELECT cli_version FROM threads WHERE id='2e8400e2-9b1d-4716-a4f6-426655440000'"
|
|
380
|
+
).fetchone()[0]
|
|
381
|
+
conn.close()
|
|
382
|
+
assert got == "0.145.0"
|
|
383
|
+
meta = json.loads(rollout.read_text().splitlines()[0])
|
|
384
|
+
assert meta["payload"]["cli_version"] == "0.145.0"
|
|
@@ -138,6 +138,10 @@ def test_convert_flow_end_to_end(tmp_path):
|
|
|
138
138
|
assert isinstance(app.screen, ResultScreen)
|
|
139
139
|
assert app.screen.outcome.error is None
|
|
140
140
|
assert (out_dir / "converted.jsonl").exists()
|
|
141
|
+
# This flow targets claude-code without placement, so the result
|
|
142
|
+
# screen must warn that the file alone is not resumable.
|
|
143
|
+
body = str(app.screen.query_one("#result-body", Static).render())
|
|
144
|
+
assert "heads up" in body and "Enable placement" in body
|
|
141
145
|
|
|
142
146
|
await pilot.press("n")
|
|
143
147
|
await pilot.pause(0.2)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/__init__.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/_content.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/_jsonl.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/_pending.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/claude_code.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/codex.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/readers/hermes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/skill_install.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/discovery.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/tui/register.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/__init__.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/_common.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/claude_code.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/hermes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/src/session_bridge/writers/hermes_db.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_handshake_convert_cli.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round11_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round12_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round13_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round14_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round16_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round17_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round18_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round18b_fixes.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round21_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round22_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round25_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round27_fixes.py
RENAMED
|
File without changes
|
{agent_session_bridge-0.2.0 → agent_session_bridge-0.2.2}/tests/test_review_round28_fixes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|