@cleocode/adapters 2026.3.74 → 2026.4.0

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.
@@ -71,8 +71,8 @@ export declare class CodexHookProvider implements AdapterHookProvider {
71
71
  /**
72
72
  * Extract a plain-text transcript from Codex CLI session data.
73
73
  *
74
- * Reads the most recent session file under ~/.codex/ and extracts
75
- * turn text into a flat string for brain observation extraction.
74
+ * Reads the most recent JSON/JSONL session file under `~/.codex/`
75
+ * and returns its turns as a flat string for brain observation extraction.
76
76
  *
77
77
  * Returns null when no session data is found or on any read error.
78
78
  *
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/providers/codex/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAW/D;;;;;;;;;;;;;GAaG;AACH,qBAAa,iBAAkB,YAAW,mBAAmB;IAC3D,OAAO,CAAC,UAAU,CAAS;IAE3B;;;;;;OAMG;IACH,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAItD;;;;;;;;;;OAUG;IACG,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D;;;;;;;OAOG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C;;;OAGG;IACH,YAAY,IAAI,OAAO;IAIvB;;;OAGG;IACH,WAAW,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAI/C;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAmDrF"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/providers/codex/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAY/D;;;;;;;;;;;;;GAaG;AACH,qBAAa,iBAAkB,YAAW,mBAAmB;IAC3D,OAAO,CAAC,UAAU,CAAS;IAE3B;;;;;;OAMG;IACH,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAItD;;;;;;;;;;OAUG;IACG,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D;;;;;;;OAOG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C;;;OAGG;IACH,YAAY,IAAI,OAAO;IAIvB;;;OAGG;IACH,WAAW,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAI/C;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAGrF"}
@@ -12,8 +12,9 @@
12
12
  * @task T162
13
13
  * @epic T134
14
14
  */
15
- import { readdir, readFile } from 'node:fs/promises';
15
+ import { homedir } from 'node:os';
16
16
  import { join } from 'node:path';
17
+ import { readLatestTranscript } from '../shared/transcript-reader.js';
17
18
  /**
18
19
  * Mapping from Codex CLI native event names to CAAMP canonical event names.
19
20
  */
@@ -90,8 +91,8 @@ export class CodexHookProvider {
90
91
  /**
91
92
  * Extract a plain-text transcript from Codex CLI session data.
92
93
  *
93
- * Reads the most recent session file under ~/.codex/ and extracts
94
- * turn text into a flat string for brain observation extraction.
94
+ * Reads the most recent JSON/JSONL session file under `~/.codex/`
95
+ * and returns its turns as a flat string for brain observation extraction.
95
96
  *
96
97
  * Returns null when no session data is found or on any read error.
97
98
  *
@@ -100,56 +101,7 @@ export class CodexHookProvider {
100
101
  * @task T162 @epic T134
101
102
  */
102
103
  async getTranscript(_sessionId, _projectDir) {
103
- try {
104
- const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? '/root';
105
- const codexDir = join(homeDir, '.codex');
106
- let allFiles = [];
107
- try {
108
- const entries = await readdir(codexDir, { withFileTypes: true });
109
- for (const entry of entries) {
110
- if (!entry.isFile())
111
- continue;
112
- const name = entry.name;
113
- if (name.endsWith('.json') || name.endsWith('.jsonl')) {
114
- allFiles.push(join(codexDir, name));
115
- }
116
- }
117
- }
118
- catch {
119
- return null;
120
- }
121
- if (allFiles.length === 0)
122
- return null;
123
- // Sort descending by filename (timestamps in filenames sort naturally)
124
- allFiles = allFiles.sort((a, b) => b.localeCompare(a));
125
- const mostRecent = allFiles[0];
126
- if (!mostRecent)
127
- return null;
128
- const raw = await readFile(mostRecent, 'utf-8');
129
- const turns = [];
130
- // Support both JSONL (one JSON per line) and JSON array formats
131
- const lines = raw.split('\n').filter((l) => l.trim());
132
- for (const line of lines) {
133
- try {
134
- const entry = JSON.parse(line);
135
- const role = entry.role;
136
- const content = entry.content;
137
- if (role === 'assistant' && typeof content === 'string') {
138
- turns.push(`assistant: ${content}`);
139
- }
140
- else if (role === 'user' && typeof content === 'string') {
141
- turns.push(`user: ${content}`);
142
- }
143
- }
144
- catch {
145
- // Skip malformed lines
146
- }
147
- }
148
- return turns.length > 0 ? turns.join('\n') : null;
149
- }
150
- catch {
151
- return null;
152
- }
104
+ return readLatestTranscript(join(homedir(), '.codex'));
153
105
  }
154
106
  }
155
107
  //# sourceMappingURL=hooks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/providers/codex/hooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/providers/codex/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAEtE;;GAEG;AACH,MAAM,eAAe,GAA2B;IAC9C,YAAY,EAAE,cAAc;IAC5B,YAAY,EAAE,kBAAkB;IAChC,gBAAgB,EAAE,MAAM;CACzB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,iBAAiB;IACpB,UAAU,GAAG,KAAK,CAAC;IAE3B;;;;;;OAMG;IACH,gBAAgB,CAAC,aAAqB;QACpC,OAAO,eAAe,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC;IAChD,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,mBAAmB,CAAC,WAAmB;QAC3C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IACzB,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,qBAAqB;QACzB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,YAAY;QACV,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED;;;OAGG;IACH,WAAW;QACT,OAAO,EAAE,GAAG,eAAe,EAAE,CAAC;IAChC,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,aAAa,CAAC,UAAkB,EAAE,WAAmB;QACzD,OAAO,oBAAoB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;IACzD,CAAC;CACF"}
@@ -78,8 +78,8 @@ export declare class GeminiCliHookProvider implements AdapterHookProvider {
78
78
  /**
79
79
  * Extract a plain-text transcript from Gemini CLI session data.
80
80
  *
81
- * Reads the most recent session file under ~/.gemini/ and extracts
82
- * turn text into a flat string for brain observation extraction.
81
+ * Reads the most recent JSON/JSONL session file under `~/.gemini/`
82
+ * and returns its turns as a flat string for brain observation extraction.
83
83
  *
84
84
  * Returns null when no session data is found or on any read error.
85
85
  *
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini-cli/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAkB/D;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAsB,YAAW,mBAAmB;IAC/D,OAAO,CAAC,UAAU,CAAS;IAE3B;;;;;;OAMG;IACH,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAItD;;;;;;;;;;OAUG;IACG,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D;;;;;;;OAOG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C;;;OAGG;IACH,YAAY,IAAI,OAAO;IAIvB;;;OAGG;IACH,WAAW,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAI/C;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAmDrF"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini-cli/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAmB/D;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAsB,YAAW,mBAAmB;IAC/D,OAAO,CAAC,UAAU,CAAS;IAE3B;;;;;;OAMG;IACH,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAItD;;;;;;;;;;OAUG;IACG,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D;;;;;;;OAOG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C;;;OAGG;IACH,YAAY,IAAI,OAAO;IAIvB;;;OAGG;IACH,WAAW,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAI/C;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAGrF"}
@@ -19,8 +19,9 @@
19
19
  * @task T161
20
20
  * @epic T134
21
21
  */
22
- import { readdir, readFile } from 'node:fs/promises';
22
+ import { homedir } from 'node:os';
23
23
  import { join } from 'node:path';
24
+ import { readLatestTranscript } from '../shared/transcript-reader.js';
24
25
  /**
25
26
  * Mapping from Gemini CLI native event names to CAAMP canonical event names.
26
27
  */
@@ -104,8 +105,8 @@ export class GeminiCliHookProvider {
104
105
  /**
105
106
  * Extract a plain-text transcript from Gemini CLI session data.
106
107
  *
107
- * Reads the most recent session file under ~/.gemini/ and extracts
108
- * turn text into a flat string for brain observation extraction.
108
+ * Reads the most recent JSON/JSONL session file under `~/.gemini/`
109
+ * and returns its turns as a flat string for brain observation extraction.
109
110
  *
110
111
  * Returns null when no session data is found or on any read error.
111
112
  *
@@ -114,56 +115,7 @@ export class GeminiCliHookProvider {
114
115
  * @task T161 @epic T134
115
116
  */
116
117
  async getTranscript(_sessionId, _projectDir) {
117
- try {
118
- const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? '/root';
119
- const geminiDir = join(homeDir, '.gemini');
120
- let allFiles = [];
121
- try {
122
- const entries = await readdir(geminiDir, { withFileTypes: true });
123
- for (const entry of entries) {
124
- if (!entry.isFile())
125
- continue;
126
- const name = entry.name;
127
- if (name.endsWith('.json') || name.endsWith('.jsonl')) {
128
- allFiles.push(join(geminiDir, name));
129
- }
130
- }
131
- }
132
- catch {
133
- return null;
134
- }
135
- if (allFiles.length === 0)
136
- return null;
137
- // Sort descending by filename (timestamps in filenames sort naturally)
138
- allFiles = allFiles.sort((a, b) => b.localeCompare(a));
139
- const mostRecent = allFiles[0];
140
- if (!mostRecent)
141
- return null;
142
- const raw = await readFile(mostRecent, 'utf-8');
143
- const turns = [];
144
- // Support both JSONL (one JSON per line) and JSON array formats
145
- const lines = raw.split('\n').filter((l) => l.trim());
146
- for (const line of lines) {
147
- try {
148
- const entry = JSON.parse(line);
149
- const role = entry.role;
150
- const content = entry.content;
151
- if (role === 'assistant' && typeof content === 'string') {
152
- turns.push(`assistant: ${content}`);
153
- }
154
- else if (role === 'user' && typeof content === 'string') {
155
- turns.push(`user: ${content}`);
156
- }
157
- }
158
- catch {
159
- // Skip malformed lines
160
- }
161
- }
162
- return turns.length > 0 ? turns.join('\n') : null;
163
- }
164
- catch {
165
- return null;
166
- }
118
+ return readLatestTranscript(join(homedir(), '.gemini'));
167
119
  }
168
120
  }
169
121
  //# sourceMappingURL=hooks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/providers/gemini-cli/hooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/providers/gemini-cli/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAEtE;;GAEG;AACH,MAAM,oBAAoB,GAA2B;IACnD,YAAY,EAAE,cAAc;IAC5B,UAAU,EAAE,YAAY;IACxB,YAAY,EAAE,aAAa;IAC3B,gBAAgB,EAAE,YAAY;IAC9B,UAAU,EAAE,YAAY;IACxB,WAAW,EAAE,WAAW;IACxB,QAAQ,EAAE,aAAa;IACvB,SAAS,EAAE,YAAY;IACvB,UAAU,EAAE,aAAa;IACzB,YAAY,EAAE,cAAc;CAC7B,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,qBAAqB;IACxB,UAAU,GAAG,KAAK,CAAC;IAE3B;;;;;;OAMG;IACH,gBAAgB,CAAC,aAAqB;QACpC,OAAO,oBAAoB,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC;IACrD,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,mBAAmB,CAAC,WAAmB;QAC3C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IACzB,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,qBAAqB;QACzB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,YAAY;QACV,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED;;;OAGG;IACH,WAAW;QACT,OAAO,EAAE,GAAG,oBAAoB,EAAE,CAAC;IACrC,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,aAAa,CAAC,UAAkB,EAAE,WAAmB;QACzD,OAAO,oBAAoB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;IAC1D,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Shared transcript-reading utility for provider hook adapters.
3
+ *
4
+ * Several providers (Gemini CLI, Codex CLI) store session data in a
5
+ * flat directory of JSON/JSONL files using the same role/content schema.
6
+ * This module centralises the "find most-recent file, parse turns"
7
+ * logic to avoid duplicating it in each hook provider.
8
+ *
9
+ * Usage:
10
+ * ```ts
11
+ * import { readLatestTranscript } from '../shared/transcript-reader.js';
12
+ *
13
+ * async getTranscript(_sessionId: string, _projectDir: string) {
14
+ * return readLatestTranscript(join(homedir(), '.gemini'));
15
+ * }
16
+ * ```
17
+ *
18
+ * @task T161
19
+ * @epic T134
20
+ */
21
+ /**
22
+ * Read the most recent JSON or JSONL session file from `providerDir` and
23
+ * return its contents as a flat transcript string.
24
+ *
25
+ * Files are sorted in descending order by filename — this works naturally
26
+ * for providers that embed timestamps in filenames. The most recently named
27
+ * file is read first.
28
+ *
29
+ * Returns `null` when:
30
+ * - `providerDir` does not exist or cannot be read
31
+ * - No JSON/JSONL files are present
32
+ * - The most recent file contains no parseable turns
33
+ *
34
+ * @param providerDir - Absolute path to the provider's session directory
35
+ * (e.g. `~/.gemini` or `~/.codex`).
36
+ * @returns A plain-text transcript with lines of the form `role: content`,
37
+ * or `null` if no transcript could be extracted.
38
+ *
39
+ * @task T161
40
+ * @epic T134
41
+ */
42
+ export declare function readLatestTranscript(providerDir: string): Promise<string | null>;
43
+ //# sourceMappingURL=transcript-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-reader.d.ts","sourceRoot":"","sources":["../../../src/providers/shared/transcript-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoDH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA8BtF"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Shared transcript-reading utility for provider hook adapters.
3
+ *
4
+ * Several providers (Gemini CLI, Codex CLI) store session data in a
5
+ * flat directory of JSON/JSONL files using the same role/content schema.
6
+ * This module centralises the "find most-recent file, parse turns"
7
+ * logic to avoid duplicating it in each hook provider.
8
+ *
9
+ * Usage:
10
+ * ```ts
11
+ * import { readLatestTranscript } from '../shared/transcript-reader.js';
12
+ *
13
+ * async getTranscript(_sessionId: string, _projectDir: string) {
14
+ * return readLatestTranscript(join(homedir(), '.gemini'));
15
+ * }
16
+ * ```
17
+ *
18
+ * @task T161
19
+ * @epic T134
20
+ */
21
+ import { readdir, readFile } from 'node:fs/promises';
22
+ import { join } from 'node:path';
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Parse a raw JSONL or JSON session file into an array of transcript turns.
28
+ *
29
+ * Lines that are not valid JSON, or that lack a string `role` and string
30
+ * `content`, are silently skipped.
31
+ *
32
+ * @param raw - Raw file contents (UTF-8 string).
33
+ * @returns Array of `{ role, content }` pairs, in file order.
34
+ */
35
+ function parseTranscriptLines(raw) {
36
+ const turns = [];
37
+ const lines = raw.split('\n').filter((l) => l.trim());
38
+ for (const line of lines) {
39
+ try {
40
+ const entry = JSON.parse(line);
41
+ const role = entry.role;
42
+ const content = entry.content;
43
+ if (typeof role === 'string' && typeof content === 'string') {
44
+ turns.push({ role, content });
45
+ }
46
+ }
47
+ catch {
48
+ // Skip malformed lines
49
+ }
50
+ }
51
+ return turns;
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Public API
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Read the most recent JSON or JSONL session file from `providerDir` and
58
+ * return its contents as a flat transcript string.
59
+ *
60
+ * Files are sorted in descending order by filename — this works naturally
61
+ * for providers that embed timestamps in filenames. The most recently named
62
+ * file is read first.
63
+ *
64
+ * Returns `null` when:
65
+ * - `providerDir` does not exist or cannot be read
66
+ * - No JSON/JSONL files are present
67
+ * - The most recent file contains no parseable turns
68
+ *
69
+ * @param providerDir - Absolute path to the provider's session directory
70
+ * (e.g. `~/.gemini` or `~/.codex`).
71
+ * @returns A plain-text transcript with lines of the form `role: content`,
72
+ * or `null` if no transcript could be extracted.
73
+ *
74
+ * @task T161
75
+ * @epic T134
76
+ */
77
+ export async function readLatestTranscript(providerDir) {
78
+ let allFiles = [];
79
+ try {
80
+ const entries = await readdir(providerDir, { withFileTypes: true });
81
+ for (const entry of entries) {
82
+ if (!entry.isFile())
83
+ continue;
84
+ const name = entry.name;
85
+ if (name.endsWith('.json') || name.endsWith('.jsonl')) {
86
+ allFiles.push(join(providerDir, name));
87
+ }
88
+ }
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ if (allFiles.length === 0)
94
+ return null;
95
+ // Sort descending — timestamps in filenames sort naturally
96
+ allFiles = allFiles.sort((a, b) => b.localeCompare(a));
97
+ const mostRecent = allFiles[0];
98
+ if (!mostRecent)
99
+ return null;
100
+ try {
101
+ const raw = await readFile(mostRecent, 'utf-8');
102
+ const turns = parseTranscriptLines(raw);
103
+ return turns.length > 0 ? turns.map((t) => `${t.role}: ${t.content}`).join('\n') : null;
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ //# sourceMappingURL=transcript-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-reader.js","sourceRoot":"","sources":["../../../src/providers/shared/transcript-reader.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/adapters",
3
- "version": "2026.3.74",
3
+ "version": "2026.4.0",
4
4
  "description": "Unified provider adapters for CLEO (Claude Code, OpenCode, Cursor)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,8 +12,8 @@
12
12
  }
13
13
  },
14
14
  "dependencies": {
15
- "@cleocode/caamp": "^1.9.1",
16
- "@cleocode/contracts": "2026.3.74"
15
+ "@cleocode/caamp": "2026.4.0",
16
+ "@cleocode/contracts": "2026.4.0"
17
17
  },
18
18
  "license": "MIT",
19
19
  "engines": {
@@ -13,9 +13,10 @@
13
13
  * @epic T134
14
14
  */
15
15
 
16
- import { readdir, readFile } from 'node:fs/promises';
16
+ import { homedir } from 'node:os';
17
17
  import { join } from 'node:path';
18
18
  import type { AdapterHookProvider } from '@cleocode/contracts';
19
+ import { readLatestTranscript } from '../shared/transcript-reader.js';
19
20
 
20
21
  /**
21
22
  * Mapping from Codex CLI native event names to CAAMP canonical event names.
@@ -100,8 +101,8 @@ export class CodexHookProvider implements AdapterHookProvider {
100
101
  /**
101
102
  * Extract a plain-text transcript from Codex CLI session data.
102
103
  *
103
- * Reads the most recent session file under ~/.codex/ and extracts
104
- * turn text into a flat string for brain observation extraction.
104
+ * Reads the most recent JSON/JSONL session file under `~/.codex/`
105
+ * and returns its turns as a flat string for brain observation extraction.
105
106
  *
106
107
  * Returns null when no session data is found or on any read error.
107
108
  *
@@ -110,54 +111,6 @@ export class CodexHookProvider implements AdapterHookProvider {
110
111
  * @task T162 @epic T134
111
112
  */
112
113
  async getTranscript(_sessionId: string, _projectDir: string): Promise<string | null> {
113
- try {
114
- const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? '/root';
115
- const codexDir = join(homeDir, '.codex');
116
-
117
- let allFiles: string[] = [];
118
- try {
119
- const entries = await readdir(codexDir, { withFileTypes: true });
120
- for (const entry of entries) {
121
- if (!entry.isFile()) continue;
122
- const name = entry.name;
123
- if (name.endsWith('.json') || name.endsWith('.jsonl')) {
124
- allFiles.push(join(codexDir, name));
125
- }
126
- }
127
- } catch {
128
- return null;
129
- }
130
-
131
- if (allFiles.length === 0) return null;
132
-
133
- // Sort descending by filename (timestamps in filenames sort naturally)
134
- allFiles = allFiles.sort((a, b) => b.localeCompare(a));
135
- const mostRecent = allFiles[0];
136
- if (!mostRecent) return null;
137
-
138
- const raw = await readFile(mostRecent, 'utf-8');
139
- const turns: string[] = [];
140
-
141
- // Support both JSONL (one JSON per line) and JSON array formats
142
- const lines = raw.split('\n').filter((l) => l.trim());
143
- for (const line of lines) {
144
- try {
145
- const entry = JSON.parse(line) as Record<string, unknown>;
146
- const role = entry.role as string | undefined;
147
- const content = entry.content;
148
- if (role === 'assistant' && typeof content === 'string') {
149
- turns.push(`assistant: ${content}`);
150
- } else if (role === 'user' && typeof content === 'string') {
151
- turns.push(`user: ${content}`);
152
- }
153
- } catch {
154
- // Skip malformed lines
155
- }
156
- }
157
-
158
- return turns.length > 0 ? turns.join('\n') : null;
159
- } catch {
160
- return null;
161
- }
114
+ return readLatestTranscript(join(homedir(), '.codex'));
162
115
  }
163
116
  }
@@ -20,9 +20,10 @@
20
20
  * @epic T134
21
21
  */
22
22
 
23
- import { readdir, readFile } from 'node:fs/promises';
23
+ import { homedir } from 'node:os';
24
24
  import { join } from 'node:path';
25
25
  import type { AdapterHookProvider } from '@cleocode/contracts';
26
+ import { readLatestTranscript } from '../shared/transcript-reader.js';
26
27
 
27
28
  /**
28
29
  * Mapping from Gemini CLI native event names to CAAMP canonical event names.
@@ -114,8 +115,8 @@ export class GeminiCliHookProvider implements AdapterHookProvider {
114
115
  /**
115
116
  * Extract a plain-text transcript from Gemini CLI session data.
116
117
  *
117
- * Reads the most recent session file under ~/.gemini/ and extracts
118
- * turn text into a flat string for brain observation extraction.
118
+ * Reads the most recent JSON/JSONL session file under `~/.gemini/`
119
+ * and returns its turns as a flat string for brain observation extraction.
119
120
  *
120
121
  * Returns null when no session data is found or on any read error.
121
122
  *
@@ -124,54 +125,6 @@ export class GeminiCliHookProvider implements AdapterHookProvider {
124
125
  * @task T161 @epic T134
125
126
  */
126
127
  async getTranscript(_sessionId: string, _projectDir: string): Promise<string | null> {
127
- try {
128
- const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? '/root';
129
- const geminiDir = join(homeDir, '.gemini');
130
-
131
- let allFiles: string[] = [];
132
- try {
133
- const entries = await readdir(geminiDir, { withFileTypes: true });
134
- for (const entry of entries) {
135
- if (!entry.isFile()) continue;
136
- const name = entry.name;
137
- if (name.endsWith('.json') || name.endsWith('.jsonl')) {
138
- allFiles.push(join(geminiDir, name));
139
- }
140
- }
141
- } catch {
142
- return null;
143
- }
144
-
145
- if (allFiles.length === 0) return null;
146
-
147
- // Sort descending by filename (timestamps in filenames sort naturally)
148
- allFiles = allFiles.sort((a, b) => b.localeCompare(a));
149
- const mostRecent = allFiles[0];
150
- if (!mostRecent) return null;
151
-
152
- const raw = await readFile(mostRecent, 'utf-8');
153
- const turns: string[] = [];
154
-
155
- // Support both JSONL (one JSON per line) and JSON array formats
156
- const lines = raw.split('\n').filter((l) => l.trim());
157
- for (const line of lines) {
158
- try {
159
- const entry = JSON.parse(line) as Record<string, unknown>;
160
- const role = entry.role as string | undefined;
161
- const content = entry.content;
162
- if (role === 'assistant' && typeof content === 'string') {
163
- turns.push(`assistant: ${content}`);
164
- } else if (role === 'user' && typeof content === 'string') {
165
- turns.push(`user: ${content}`);
166
- }
167
- } catch {
168
- // Skip malformed lines
169
- }
170
- }
171
-
172
- return turns.length > 0 ? turns.join('\n') : null;
173
- } catch {
174
- return null;
175
- }
128
+ return readLatestTranscript(join(homedir(), '.gemini'));
176
129
  }
177
130
  }