agent-session-bridge 0.2.0__py3-none-any.whl
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.dist-info/METADATA +288 -0
- agent_session_bridge-0.2.0.dist-info/RECORD +37 -0
- agent_session_bridge-0.2.0.dist-info/WHEEL +5 -0
- agent_session_bridge-0.2.0.dist-info/entry_points.txt +3 -0
- agent_session_bridge-0.2.0.dist-info/licenses/LICENSE +21 -0
- agent_session_bridge-0.2.0.dist-info/top_level.txt +1 -0
- session_bridge/__init__.py +0 -0
- session_bridge/_ids.py +43 -0
- session_bridge/cli.py +473 -0
- session_bridge/convert.py +130 -0
- session_bridge/handshake.py +175 -0
- session_bridge/ir.py +246 -0
- session_bridge/place.py +98 -0
- session_bridge/readers/__init__.py +0 -0
- session_bridge/readers/_content.py +42 -0
- session_bridge/readers/_jsonl.py +50 -0
- session_bridge/readers/_pending.py +63 -0
- session_bridge/readers/claude_code.py +226 -0
- session_bridge/readers/codex.py +203 -0
- session_bridge/readers/hermes.py +167 -0
- session_bridge/skill_install.py +134 -0
- session_bridge/skills/session-handoff/SKILL.md +119 -0
- session_bridge/tui/__init__.py +0 -0
- session_bridge/tui/actions.py +123 -0
- session_bridge/tui/app.py +65 -0
- session_bridge/tui/discovery.py +208 -0
- session_bridge/tui/options.py +86 -0
- session_bridge/tui/register.py +475 -0
- session_bridge/tui/screens.py +710 -0
- session_bridge/tui/summary.py +41 -0
- session_bridge/writers/__init__.py +0 -0
- session_bridge/writers/_common.py +288 -0
- session_bridge/writers/claude_code.py +152 -0
- session_bridge/writers/codex.py +154 -0
- session_bridge/writers/codex_db.py +302 -0
- session_bridge/writers/hermes.py +142 -0
- session_bridge/writers/hermes_db.py +294 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-session-bridge
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Local-first cross-harness agent-session portability: export a live coding-agent session from one harness and resume it in another.
|
|
5
|
+
Author: Prakash Chaudhary
|
|
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
|
|
10
|
+
Project-URL: Project page, https://www.connectwithprakash.com/project/session-bridge
|
|
11
|
+
Keywords: ai-agents,claude-code,codex,session,cli,tui
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
28
|
+
Provides-Extra: tui
|
|
29
|
+
Requires-Dist: textual>=1.0; extra == "tui"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# session-bridge
|
|
33
|
+
|
|
34
|
+
[](https://github.com/connectwithprakash/session-bridge/actions/workflows/test.yml)
|
|
35
|
+
[](https://pypi.org/project/agent-session-bridge/)
|
|
36
|
+
[](https://pypi.org/project/agent-session-bridge/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
|
|
39
|
+
Local-first, cross-harness **agent-session portability**. Export a coding-agent
|
|
40
|
+
session from one harness and resume it in another when the original hits a usage
|
|
41
|
+
limit or otherwise stops.
|
|
42
|
+
|
|
43
|
+
Supports three harnesses today:
|
|
44
|
+
|
|
45
|
+
| Harness | Session store |
|
|
46
|
+
|---|---|
|
|
47
|
+
| Claude Code | `~/.claude/projects/<encoded-cwd>/<uuid>.jsonl` |
|
|
48
|
+
| Codex | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` |
|
|
49
|
+
| Hermes | `~/.hermes/sessions/<ts>_<id>.jsonl` |
|
|
50
|
+
|
|
51
|
+
No cloud. Everything runs against files already on your disk.
|
|
52
|
+
|
|
53
|
+
## Supported versions
|
|
54
|
+
|
|
55
|
+
session-bridge's real dependencies are other tools' storage formats, which can
|
|
56
|
+
change without notice. This table states exactly what each reader/registrar was
|
|
57
|
+
last verified against; registration paths validate the store schema at runtime
|
|
58
|
+
and fail closed (no write) when it doesn't match.
|
|
59
|
+
|
|
60
|
+
| Harness | Last verified against | Verification |
|
|
61
|
+
|---|---|---|
|
|
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 |
|
|
64
|
+
| Hermes | `state.db` schema as of 2026-07 | Live `hermes --resume` replay of a registered session |
|
|
65
|
+
| Python | 3.11 – 3.13 | CI test matrix |
|
|
66
|
+
|
|
67
|
+
A newer harness version usually still works (formats drift rarely), but treat
|
|
68
|
+
anything beyond this table as unverified: run `session-bridge inspect` first,
|
|
69
|
+
and expect SQLite registration to refuse cleanly if the schema moved.
|
|
70
|
+
|
|
71
|
+
## Why
|
|
72
|
+
|
|
73
|
+
Each harness writes an incompatible session log, and nothing bridges them.
|
|
74
|
+
Claude Code `/export` is lossy plain text, and OpenCode import/export is buggy
|
|
75
|
+
across versions. session-bridge normalizes any supported session into one
|
|
76
|
+
intermediate representation (IR), then renders it into another harness's shape.
|
|
77
|
+
It also carries the *pending state* (open tool calls, queued input) forward
|
|
78
|
+
through a **resume handshake**, so the receiving agent picks up deliberately
|
|
79
|
+
instead of guessing.
|
|
80
|
+
|
|
81
|
+
## How it works
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
source.jsonl ─▶ reader ─▶ IR (Session: messages, tools, pending) ─▶ writer ─▶ target.jsonl
|
|
85
|
+
│
|
|
86
|
+
└─▶ resume handshake (prepended system message)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- **IR** (`ir.py`) is the union of what the three harnesses can express: threaded
|
|
90
|
+
messages with typed content blocks (text / reasoning / tool_call / tool_result),
|
|
91
|
+
session metadata, tool schemas, and explicit pending state.
|
|
92
|
+
- **Readers** (`readers/`) normalize each harness into the IR.
|
|
93
|
+
- **Writers** (`writers/`) render the IR into a target harness and emit a
|
|
94
|
+
`ConversionReport` naming every asymmetry that could not transfer losslessly.
|
|
95
|
+
- **Handshake** (`handshake.py`) turns detected pending state + conversion notes
|
|
96
|
+
into a resume preamble injected as the first message of the resumed session.
|
|
97
|
+
|
|
98
|
+
## Install
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uv tool install 'agent-session-bridge[tui]' # recommended: CLI + TUI, isolated env
|
|
102
|
+
uvx agent-session-bridge --help # or: no-install run
|
|
103
|
+
pip install 'agent-session-bridge[tui]' # or: plain pip
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Installs the `session-bridge` command (docs use that name); an
|
|
107
|
+
`agent-session-bridge` alias is included, so either name runs the same CLI.
|
|
108
|
+
Drop `[tui]` for the CLI without the interactive TUI. From source:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cd session-bridge && uv sync --extra dev --extra tui # or: python3 -m pip install -e .
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
New here? [`TUTORIAL.md`](TUTORIAL.md) is a step-by-step walkthrough (find your
|
|
115
|
+
session file → inspect → convert → resume) with a real worked example. The
|
|
116
|
+
sections below are the quick reference.
|
|
117
|
+
|
|
118
|
+
## Usage
|
|
119
|
+
|
|
120
|
+
Prefer a guided flow? The interactive TUI discovers sessions across all three
|
|
121
|
+
stores, walks you through target/options, and shows conversion notes **before**
|
|
122
|
+
anything is written:
|
|
123
|
+
|
|
124
|
+

|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
python3 -m pip install -e '.[tui]' # the TUI needs the optional textual dependency
|
|
128
|
+
session-bridge tui
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Pick a session, then `c` to convert (with optional Claude Code placement) or
|
|
132
|
+
`g` to register it into Hermes's `state.db` or Codex's `state_5.sqlite`. Both
|
|
133
|
+
flows end in a plan screen — loss warnings, backup plan, and the equivalent
|
|
134
|
+
CLI command — before any file or database is touched; registration backs up
|
|
135
|
+
the store first by default, and the planning phase opens live stores
|
|
136
|
+
read-only.
|
|
137
|
+
|
|
138
|
+
Working with an agent instead? The repo ships an agent skill
|
|
139
|
+
([`skills/session-handoff`](skills/session-handoff/SKILL.md)) that teaches
|
|
140
|
+
any harness's agent to hand off its current session to another harness.
|
|
141
|
+
Install it machine-wide into every harness found on the box:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
session-bridge install-skill # symlinks into ~/.claude, ~/.codex, ~/.hermes skills dirs
|
|
145
|
+
session-bridge install-skill --copy # copies instead (survives uninstall, goes stale on upgrade)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Inspect a session's structure:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
session-bridge inspect --from claude-code ~/.claude/projects/<dir>/<uuid>.jsonl
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Convert between harnesses:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
session-bridge convert --from hermes --to claude-code SESSION.jsonl \
|
|
158
|
+
-o resumed.jsonl --handshake-out resume.md
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Conversion notes (lossy asymmetries) are printed to stderr; the handshake is
|
|
162
|
+
prepended to the output by default (use `--no-handshake` to disable).
|
|
163
|
+
|
|
164
|
+
If the source stopped mid-turn with a tool call that never returned, that call
|
|
165
|
+
has no result, and a provider rejects a tool call with no matching result on the
|
|
166
|
+
next turn (OpenAI Responses returns a 400; Anthropic requires a `tool_result`).
|
|
167
|
+
Pass `--stub-open-calls` to append a synthetic interrupted result
|
|
168
|
+
(`[session interrupted...]`, marked as an error) for each genuinely-open call, so
|
|
169
|
+
the converted transcript is valid to resume; the report still discloses that the
|
|
170
|
+
call was interrupted.
|
|
171
|
+
|
|
172
|
+
## What transfers, and what doesn't
|
|
173
|
+
|
|
174
|
+
The conversation core (user/assistant text, reasoning summaries, tool calls,
|
|
175
|
+
tool results, and call↔result linkage) transfers between all three harnesses.
|
|
176
|
+
The following are **inherently lossy** and are reported per conversion (see
|
|
177
|
+
`docs/schema-reference.md` for the full analysis):
|
|
178
|
+
|
|
179
|
+
1. **Thread topology:** only Claude Code has `parentUuid` branches. Converting
|
|
180
|
+
away flattens forks; converting in synthesizes a linear chain.
|
|
181
|
+
2. **Reasoning signatures:** provider-bound, so reasoning survives as summary text.
|
|
182
|
+
3. **Tool schemas:** only Hermes stores them; reconstructed from invoked names otherwise.
|
|
183
|
+
4. **Base/system instructions:** only Codex stores them.
|
|
184
|
+
5. **Queued user input:** only Claude Code records it, so it surfaces in the handshake.
|
|
185
|
+
6. **Permission/sandbox posture:** richest in Codex, absent in Hermes.
|
|
186
|
+
7. **Per-turn model switches:** Hermes stores a single session model.
|
|
187
|
+
|
|
188
|
+
## Getting a converted session recognized by the target
|
|
189
|
+
|
|
190
|
+
How a converted transcript becomes resumable differs per harness. Both cases are
|
|
191
|
+
verified on real installs (a session was round-tripped Claude Code → Hermes →
|
|
192
|
+
Claude Code and successfully resumed in a live `claude` process, recalling a fact
|
|
193
|
+
that existed only in the converted transcript).
|
|
194
|
+
|
|
195
|
+
| Harness | How to place it | Resumes from file alone? |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| Claude Code (2.1.x) | Write to `~/.claude/projects/<encoded-cwd>/<uuid>.jsonl`, then `claude --resume <uuid>` **launched from the matching cwd** | **Yes** |
|
|
198
|
+
| Hermes | Valid filename in `~/.hermes/sessions/` is not enough | **No** (needs a SQLite session-store row) |
|
|
199
|
+
| Codex | `register-codex` writes a rollout and its `threads` index row in `state_5.sqlite` | **Yes** (live-recall verified with Codex CLI 0.145.0) |
|
|
200
|
+
|
|
201
|
+
**Claude Code** resolves `--resume <uuid>` directly from the transcript file. The
|
|
202
|
+
one catch: the encoded-cwd directory name must match the directory you launch
|
|
203
|
+
`claude` from (note macOS symlinks like `/tmp` → `/private/tmp`; use the real
|
|
204
|
+
resolved path). No separate index write is needed. Pass `--place-claude-cwd` to
|
|
205
|
+
`convert` and session-bridge writes the transcript to the right place and prints
|
|
206
|
+
the exact resume command:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
session-bridge convert --from hermes --to claude-code SESSION.jsonl \
|
|
210
|
+
--place-claude-cwd ~/Developer/myproject
|
|
211
|
+
# placed resumable session -> ~/.claude/projects/-Users-you-Developer-myproject/<uuid>.jsonl
|
|
212
|
+
# resume with: (cd ~/Developer/myproject && claude --resume <uuid>)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
If a transcript already exists at the chosen `--session-id`, placement fails
|
|
216
|
+
rather than silently overwriting a recovered session; pass `--force` to replace
|
|
217
|
+
it deliberately.
|
|
218
|
+
|
|
219
|
+
**Hermes** stores sessions in a SQLite database (`~/.hermes/state.db`), across a
|
|
220
|
+
`sessions` row plus one `messages` row per turn; the `.jsonl` files are exports,
|
|
221
|
+
not the source of truth. Use `session-bridge register` to write those rows (it
|
|
222
|
+
backs up the DB first):
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
session-bridge register --from claude-code SESSION.jsonl \
|
|
226
|
+
--model moonshotai/kimi-k3 --title "resumed from claude code"
|
|
227
|
+
# backed up state.db -> ...
|
|
228
|
+
# registered session sb_... into ~/.hermes/state.db
|
|
229
|
+
# resume with: hermes --resume sb_...
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Verified end-to-end against a real store: `hermes --resume` replays the registered
|
|
233
|
+
history and the model recalls it. Two things matter, both handled by the command:
|
|
234
|
+
|
|
235
|
+
- a real `started_at` (set automatically) so the session isn't sorted below the
|
|
236
|
+
default `hermes sessions list` limit;
|
|
237
|
+
- `--model` must name a model Hermes has a provider for. A cross-harness source id
|
|
238
|
+
(e.g. an Anthropic `claude-*` id from a Claude Code session) that Hermes cannot
|
|
239
|
+
route makes the resumed turn fall back and lose context, so set `--model` to a
|
|
240
|
+
Hermes-configured model.
|
|
241
|
+
|
|
242
|
+
**Codex** reading and round-trip are validated against a real tool-using session
|
|
243
|
+
(driven through OpenRouter): `function_call` / `function_call_output` / `reasoning`
|
|
244
|
+
(both `summary` and `content[]` shapes) parse correctly and round-trip identically.
|
|
245
|
+
Codex also indexes sessions in SQLite (`state_5.sqlite`, a `threads` row with a
|
|
246
|
+
`rollout_path`). Use `register-codex` to write a converted rollout and index it;
|
|
247
|
+
the command validates the local schema, takes a SQLite online-backup before the
|
|
248
|
+
mutation, publishes the rollout without overwriting another registration, then
|
|
249
|
+
indexes it in SQLite. It removes its own rollout if the index transaction fails;
|
|
250
|
+
a process crash can still leave an unindexed rollout, which Codex ignores.
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
session-bridge register-codex --from hermes SESSION.jsonl \
|
|
254
|
+
--cwd ~/Developer/myproject --title "resumed from Hermes"
|
|
255
|
+
# backed up Codex state_5.sqlite -> ...
|
|
256
|
+
# registered session <uuid> into ~/.codex/state_5.sqlite
|
|
257
|
+
# resume with: (cd ~/Developer/myproject && codex resume <uuid>)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
This registration path is covered against an isolated Codex-shaped SQLite store
|
|
261
|
+
and was live-recall verified with Codex CLI 0.145.0: after `codex exec resume`
|
|
262
|
+
opened an imported session, the model returned a unique sentinel that existed
|
|
263
|
+
only in that transcript.
|
|
264
|
+
|
|
265
|
+
## Known limitations
|
|
266
|
+
|
|
267
|
+
- Codex registration is schema-validated and regression-tested against an isolated
|
|
268
|
+
`threads` store; live recall was verified with Codex CLI 0.145.0. Future Codex
|
|
269
|
+
schema changes still need the same authenticated acceptance check.
|
|
270
|
+
- Queued-input detection is conservative: it may over-report undelivered input
|
|
271
|
+
rather than silently drop it (the safe direction for resume). Enqueue/dequeue
|
|
272
|
+
matching is scoped per `sessionId`.
|
|
273
|
+
- Pending-state resumption produces a handshake for a human/agent to act on; it
|
|
274
|
+
does not itself re-execute open tool calls.
|
|
275
|
+
- Failed tool results: Codex and Hermes have no native error flag, so `is_error`
|
|
276
|
+
is preserved as a `[tool error]` text prefix (and reported) rather than a field.
|
|
277
|
+
- Empty-content messages: writers preserve the turn to keep message count stable,
|
|
278
|
+
but a fully empty turn does not round-trip back through the Codex/Hermes readers'
|
|
279
|
+
content guards. This is documented, and not observed in real data.
|
|
280
|
+
|
|
281
|
+
## Development
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
python3 -m pytest # IR, three readers, writers/round-trips, handshake, placement, and both SQLite registrars
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Real captured sessions may contain secrets; `fixtures/real/` is gitignored and
|
|
288
|
+
tests run only against synthetic, faithful fixtures.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
agent_session_bridge-0.2.0.dist-info/licenses/LICENSE,sha256=lM-0nT3o6bxmBhZE7Dx805LAsocbK292sH2JTvenvps,1074
|
|
2
|
+
session_bridge/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
session_bridge/_ids.py,sha256=aFRqgzFaDA0bWhHiqQCR2unfUZQ9kFBaJtpuCvo-0SA,1627
|
|
4
|
+
session_bridge/cli.py,sha256=HcAI07jNlWc7ndjQ1FtuMzDXxeue2cFLS7eDUfEcaO8,19421
|
|
5
|
+
session_bridge/convert.py,sha256=fjA_qfcZg9hHHCY-mPufckrKJKDJWdapGVJGvTIUhH8,4684
|
|
6
|
+
session_bridge/handshake.py,sha256=csfTHVPit2EPcvFwSSHxCU8SEadrhtMNYRu79hoB9JE,7509
|
|
7
|
+
session_bridge/ir.py,sha256=Y8dqkF_3okeK_zVl1sqN0YJAa0zELrdvGqPkJCYMINU,8920
|
|
8
|
+
session_bridge/place.py,sha256=oY4j83BIvTA19XwfhqLiwkitCDIv7NhLWmYBIn4cqNc,3776
|
|
9
|
+
session_bridge/skill_install.py,sha256=txh7AyPGldfCN5yKhYF5H_sTOXEjHuCHZhtEyeeLuuE,4673
|
|
10
|
+
session_bridge/readers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
session_bridge/readers/_content.py,sha256=nfGECKPg8r5hWZqY5ZOkyTip5Cx3d2zX6pzlOJQUark,1553
|
|
12
|
+
session_bridge/readers/_jsonl.py,sha256=wT-NbyyifmgL_DSmrnTnmDZK2YukdqCSv1ayZ7BJQjM,2158
|
|
13
|
+
session_bridge/readers/_pending.py,sha256=Gv7Mg-Qa92XFSnfFD7adr4lXG5g0Mon_QYSJd7uVzlI,3107
|
|
14
|
+
session_bridge/readers/claude_code.py,sha256=ZiCXQVd8CHMHwQZWDkQFVt4cw6zGH6vmfZdZsUzGAnU,9710
|
|
15
|
+
session_bridge/readers/codex.py,sha256=1TWGme4koEtHQXduIG0LpDCusHwHQo5TGlvqMJ94wl4,8429
|
|
16
|
+
session_bridge/readers/hermes.py,sha256=kAYJnjRrmLtn7jlWUx9PtgWLGmctR5gRn2wRpY14Xpo,6109
|
|
17
|
+
session_bridge/skills/session-handoff/SKILL.md,sha256=ahK87vtI9X1HZD4YNc9szf2I6KAEjcw4MK16yVD8-mE,5021
|
|
18
|
+
session_bridge/tui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
session_bridge/tui/actions.py,sha256=4eSItFAxZbnJhXzY6Fx89slqtCp0Ya905-pFEuxP540,4257
|
|
20
|
+
session_bridge/tui/app.py,sha256=xOKzw_cmsdkMXoUoyCz_ognm90vADxCkJFBhWxAEhl0,1934
|
|
21
|
+
session_bridge/tui/discovery.py,sha256=g4V3_a6AXd2PMO29Azf5QCPYgJYh6Vzxtme64-joXGM,7786
|
|
22
|
+
session_bridge/tui/options.py,sha256=HpQHS-yUh9ldSRHsIb2fcqc9ewrh1BdV1zecl5u0w0A,3036
|
|
23
|
+
session_bridge/tui/register.py,sha256=QnP-y0JEW4jKVpQelsIp5LAu_jCXlCWQBs9RgRKB2zw,16840
|
|
24
|
+
session_bridge/tui/screens.py,sha256=BSGxLYaWlHa4EGAWQXWFw5bklfkraA3GhrkKZ_9fiT8,28146
|
|
25
|
+
session_bridge/tui/summary.py,sha256=c_UuMaDsA660Fdtgc5HpKHYZlMeAhcwa0sKazu5aLFc,1828
|
|
26
|
+
session_bridge/writers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
session_bridge/writers/_common.py,sha256=DOcwESFvzKvd2kZgl5rcYjJ_vsFFFkE5GhGjcyyp6Bo,13350
|
|
28
|
+
session_bridge/writers/claude_code.py,sha256=EpC0cK0pfFYme8YLcsNNLNZ9bOF8F1ALRg2JwpgSGGo,6223
|
|
29
|
+
session_bridge/writers/codex.py,sha256=fHObELNwkM7gPqQyU0e5eOYvyMRlkd_Ay92bfmkem-U,6372
|
|
30
|
+
session_bridge/writers/codex_db.py,sha256=sqNVmgzW79uOUNCM3qylNFotE-VyOfRMumnkceQjIqM,10710
|
|
31
|
+
session_bridge/writers/hermes.py,sha256=qh-oWS0l-m89GMS5n63OOllnFnR1_3bDOS-OeTDpTlQ,5151
|
|
32
|
+
session_bridge/writers/hermes_db.py,sha256=SrFMJ44QBbPSw56ovwifU9mimYfbw34RD_YQ6zNwNI8,12242
|
|
33
|
+
agent_session_bridge-0.2.0.dist-info/METADATA,sha256=fCAK08BLq1jwsl5HGf0tOVsl9hTH6gVZeIDAyT4tQjs,13928
|
|
34
|
+
agent_session_bridge-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
35
|
+
agent_session_bridge-0.2.0.dist-info/entry_points.txt,sha256=nbWMsfbzdM0kc98QBFWYG8gSt9YbL7gTdhLhnqVMpeQ,106
|
|
36
|
+
agent_session_bridge-0.2.0.dist-info/top_level.txt,sha256=PQPKTGEKt7KLPeW7SH9c8InG50Yup8yEaz301YQIPYA,15
|
|
37
|
+
agent_session_bridge-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Prakash Chaudhary
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
session_bridge
|
|
File without changes
|
session_bridge/_ids.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Session-id validation shared by every path that puts an id into a filesystem
|
|
2
|
+
path or a store key.
|
|
3
|
+
|
|
4
|
+
A session id can come from a CLI argument (``--session-id``) or an untrusted
|
|
5
|
+
source session. Interpolating it into a file path (``place_claude_code``) without
|
|
6
|
+
validation allows path traversal (``../../evil``), so all id-consuming paths must
|
|
7
|
+
reject anything outside a safe charset.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
# UUIDs, Hermes ids (``20260718_124320_97f3660d``), and the tool's own ``sb_...``
|
|
15
|
+
# ids all fit this. Deliberately excludes ``/``, ``.``, and whitespace.
|
|
16
|
+
_SAFE_ID = re.compile(r"^[A-Za-z0-9_-]+$")
|
|
17
|
+
|
|
18
|
+
# Bound the length so a charset-valid but absurd id doesn't reach the filesystem
|
|
19
|
+
# and raise a raw OSError ("File name too long", errno 63) at write time. 128 is
|
|
20
|
+
# comfortably above every real id format (a UUID is 36 chars).
|
|
21
|
+
_MAX_ID_LEN = 128
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UnsafeSessionIdError(ValueError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def validate_session_id(session_id: str) -> str:
|
|
29
|
+
"""Return ``session_id`` unchanged if safe, else raise UnsafeSessionIdError.
|
|
30
|
+
|
|
31
|
+
Rejects empty ids, ids longer than ``_MAX_ID_LEN``, and any id containing
|
|
32
|
+
characters outside ``[A-Za-z0-9_-]`` — in particular path separators and
|
|
33
|
+
``..`` sequences.
|
|
34
|
+
"""
|
|
35
|
+
if not session_id or not _SAFE_ID.match(session_id):
|
|
36
|
+
raise UnsafeSessionIdError(
|
|
37
|
+
f"unsafe session id {session_id!r}: only letters, digits, '_' and '-' allowed"
|
|
38
|
+
)
|
|
39
|
+
if len(session_id) > _MAX_ID_LEN:
|
|
40
|
+
raise UnsafeSessionIdError(
|
|
41
|
+
f"session id too long ({len(session_id)} chars; max {_MAX_ID_LEN})"
|
|
42
|
+
)
|
|
43
|
+
return session_id
|