agent_sessions 0.3.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +71 -3
- data/doc/Agent/Sessions/Adapters/Amp.md +91 -0
- data/doc/Agent/Sessions/Adapters/Base.md +198 -0
- data/doc/Agent/Sessions/Adapters/Claude.md +63 -0
- data/doc/Agent/Sessions/Adapters/Codex.md +79 -0
- data/doc/Agent/Sessions/Adapters/Copilot.md +38 -0
- data/doc/Agent/Sessions/Adapters/Cursor.md +54 -0
- data/doc/Agent/Sessions/Adapters/CursorIde.md +49 -0
- data/doc/Agent/Sessions/Adapters/Enumeration.md +135 -0
- data/doc/Agent/Sessions/Adapters/Gemini.md +67 -0
- data/doc/Agent/Sessions/Adapters/Grok.md +64 -0
- data/doc/Agent/Sessions/Adapters/Opencode.md +93 -0
- data/doc/Agent/Sessions/Adapters/Pi.md +137 -0
- data/doc/Agent/Sessions/Adapters/Qwen.md +32 -0
- data/doc/Agent/Sessions/Adapters.md +5 -0
- data/doc/Agent/Sessions/Audit/Finding.md +27 -0
- data/doc/Agent/Sessions/Audit.md +21 -0
- data/doc/Agent/Sessions/CLI.md +36 -0
- data/doc/Agent/Sessions/Check.md +27 -0
- data/doc/Agent/Sessions/Compaction.md +23 -0
- data/doc/Agent/Sessions/EnvOverride.md +19 -0
- data/doc/Agent/Sessions/Error.md +6 -0
- data/doc/Agent/Sessions/HomeExpansion.md +11 -0
- data/doc/Agent/Sessions/Location.md +60 -0
- data/doc/Agent/Sessions/Message.md +59 -0
- data/doc/Agent/Sessions/MissingDependency.md +6 -0
- data/doc/Agent/Sessions/Node.md +24 -0
- data/doc/Agent/Sessions/Part.md +41 -0
- data/doc/Agent/Sessions/Readers/Amp.md +41 -0
- data/doc/Agent/Sessions/Readers/Base.md +91 -0
- data/doc/Agent/Sessions/Readers/Claude.md +77 -0
- data/doc/Agent/Sessions/Readers/Codex.md +81 -0
- data/doc/Agent/Sessions/Readers/Copilot.md +31 -0
- data/doc/Agent/Sessions/Readers/Gemini.md +38 -0
- data/doc/Agent/Sessions/Readers/Grok.md +45 -0
- data/doc/Agent/Sessions/Readers/Opencode.md +37 -0
- data/doc/Agent/Sessions/Readers/Pi.md +38 -0
- data/doc/Agent/Sessions/Readers/Qwen.md +30 -0
- data/doc/Agent/Sessions/Readers.md +5 -0
- data/doc/Agent/Sessions/Session.md +73 -0
- data/doc/Agent/Sessions/Sqlite.md +43 -0
- data/doc/Agent/Sessions/Store.md +57 -0
- data/doc/Agent/Sessions/UnknownAgent.md +6 -0
- data/doc/Agent/Sessions/UnreadableStore.md +6 -0
- data/doc/Agent/Sessions/UnsupportedFormat.md +6 -0
- data/doc/Agent/Sessions/Usage.md +56 -0
- data/doc/Agent/Sessions.md +177 -0
- data/doc/Agent.md +5 -0
- data/doc/CHANGELOG.md +69 -0
- data/doc/README.md +162 -0
- data/doc/index.csv +316 -0
- data/exe/agent-sessions +0 -2
- data/lib/agent/sessions/adapters/cursor.rb +3 -3
- data/lib/agent/sessions/cli.rb +2 -2
- data/lib/agent/sessions/message.rb +1 -2
- data/lib/agent/sessions/part.rb +1 -2
- data/lib/agent/sessions/version.rb +1 -1
- data/llm.txt +177 -0
- metadata +54 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Class Agent::Sessions::Adapters::Cursor <a id="class-Agent-Sessions-Adapters-Cursor"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Adapters::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/adapters/cursor.rb |
|
|
7
|
+
|
|
8
|
+
## Public Instance Methods
|
|
9
|
+
### `project_path_for(path)` <a id="method-i-project_path_for"></a> <a id="project_path_for-instance_method"></a>
|
|
10
|
+
cwd's presence in meta.json is not enough on its own (rule 1): the key can
|
|
11
|
+
hold a Hash, an Integer, anything JSON allows, and project_paths' .uniq.sort
|
|
12
|
+
raises on a non-String member. is_a?(String) is the guard, not merely a style
|
|
13
|
+
preference.
|
|
14
|
+
|
|
15
|
+
### `session_id_from(path)` <a id="method-i-session_id_from"></a> <a id="session_id_from-instance_method"></a>
|
|
16
|
+
chats/<chat-id>/<uuid>/store.db — two nested ids (design doc 16 Q5), so the
|
|
17
|
+
session id keeps both. The blob store is never opened here; the sibling
|
|
18
|
+
meta.json is the metadata source (8.3), with stat as fallback.
|
|
19
|
+
|
|
20
|
+
Pure string manipulation on `path` — File.basename/File.dirname never raise
|
|
21
|
+
for any String input, so this hook cannot violate rule 3 (build_session lets a
|
|
22
|
+
raising hook propagate) regardless of shape. A path shallower than two
|
|
23
|
+
segments is not reachable through this store's OWN enumeration: the glob above
|
|
24
|
+
is "<strong>/</strong>/store.db", and Dir.glob's "*" never crosses a "/", so
|
|
25
|
+
every path this adapter actually enumerates is exactly two directories deep,
|
|
26
|
+
by construction, not by convention (see
|
|
27
|
+
test_session_id_from_does_not_raise_for_a_shallow_path, which calls this hook
|
|
28
|
+
directly to pin the behaviour for a caller that bypasses the glob).
|
|
29
|
+
|
|
30
|
+
### `started_at_for(path, stat)` <a id="method-i-started_at_for"></a> <a id="started_at_for-instance_method"></a>
|
|
31
|
+
Not documented.
|
|
32
|
+
|
|
33
|
+
### `updated_at_for(path, stat)` <a id="method-i-updated_at_for"></a> <a id="updated_at_for-instance_method"></a>
|
|
34
|
+
Not documented.
|
|
35
|
+
|
|
36
|
+
### `warnings()` <a id="method-i-warnings"></a> <a id="warnings-instance_method"></a>
|
|
37
|
+
meta.json's field names (createdAtMs, updatedAtMs, cwd) are read from design
|
|
38
|
+
doc 8.3, itself written from a machine that had them to check against — this
|
|
39
|
+
machine has no ~/.cursor/chats (Cursor CLI is a separate product from the
|
|
40
|
+
Cursor editor and is simply not installed here). Gated, the same shape as pi's
|
|
41
|
+
identical warning about its own unverified header key and cursor_ide's about
|
|
42
|
+
its real session location below: a "here is what breaks, please act on it"
|
|
43
|
+
report reaches only someone whose declared store actually exists.
|
|
44
|
+
|
|
45
|
+
Why THIS unverified assumption specifically needs a warning, where some others
|
|
46
|
+
might get away without one: the failure is silent and looks correct. If
|
|
47
|
+
createdAtMs/updatedAtMs are the wrong keys, meta_time returns nil and
|
|
48
|
+
started_at_for/updated_at_for fall back to stat.birthtime/mtime — real file
|
|
49
|
+
timestamps, not an obviously broken value. If cwd is the wrong key,
|
|
50
|
+
project_path_for returns nil exactly the way it correctly does for a chat that
|
|
51
|
+
genuinely has no recorded cwd. Nothing in the output distinguishes "Cursor
|
|
52
|
+
recorded no project" from "the gem read the wrong key" — `projects`, `du --by
|
|
53
|
+
project`, and `sessions_for_project` all silently under-report Cursor, with no
|
|
54
|
+
error and no implausible-looking number anywhere to notice.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Class Agent::Sessions::Adapters::CursorIde <a id="class-Agent-Sessions-Adapters-CursorIde"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Adapters::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/adapters/cursor_ide.rb |
|
|
7
|
+
|
|
8
|
+
Cursor's IDE agent (Composer). Repointed 2026-08-24 at the store the 0.2
|
|
9
|
+
adapter's own warning named as the real one, now that it has been opened
|
|
10
|
+
rather than inferred: ~/Library/Application Support/Cursor/User/
|
|
11
|
+
globalStorage/state.vscdb, table cursorDiskKV, keys composerData:<uuid> — 6
|
|
12
|
+
real rows on the machine this was written on. The 0.2 declaration
|
|
13
|
+
(~/.cursor/projects/<strong>/agent-transcripts/</strong>) does not exist there
|
|
14
|
+
at all, so this adapter reported nothing for an agent that had sessions.
|
|
15
|
+
|
|
16
|
+
What is verified: the file, the table, the key prefix, and the record's own
|
|
17
|
+
composerId/createdAt. What is NOT: anything about the conversation itself —
|
|
18
|
+
all six records on this machine carry "conversation": [], so the shape of a
|
|
19
|
+
turn has never been seen here. fidelity stays :metadata and no reader exists,
|
|
20
|
+
which is the honest report: this adapter can say a session happened and when,
|
|
21
|
+
and must not pretend to say what was said.
|
|
22
|
+
|
|
23
|
+
## Constants
|
|
24
|
+
### `KEY_PREFIX` <a id="constant-KEY_PREFIX"></a> <a id="KEY_PREFIX-constant"></a>
|
|
25
|
+
Not documented.
|
|
26
|
+
|
|
27
|
+
## Public Instance Methods
|
|
28
|
+
### `project_path_for(_path)` <a id="method-i-project_path_for"></a> <a id="project_path_for-instance_method"></a>
|
|
29
|
+
Cursor's composer records do not name a project. context.fileSelections holds
|
|
30
|
+
paths of files ATTACHED to a turn — on this machine, a settings file from an
|
|
31
|
+
unrelated directory — and a workspace root inferred from one attachment would
|
|
32
|
+
be a guess dressed as a fact. nil is the honest answer, and `projects`
|
|
33
|
+
reporting nothing for this agent is correct rather than empty-looking.
|
|
34
|
+
|
|
35
|
+
### `project_paths()` <a id="method-i-project_paths"></a> <a id="project_paths-instance_method"></a>
|
|
36
|
+
Not documented.
|
|
37
|
+
|
|
38
|
+
### `sessions()` <a id="method-i-sessions"></a> <a id="sessions-instance_method"></a>
|
|
39
|
+
Rows, not files, so Base's glob enumeration is replaced the way opencode's is
|
|
40
|
+
— including the existence check FIRST, so a machine without Cursor never needs
|
|
41
|
+
the sqlite3 gem at all.
|
|
42
|
+
|
|
43
|
+
value is parsed for createdAt alone. It is the whole composer document
|
|
44
|
+
(context, capabilities, code blocks), which is why `bytes` stays nil: a row in
|
|
45
|
+
a shared database has no file size of its own, and the database's size belongs
|
|
46
|
+
to all 7 rows together.
|
|
47
|
+
|
|
48
|
+
### `sessions_for_project(_dir)` <a id="method-i-sessions_for_project"></a> <a id="sessions_for_project-instance_method"></a>
|
|
49
|
+
Not documented.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Module Agent::Sessions::Adapters::Enumeration <a id="module-Agent-Sessions-Adapters-Enumeration"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Defined in** | lib/agent/sessions/adapters/enumeration.rb |
|
|
6
|
+
|
|
7
|
+
Layer 2: turning a resolved store into sessions. Split out of Base once it
|
|
8
|
+
held three concerns at 460 lines, and before Layer 3 readers add a fourth.
|
|
9
|
+
|
|
10
|
+
Mixed into Base rather than included per adapter, so every adapter keeps
|
|
11
|
+
inheriting all of this and overriding the hooks it needs — the extraction is a
|
|
12
|
+
move, not a change in how an adapter is written.
|
|
13
|
+
|
|
14
|
+
What this half needs from Layer 1 is deliberately small, and worth keeping
|
|
15
|
+
small: `primary_layer` (the store to enumerate and the format to stamp on each
|
|
16
|
+
session) and the class-level DSL readers `agent_name` and `fidelity_value`.
|
|
17
|
+
Nothing here resolves a path, reads an env override, or touches @env. A method
|
|
18
|
+
that needs to do any of those belongs in Base.
|
|
19
|
+
|
|
20
|
+
## Constants
|
|
21
|
+
### `MAX_LINE_BYTES` <a id="constant-MAX_LINE_BYTES"></a> <a id="MAX_LINE_BYTES-constant"></a>
|
|
22
|
+
Caps how many bytes one iteration of a JSONL scan may pull into memory. "One
|
|
23
|
+
line" is not a bounded quantity on disk: a record carrying a pasted file or a
|
|
24
|
+
base64 image is routinely tens of MB, and a truncated file may hold no newline
|
|
25
|
+
at all. An over-long line arrives as chunks of this size, which fail to parse
|
|
26
|
+
and are skipped, so the scan gives up rather than reading a 2.6 GB file into a
|
|
27
|
+
single String.
|
|
28
|
+
|
|
29
|
+
## Public Instance Methods
|
|
30
|
+
### `bytes_for(_path, stat)` <a id="method-i-bytes_for"></a> <a id="bytes_for-instance_method"></a>
|
|
31
|
+
Bytes this session occupies on disk. The transcript alone for a store that
|
|
32
|
+
keeps one file per session; an adapter whose agent writes sidecar files beside
|
|
33
|
+
the transcript overrides this and adds them. Like the two time hooks it takes
|
|
34
|
+
the stat the enumerator already holds, so the common case still costs nothing
|
|
35
|
+
beyond the syscall already made.
|
|
36
|
+
|
|
37
|
+
An override runs EAGERLY for every session, so it carries build_session's
|
|
38
|
+
constraint: it must not raise on an unreadable path, or one bad sidecar takes
|
|
39
|
+
down the whole listing rather than its own row.
|
|
40
|
+
|
|
41
|
+
### `encode_project(_dir)` <a id="method-i-encode_project"></a> <a id="encode_project-instance_method"></a>
|
|
42
|
+
nil means this adapter has no directory-name fallback rule. Used only by
|
|
43
|
+
sessions_for_project, and only for a session whose own recorded cwd could not
|
|
44
|
+
be read. When overridden: dir arrives pre-expanded here from
|
|
45
|
+
sessions_for_project (File.expand_path), which is the precondition an override
|
|
46
|
+
may rely on — a direct caller must pass an absolute, expanded path itself, or
|
|
47
|
+
the encoding is nonsense ("app", "~/app", and a trailing slash all encode
|
|
48
|
+
differently from the canonical form real project directories were named from).
|
|
49
|
+
|
|
50
|
+
### `project_dir_name(path)` <a id="method-i-project_dir_name"></a> <a id="project_dir_name-instance_method"></a>
|
|
51
|
+
The directory whose name the encoding must match, when sessions_for_project
|
|
52
|
+
falls back to it. Overridable: not every store puts the encoded project
|
|
53
|
+
directly above the session file — cursor_ide nests
|
|
54
|
+
projects/<name>/agent-transcripts/*, where the immediate parent is
|
|
55
|
+
agent-transcripts and matching it would find nothing, silently.
|
|
56
|
+
|
|
57
|
+
### `project_path_for(_path)` <a id="method-i-project_path_for"></a> <a id="project_path_for-instance_method"></a>
|
|
58
|
+
nil means the project is unknown for this session. Adapters override with a
|
|
59
|
+
bounded read of their own metadata; Base cannot guess.
|
|
60
|
+
|
|
61
|
+
### `project_paths()` <a id="method-i-project_paths"></a> <a id="project_paths-instance_method"></a>
|
|
62
|
+
Distinct recorded project paths, sorted. This is the read-everything direction
|
|
63
|
+
(design doc section 7): the encodings cannot be reversed, so the recorded cwd
|
|
64
|
+
inside each file is the only reliable source. Sessions whose project cannot be
|
|
65
|
+
determined are excluded, not returned as nil.
|
|
66
|
+
|
|
67
|
+
Sorted rather than left in glob order because a stable order is what makes
|
|
68
|
+
`projects` output diffable and `du --by project` deterministic — and adapters
|
|
69
|
+
answering from a database would otherwise impose their own.
|
|
70
|
+
|
|
71
|
+
### `session_id_from(path)` <a id="method-i-session_id_from"></a> <a id="session_id_from-instance_method"></a>
|
|
72
|
+
--- Layer 2 hooks, overridable per adapter ---
|
|
73
|
+
|
|
74
|
+
### `sessions()` <a id="method-i-sessions"></a> <a id="sessions-instance_method"></a>
|
|
75
|
+
Lazily enumerates the primary store. Each consumed session costs one stat plus
|
|
76
|
+
filename parsing — never a content read. project_path is the exception and
|
|
77
|
+
pays for itself on first access.
|
|
78
|
+
|
|
79
|
+
A store the gem has no layout for is refused rather than reported empty:
|
|
80
|
+
Location#enumerable? exists precisely so "nothing here to enumerate" and
|
|
81
|
+
"enumerated, found none" stay distinguishable, and silently returning no
|
|
82
|
+
sessions is this gem's worst failure mode.
|
|
83
|
+
|
|
84
|
+
### `sessions_for_project(dir)` <a id="method-i-sessions_for_project"></a> <a id="sessions_for_project-instance_method"></a>
|
|
85
|
+
Match by RECORDED cwd, exact, per session (design doc section 7, revised
|
|
86
|
+
2026-08-05 — the third design for this method, kept honest here because the
|
|
87
|
+
next reader deserves to know why it is not "cheap"). The first two designs
|
|
88
|
+
were built and disproved against a real store, not in theory:
|
|
89
|
+
|
|
90
|
+
1. Directory-name matching (the original design) assumed a
|
|
91
|
+
session's parent directory equals encode(its own recorded cwd).
|
|
92
|
+
A project rename breaks that: the agent keeps writing under the
|
|
93
|
+
OLD encoded directory, so two directories can hold live sessions
|
|
94
|
+
for the SAME current cwd, and name-only matching silently
|
|
95
|
+
dropped the stale one — false negatives, the failure mode this
|
|
96
|
+
gem treats as worst (decision 11).
|
|
97
|
+
|
|
98
|
+
2. One-read-per-directory sampling (the first fix for #1) assumed
|
|
99
|
+
sessions within a directory share a cwd, to keep the match
|
|
100
|
+
sublinear. Reading a real renamed project's stale directory
|
|
101
|
+
disproved that: two of its three sessions had been resumed after
|
|
102
|
+
the rename and recorded the NEW cwd; the third was never resumed
|
|
103
|
+
and still recorded the OLD one. Sampling one session and
|
|
104
|
+
applying its verdict to the whole directory is wrong in BOTH
|
|
105
|
+
directions on the same store — it invented a false positive
|
|
106
|
+
here, and a different glob order would just as easily have
|
|
107
|
+
reproduced #1's false negative for that same directory.
|
|
108
|
+
Approximate cwd resolution doesn't make the error smaller; it
|
|
109
|
+
just moves where it lands.
|
|
110
|
+
|
|
111
|
+
Measured cost of reading every session instead of sampling: 0.17 ms per
|
|
112
|
+
session (68 real Claude sessions, full sweep, 0.012s total) — 0.7s
|
|
113
|
+
extrapolated to a 4,000-session store. That is what the sampling complexity
|
|
114
|
+
was buying, and it is not a trade worth making: the enumerator is already
|
|
115
|
+
lazy, so a caller taking first(n) never pays for sessions it never asked
|
|
116
|
+
about, and even the worst case (every session checked, no match) stays under a
|
|
117
|
+
second on a store two orders of magnitude larger than anything observed.
|
|
118
|
+
|
|
119
|
+
A session whose own cwd cannot be read (the scan gave up, the file is
|
|
120
|
+
unreadable, the adapter declares no reader) falls back to comparing ITS OWN
|
|
121
|
+
directory's name against the encoding, when the adapter declares one — this is
|
|
122
|
+
the only thing encode_project still buys: it keeps a session with an
|
|
123
|
+
unreadable header from becoming invisible, without resolving an unknown
|
|
124
|
+
project for every other session that happens to share its directory.
|
|
125
|
+
|
|
126
|
+
### `started_at_for(_path, stat)` <a id="method-i-started_at_for"></a> <a id="started_at_for-instance_method"></a>
|
|
127
|
+
Both time hooks take the stat the enumerator already holds, so a session still
|
|
128
|
+
costs one syscall. path is passed for adapters that answer from a sibling
|
|
129
|
+
metadata file instead.
|
|
130
|
+
|
|
131
|
+
nil beats a wrong guess when the filesystem cannot answer at all — and it says
|
|
132
|
+
so through ENOSYS/EPERM from statx as often as NotImplementedError.
|
|
133
|
+
|
|
134
|
+
### `updated_at_for(_path, stat)` <a id="method-i-updated_at_for"></a> <a id="updated_at_for-instance_method"></a>
|
|
135
|
+
Not documented.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Class Agent::Sessions::Adapters::Gemini <a id="class-Agent-Sessions-Adapters-Gemini"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Adapters::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/adapters/gemini.rb |
|
|
7
|
+
|
|
8
|
+
Gemini CLI. Verified against a real store on this machine (2026-08-24): 9
|
|
9
|
+
project directories, 12 chat files, 121 records.
|
|
10
|
+
|
|
11
|
+
The store is keyed by an opaque project hash, not by an encoded path:
|
|
12
|
+
~/.gemini/tmp/<projectHash>/chats/session-<UTC stamp>-<hex8>.json, with a
|
|
13
|
+
sibling logs.json holding a flat prompt log. Nothing anywhere under the store
|
|
14
|
+
names a working directory — grepping every JSON file in it for cwd, workspace,
|
|
15
|
+
projectPath, rootPath and directory found zero — which is why project
|
|
16
|
+
resolution below depends on a separate map file rather than on decoding the
|
|
17
|
+
hash.
|
|
18
|
+
|
|
19
|
+
## Constants
|
|
20
|
+
### `FILENAME` <a id="constant-FILENAME"></a> <a id="FILENAME-constant"></a>
|
|
21
|
+
session-2025-11-29T20-08-b20947ab.json. The trailing hex is NOT a session id:
|
|
22
|
+
two files in the real store share d4abc9ce while being different sessions, so
|
|
23
|
+
the whole basename is the id — unique, stable, and derivable without opening
|
|
24
|
+
the file, which is what Layer 2 is for. The agent's own sessionId lives inside
|
|
25
|
+
the document and reaches a caller through the reader.
|
|
26
|
+
|
|
27
|
+
## Public Class Methods
|
|
28
|
+
### `reader_class()` <a id="method-c-reader_class"></a> <a id="reader_class-class_method"></a>
|
|
29
|
+
Not documented.
|
|
30
|
+
|
|
31
|
+
## Public Instance Methods
|
|
32
|
+
### `encode_project(dir)` <a id="method-i-encode_project"></a> <a id="encode_project-instance_method"></a>
|
|
33
|
+
Not documented.
|
|
34
|
+
|
|
35
|
+
### `project_dir_name(path)` <a id="method-i-project_dir_name"></a> <a id="project_dir_name-instance_method"></a>
|
|
36
|
+
<base>/tmp/<projectHash>/chats/<file>.json — two levels up from the file, not
|
|
37
|
+
one, so Base's default (the immediate parent) would answer "chats" for every
|
|
38
|
+
session.
|
|
39
|
+
|
|
40
|
+
### `project_path_for(path)` <a id="method-i-project_path_for"></a> <a id="project_path_for-instance_method"></a>
|
|
41
|
+
The store groups sessions under a hash of the project directory that this gem
|
|
42
|
+
cannot reverse: it is not a plain SHA-256 of the path (tested directly against
|
|
43
|
+
real directories), and the store records the path nowhere else.
|
|
44
|
+
~/.gemini/projects.json is the map Gemini itself keeps —when it exists, this
|
|
45
|
+
reads it; when it does not, nil is the honest answer and `projects` reports
|
|
46
|
+
nothing rather than inventing a name from the hash.
|
|
47
|
+
|
|
48
|
+
### `project_paths()` <a id="method-i-project_paths"></a> <a id="project_paths-instance_method"></a>
|
|
49
|
+
Not documented.
|
|
50
|
+
|
|
51
|
+
### `started_at_for(path, stat)` <a id="method-i-started_at_for"></a> <a id="started_at_for-instance_method"></a>
|
|
52
|
+
UTC, unlike Codex and pi, whose rollout filenames use the local clock.
|
|
53
|
+
Verified rather than assumed: four real filenames match their own document's
|
|
54
|
+
startTime to the minute when read as UTC (12-42 against
|
|
55
|
+
2025-12-12T12:42:50.033Z), and would be three hours out as local time on the
|
|
56
|
+
machine this was written on.
|
|
57
|
+
|
|
58
|
+
Minute precision only — the document's startTime carries seconds, but reading
|
|
59
|
+
it would cost a parse per session, and Layer 2 is stat-only.
|
|
60
|
+
|
|
61
|
+
### `warnings()` <a id="method-i-warnings"></a> <a id="warnings-instance_method"></a>
|
|
62
|
+
The delta-log variant: tokentelemetry's parser of this same store handles
|
|
63
|
+
chats written as JSONL with a header line and `$set` deltas. No such file
|
|
64
|
+
exists here (zero .jsonl anywhere under the store), so this adapter reads the
|
|
65
|
+
verified .json spelling only — and says so where a user with the other
|
|
66
|
+
spelling will see it, rather than silently enumerating nothing for half their
|
|
67
|
+
sessions.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Class Agent::Sessions::Adapters::Grok <a id="class-Agent-Sessions-Adapters-Grok"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Adapters::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/adapters/grok.rb |
|
|
7
|
+
|
|
8
|
+
Grok Build (xAI). PROVISIONAL: ~/.grok does not exist on the machine this was
|
|
9
|
+
written on (2026-08-24), so every claim follows tokentelemetry's working
|
|
10
|
+
parser of the same store (resources/tokentelemetry, _scan_grok_sessions and
|
|
11
|
+
_grok_usage_from_unified_log) rather than observation.
|
|
12
|
+
|
|
13
|
+
A Grok session is a DIRECTORY, not a file:
|
|
14
|
+
~/.grok/sessions/<url-encoded cwd>/<session-uuid>/
|
|
15
|
+
summary.json chat_history.jsonl events.jsonl updates.jsonl
|
|
16
|
+
signals.json plan_mode.json subagents/<spawn-id>/meta.json
|
|
17
|
+
|
|
18
|
+
summary.json is what this adapter enumerates, because it is the record that
|
|
19
|
+
always exists and carries the session's own metadata. The transcript beside it
|
|
20
|
+
is what the reader reads, and `bytes` counts the whole directory — the same
|
|
21
|
+
choice Claude's adapter makes for its sidecar tree, and for the same reason:
|
|
22
|
+
those bytes belong to this session, and a `du` that ignored them would
|
|
23
|
+
disagree with the disk.
|
|
24
|
+
|
|
25
|
+
## Public Class Methods
|
|
26
|
+
### `reader_class()` <a id="method-c-reader_class"></a> <a id="reader_class-class_method"></a>
|
|
27
|
+
Not documented.
|
|
28
|
+
|
|
29
|
+
## Public Instance Methods
|
|
30
|
+
### `bytes_for(path, stat)` <a id="method-i-bytes_for"></a> <a id="bytes_for-instance_method"></a>
|
|
31
|
+
The whole session directory, not just summary.json: the transcript and every
|
|
32
|
+
sibling log live in it.
|
|
33
|
+
|
|
34
|
+
### `decode_project(name)` <a id="method-i-decode_project"></a> <a id="decode_project-instance_method"></a>
|
|
35
|
+
Not documented.
|
|
36
|
+
|
|
37
|
+
### `encode_project(dir)` <a id="method-i-encode_project"></a> <a id="encode_project-instance_method"></a>
|
|
38
|
+
Not documented.
|
|
39
|
+
|
|
40
|
+
### `project_dir_name(path)` <a id="method-i-project_dir_name"></a> <a id="project_dir_name-instance_method"></a>
|
|
41
|
+
Not documented.
|
|
42
|
+
|
|
43
|
+
### `project_path_for(path)` <a id="method-i-project_path_for"></a> <a id="project_path_for-instance_method"></a>
|
|
44
|
+
The project bucket is a URL-encoded absolute path (tokentelemetry unquotes
|
|
45
|
+
it), so unlike Claude's and pi's dash encodings this one is losslessly
|
|
46
|
+
reversible. summary.json's own info.cwd is preferred where readable, because a
|
|
47
|
+
recorded path beats a decoded directory name; the decode is the fallback, and
|
|
48
|
+
a good one.
|
|
49
|
+
|
|
50
|
+
### `session_id_from(path)` <a id="method-i-session_id_from"></a> <a id="session_id_from-instance_method"></a>
|
|
51
|
+
<sessions>/<url-encoded cwd>/<session-uuid>/summary.json — the id is the
|
|
52
|
+
directory holding the file, not the file's own basename, which is the constant
|
|
53
|
+
"summary".
|
|
54
|
+
|
|
55
|
+
### `started_at_for(path, stat)` <a id="method-i-started_at_for"></a> <a id="started_at_for-instance_method"></a>
|
|
56
|
+
summary.json carries the session's own clock; the file's mtime is only ever a
|
|
57
|
+
proxy for it. Both are ISO 8601 strings per the reference parser, with
|
|
58
|
+
created_at standing in when updated_at is absent.
|
|
59
|
+
|
|
60
|
+
### `updated_at_for(path, stat)` <a id="method-i-updated_at_for"></a> <a id="updated_at_for-instance_method"></a>
|
|
61
|
+
Not documented.
|
|
62
|
+
|
|
63
|
+
### `warnings()` <a id="method-i-warnings"></a> <a id="warnings-instance_method"></a>
|
|
64
|
+
Not documented.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Class Agent::Sessions::Adapters::Opencode <a id="class-Agent-Sessions-Adapters-Opencode"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Adapters::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/adapters/opencode.rb |
|
|
7
|
+
|
|
8
|
+
## Constants
|
|
9
|
+
### `DATABASE_GLOB` <a id="constant-DATABASE_GLOB"></a> <a id="DATABASE_GLOB-constant"></a>
|
|
10
|
+
opencode names its database per release channel — opencode.db,
|
|
11
|
+
opencode-stable.db — so the filename is a glob, not a constant (tokentelemetry
|
|
12
|
+
globs the same pattern). Unverified here: this machine has only the plain
|
|
13
|
+
name.
|
|
14
|
+
|
|
15
|
+
### `SESSION_COLUMNS` <a id="constant-SESSION_COLUMNS"></a> <a id="SESSION_COLUMNS-constant"></a>
|
|
16
|
+
Not documented.
|
|
17
|
+
|
|
18
|
+
## Public Class Methods
|
|
19
|
+
### `reader_class()` <a id="method-c-reader_class"></a> <a id="reader_class-class_method"></a>
|
|
20
|
+
Not documented.
|
|
21
|
+
|
|
22
|
+
## Public Instance Methods
|
|
23
|
+
### `base_dir()` <a id="method-i-base_dir"></a> <a id="base_dir-instance_method"></a>
|
|
24
|
+
The declared default stands unless another candidate actually holds a
|
|
25
|
+
database. Falling back to it rather than to the first candidate that merely
|
|
26
|
+
exists keeps `where` printing a concrete, conventional path on a machine with
|
|
27
|
+
no opencode at all.
|
|
28
|
+
|
|
29
|
+
### `project_paths()` <a id="method-i-project_paths"></a> <a id="project_paths-instance_method"></a>
|
|
30
|
+
ORDER BY matches the sorted order Base guarantees, so `projects` output is
|
|
31
|
+
stable and diffable whichever adapter answers it. is_a?(String) excludes a row
|
|
32
|
+
whose directory is NULL (build_db_session's guard, same rule 2 container
|
|
33
|
+
check) rather than letting a literal nil sort in among real paths — "excluded,
|
|
34
|
+
not nil", matching Base's project_paths docstring. .uniq is needed on top of
|
|
35
|
+
SQL's own DISTINCT: SQLite's DISTINCT treats a BLOB and a byte-identical TEXT
|
|
36
|
+
value as different rows (confirmed directly — typeof reports "text" vs "blob"
|
|
37
|
+
for the same bytes even though the sqlite3 gem returns both to Ruby as String,
|
|
38
|
+
see build_db_session's comment), so without this a blob/text pair with
|
|
39
|
+
identical bytes would surface as two entries where Base's own
|
|
40
|
+
<code>.uniq.sort</code> would collapse them to one.
|
|
41
|
+
|
|
42
|
+
### `sessions()` <a id="method-i-sessions"></a> <a id="sessions-instance_method"></a>
|
|
43
|
+
Sessions are rows, not files, so the Base glob enumeration is replaced by a
|
|
44
|
+
deferred query: it runs at first consumption, and only if the database exists.
|
|
45
|
+
The existence check comes FIRST so machines without opencode never need
|
|
46
|
+
sqlite3 at all (design doc section 9).
|
|
47
|
+
|
|
48
|
+
Row order is deliberately unspecified: no ORDER BY, so rows arrive in whatever
|
|
49
|
+
order SQLite's own scan produces (rowid order, absent an index that would
|
|
50
|
+
change it) — unlike the other six adapters, which are path-sorted for free by
|
|
51
|
+
Dir.glob. Invisible today because nothing here sorts before Task 10 does its
|
|
52
|
+
own sort_by(&:updated_at); stated so a future caller of THIS method directly
|
|
53
|
+
does not come to depend on insertion order looking stable.
|
|
54
|
+
|
|
55
|
+
The gap between this check and the open below is a real TOCTOU window
|
|
56
|
+
—opencode could delete or migrate the file in between — but the open that
|
|
57
|
+
follows a vanished file raises SQLite3::CantOpenException (verified directly),
|
|
58
|
+
which each_session_row already turns into UnreadableStore. That is judged the
|
|
59
|
+
right answer, not a bug to special-case: unlike Base's own glob-then-stat race
|
|
60
|
+
(one file silently missing from a multi-file listing, so build_session's
|
|
61
|
+
rescue drops it and moves on), a vanished DATABASE is the store's only source
|
|
62
|
+
for every session, so there is nothing partial to return — "the store I just
|
|
63
|
+
confirmed exists is now unreadable" is what happened, and UnreadableStore says
|
|
64
|
+
exactly that.
|
|
65
|
+
|
|
66
|
+
Opens once per consumption, not once per instance: `sessions`,
|
|
67
|
+
`sessions_for_project` and `project_paths` each open, query and close their
|
|
68
|
+
own connection through each_session_row. That costs an extra open when a
|
|
69
|
+
caller uses more than one of the three, but keeps every method independently
|
|
70
|
+
correct rather than threading a shared handle through them — and
|
|
71
|
+
Enumerator.new's block does not even run until the RETURNED lazy enumerator is
|
|
72
|
+
consumed, so a caller that builds `sessions` and never touches it opens
|
|
73
|
+
nothing at all. Confirmed empirically (not just assumed from Enumerator's
|
|
74
|
+
docs) that the `ensure db&.close` inside each_session_row fires promptly
|
|
75
|
+
either way a caller can stop early —`.lazy.first(n)` and an external `each {
|
|
76
|
+
break }` both unwind the generator fiber immediately, before the outer call
|
|
77
|
+
returns — so a caller taking <code>sessions.first</code> never leaves a
|
|
78
|
+
connection open waiting for GC to reclaim the fiber.
|
|
79
|
+
|
|
80
|
+
### `sessions_for_project(dir)` <a id="method-i-sessions_for_project"></a> <a id="sessions_for_project-instance_method"></a>
|
|
81
|
+
The directory column holds the full recorded path, so filtering is a WHERE
|
|
82
|
+
clause instead of the Base read-and-compare loop.
|
|
83
|
+
|
|
84
|
+
### `verify()` <a id="method-i-verify"></a> <a id="verify-instance_method"></a>
|
|
85
|
+
Base checks the declared path literally, which gets a machine holding only a
|
|
86
|
+
channel-named database wrong twice: its "is this agent installed" gate sees no
|
|
87
|
+
declared layer and skips the store checks entirely, and the store check itself
|
|
88
|
+
would report :fail on a real store that is merely called something the
|
|
89
|
+
declaration did not predict.
|
|
90
|
+
|
|
91
|
+
Any file matching the glob satisfies the claim. The detail names what was
|
|
92
|
+
actually found, so a non-canonical filename is visible rather than merely
|
|
93
|
+
tolerated — the same reason detail_for prints a file count.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Class Agent::Sessions::Adapters::Pi <a id="class-Agent-Sessions-Adapters-Pi"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Adapters::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/adapters/pi.rb |
|
|
7
|
+
|
|
8
|
+
## Constants
|
|
9
|
+
### `FILENAME` <a id="constant-FILENAME"></a> <a id="FILENAME-constant"></a>
|
|
10
|
+
FILENAME's h{8} id specifically contradicts the one written source available:
|
|
11
|
+
design doc section 8.6 says pi's **entries** carry an 8-character hex id,
|
|
12
|
+
while the section 8 table gives the filename itself as <timestamp>_<uuid>.
|
|
13
|
+
Both cannot be right, and nothing on this machine can settle which one pi's
|
|
14
|
+
own encoder does. h{8} is what is implemented here; if a real file uses a full
|
|
15
|
+
uuid instead, this regex simply never matches it, and session_id_from below
|
|
16
|
+
falls back to the basename — a real but visibly non-canonical id, not a crash.
|
|
17
|
+
|
|
18
|
+
## Public Class Methods
|
|
19
|
+
### `reader_class()` <a id="method-c-reader_class"></a> <a id="reader_class-class_method"></a>
|
|
20
|
+
The reader shares this adapter's provisional standing: written against
|
|
21
|
+
tokentelemetry's parser of the same format, since this machine's pi store
|
|
22
|
+
holds no session files to observe. Its own header comment says what remains
|
|
23
|
+
unverified.
|
|
24
|
+
|
|
25
|
+
## Public Instance Methods
|
|
26
|
+
### `encode_project(dir)` <a id="method-i-encode_project"></a> <a id="encode_project-instance_method"></a>
|
|
27
|
+
Verified against nine real pi project directories found on this machine on
|
|
28
|
+
2026-08-05 (~/.pi/agent/sessions/--*--, empty of .jsonl but real encoder
|
|
29
|
+
output regardless — see the class comment above) — see
|
|
30
|
+
test_encode_project_round_trips_the_nine_real_pi_directories in
|
|
31
|
+
test/pi_adapter_test.rb. Design doc section 7 described this only as "wrap the
|
|
32
|
+
dashed cwd in double dashes," ambiguous on two points neither of us had
|
|
33
|
+
settled by observation. Both are now settled by real output rather than by
|
|
34
|
+
carrying Claude's rule over into pi's:
|
|
35
|
+
|
|
36
|
+
1. The dash count. Read literally — dash-encode the WHOLE cwd,
|
|
37
|
+
including its leading "/", then wrap that in "--" — an
|
|
38
|
+
absolute path would get THREE leading dashes. Real pi output
|
|
39
|
+
has TWO: the leading "/" is absorbed into the wrap rather than
|
|
40
|
+
separately encoded, matching the store's own "--*--/*.jsonl"
|
|
41
|
+
glob (above).
|
|
42
|
+
|
|
43
|
+
2. The character class. This used to read like Claude's "every
|
|
44
|
+
non-alphanumeric character becomes -" and that was WRONG: pi
|
|
45
|
+
preserves dots. Two of the nine real directories contain a
|
|
46
|
+
literal "." (a domain name in the path) unchanged, while the
|
|
47
|
+
"/" separators around it became "-". Claude has 45 project
|
|
48
|
+
directories on this same machine and not one contains a dot —
|
|
49
|
+
the two adapters' rules genuinely differ; they do not merely
|
|
50
|
+
happen to agree on every example seen before now.
|
|
51
|
+
|
|
52
|
+
What remains a guess: "_", spaces, and any other non-"/" separator never
|
|
53
|
+
appear in the nine real directories, so nothing here confirms whether pi
|
|
54
|
+
encodes them or preserves them too, the way it preserves ".". Do not widen
|
|
55
|
+
this gsub back into a character class without new evidence — that is exactly
|
|
56
|
+
the mistake being corrected here.
|
|
57
|
+
|
|
58
|
+
This directory-name encoding is what sessions_for_project falls back to when a
|
|
59
|
+
session's own header cwd cannot be read (see Base#encode_project) — pi's whole
|
|
60
|
+
safety net for project_path_for's still-unverified "cwd" header key
|
|
61
|
+
assumption. That fallback is now solid: a wrong "cwd" key degrades to accurate
|
|
62
|
+
name matching instead of two guesses compounding into silent failure.
|
|
63
|
+
|
|
64
|
+
Expects an absolute, expanded path; sessions_for_project expands first.
|
|
65
|
+
|
|
66
|
+
### `project_path_for(path)` <a id="method-i-project_path_for"></a> <a id="project_path_for-instance_method"></a>
|
|
67
|
+
pi publishes its format: one header line, then typed entries (design doc 8.6)
|
|
68
|
+
— which argues for staying TIGHTER than Claude's 25, whose cwd genuinely was
|
|
69
|
+
not on line 1 and whose format was never published. But "publishes a spec" is
|
|
70
|
+
not the same evidence as "measured against a real file," and this machine has
|
|
71
|
+
zero pi sessions to measure against. limit: 25 matches Claude's number not
|
|
72
|
+
because pi is assumed to behave like Claude, but because scan_jsonl_for_key
|
|
73
|
+
returns as soon as it finds a usable record: the width costs nothing while the
|
|
74
|
+
line-1 assumption holds, and is only ever paid on the one case this file
|
|
75
|
+
cannot rule out — a preamble pi does not document, the same way Claude's
|
|
76
|
+
kebab-case preamble was not documented either. The one real cost of going
|
|
77
|
+
wide: the predicate below is type-checked but not otherwise selective, so a
|
|
78
|
+
longer window is more exposure to a later, unrelated record that happens to
|
|
79
|
+
carry a String "cwd" of its own — a decoy shadowing pi's real one —a risk this
|
|
80
|
+
file cannot bound without a real session to look at.
|
|
81
|
+
|
|
82
|
+
The predicate is mandatory, not decoration. scan_jsonl_for_key stops at the
|
|
83
|
+
first record merely CARRYING the key, so without a value guard a record
|
|
84
|
+
holding "cwd": null shadows a later usable one permanently, and a non-String
|
|
85
|
+
cwd reaches project_paths' .uniq.sort and raises.
|
|
86
|
+
|
|
87
|
+
### `session_id_from(path)` <a id="method-i-session_id_from"></a> <a id="session_id_from-instance_method"></a>
|
|
88
|
+
Not documented.
|
|
89
|
+
|
|
90
|
+
### `started_at_for(path, stat)` <a id="method-i-started_at_for"></a> <a id="started_at_for-instance_method"></a>
|
|
91
|
+
The rescue is not optional. d{2} accepts 00-99, and Time.new raises
|
|
92
|
+
ArgumentError on month 13, minute 60 and friends. build_session scopes its own
|
|
93
|
+
rescue to File.stat so that a raising hook surfaces as the adapter bug it
|
|
94
|
+
usually is — but this hook raises on FILE DATA, and without the rescue one
|
|
95
|
+
malformed filename returns zero sessions from `sessions`, `project_paths` and
|
|
96
|
+
`for_project` alike, and exits the CLI with a raw backtrace that takes every
|
|
97
|
+
other agent's rows with it. That failure mode was measured in Task 4, against
|
|
98
|
+
Codex — pi has no real filenames of its own to reproduce it against, but the
|
|
99
|
+
mechanism (Time.new rejecting digits d{2} happily accepted) belongs to Ruby,
|
|
100
|
+
not to any one adapter's data, so the same rescue applies here.
|
|
101
|
+
|
|
102
|
+
Local, not UTC: copied from Codex's VERIFIED behaviour (its rollout filenames
|
|
103
|
+
use the local clock, confirmed against 360 real files). pi's is UNVERIFIED —
|
|
104
|
+
no real pi filename exists on this machine to check it against. If pi instead
|
|
105
|
+
publishes UTC filenames, every pi started_at is silently off by the machine's
|
|
106
|
+
UTC offset, with no signal that it happened. The test fixture's header
|
|
107
|
+
timestamp and filename timestamp deliberately disagree (see build_fixture in
|
|
108
|
+
test/pi_adapter_test.rb) so that a started_at_for which quietly fell back to
|
|
109
|
+
reading the header would be caught returning the wrong hour, rather than
|
|
110
|
+
passing by coincidence on a UTC machine.
|
|
111
|
+
|
|
112
|
+
The rescue wraps Time.new alone rather than the whole method. A method-scoped
|
|
113
|
+
rescue would also swallow an ArgumentError from a future signature change —
|
|
114
|
+
the commonest Ruby programming error — and silently fall back to
|
|
115
|
+
stat.birthtime for every pi session: a plausible-looking wrong started_at with
|
|
116
|
+
no signal, which is worse than a crash.
|
|
117
|
+
|
|
118
|
+
### `warnings()` <a id="method-i-warnings"></a> <a id="warnings-instance_method"></a>
|
|
119
|
+
pi's *session files* are still absent from this machine: all nine directories
|
|
120
|
+
under ~/.pi/agent/sessions (real pi output — see encode_project below) are
|
|
121
|
+
empty of .jsonl (2026-08-05). Everything about a session's CONTENT is
|
|
122
|
+
therefore still inference from design doc 8.6, not observation: the header's
|
|
123
|
+
"cwd" key (project_path_for below), which line it is on (the limit: argument
|
|
124
|
+
there), and whether a session's id segment is 8 hex characters or a full uuid
|
|
125
|
+
(FILENAME below). `warnings` below repeats the gist where a CLI user will
|
|
126
|
+
actually see it, gated on the store existing.
|
|
127
|
+
|
|
128
|
+
The directory NAMING scheme is a different story: it is real pi output, not a
|
|
129
|
+
guess — see encode_project's comment.
|
|
130
|
+
|
|
131
|
+
To check the remaining unverified points against a real session:
|
|
132
|
+
head -1 ~/.pi/agent/sessions/--*--/*.jsonl
|
|
133
|
+
|
|
134
|
+
and look for: which key actually holds the cwd (assumed "cwd"), which line it
|
|
135
|
+
is on (assumed line 1), and whether the id segment is 8 hex characters or a
|
|
136
|
+
full uuid (assumed 8 hex). A mismatch means fixing the matching line below and
|
|
137
|
+
the warning above it, not just the comment next to it.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Class Agent::Sessions::Adapters::Qwen <a id="class-Agent-Sessions-Adapters-Qwen"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Adapters::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/adapters/qwen.rb |
|
|
7
|
+
|
|
8
|
+
Qwen Code. PROVISIONAL: ~/.qwen does not exist on the machine this was written
|
|
9
|
+
on (2026-08-24), so every claim here comes from tokentelemetry's working
|
|
10
|
+
parser of the same store (resources/tokentelemetry, backend/main.py section 4)
|
|
11
|
+
rather than from observation — the same standing the pi reader carries, and
|
|
12
|
+
declared the same way.
|
|
13
|
+
|
|
14
|
+
Qwen is a Gemini CLI fork that kept Gemini's directory layout and adopted
|
|
15
|
+
Anthropic's message shape, which is why its store looks like ~/.gemini's while
|
|
16
|
+
its records read like Claude's.
|
|
17
|
+
|
|
18
|
+
## Public Class Methods
|
|
19
|
+
### `reader_class()` <a id="method-c-reader_class"></a> <a id="reader_class-class_method"></a>
|
|
20
|
+
Not documented.
|
|
21
|
+
|
|
22
|
+
## Public Instance Methods
|
|
23
|
+
### `project_path_for(path)` <a id="method-i-project_path_for"></a> <a id="project_path_for-instance_method"></a>
|
|
24
|
+
Unlike Gemini's, the project directory is reported as a name this gem cannot
|
|
25
|
+
decode into a path, so the recorded cwd inside the file is the only source —
|
|
26
|
+
the same position Claude and pi are in. The predicate is mandatory:
|
|
27
|
+
scan_jsonl_for_key stops at the first record merely CARRYING the key, so
|
|
28
|
+
without it a record holding "cwd": null shadows a later usable one
|
|
29
|
+
permanently.
|
|
30
|
+
|
|
31
|
+
### `warnings()` <a id="method-i-warnings"></a> <a id="warnings-instance_method"></a>
|
|
32
|
+
Not documented.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Module Agent::Sessions::Adapters <a id="module-Agent-Sessions-Adapters"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Defined in** | lib/agent/sessions/adapters/pi.rb, lib/agent/sessions/adapters/amp.rb, lib/agent/sessions/adapters/base.rb, lib/agent/sessions/adapters/grok.rb, lib/agent/sessions/adapters/qwen.rb, lib/agent/sessions/adapters/codex.rb, lib/agent/sessions/adapters/claude.rb, lib/agent/sessions/adapters/cursor.rb, lib/agent/sessions/adapters/gemini.rb, lib/agent/sessions/adapters/copilot.rb, lib/agent/sessions/adapters/opencode.rb, lib/agent/sessions/adapters/cursor_ide.rb, lib/agent/sessions/adapters/enumeration.rb |
|