@bli-cockpit/memory-mcp 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/transcript.d.ts +27 -1
- package/dist/hooks/transcript.js +54 -12
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The last exchange out of
|
|
2
|
+
* The last exchange out of an agent host's transcript (BLI-3580; Codex added
|
|
3
|
+
* by BLI-3729).
|
|
3
4
|
*
|
|
4
5
|
* The Stop hook is handed `transcript_path`: a JSONL file, one record per line,
|
|
5
6
|
* appended to for the whole session. What the save door wants is the LAST
|
|
@@ -19,6 +20,31 @@
|
|
|
19
20
|
* A record shape that is not recognised is skipped, never guessed at. The
|
|
20
21
|
* reader returns null rather than half an exchange: half a turn saved as a
|
|
21
22
|
* memory is a memory that says something the person did not.
|
|
23
|
+
*
|
|
24
|
+
* ## Two hosts, two record shapes (BLI-3729)
|
|
25
|
+
*
|
|
26
|
+
* `cockpit memory install` now registers the same three hooks with Codex, and
|
|
27
|
+
* Codex hands its Stop hook a `transcript_path` too — pointing at its own
|
|
28
|
+
* rollout JSONL, whose records are shaped differently:
|
|
29
|
+
*
|
|
30
|
+
* Claude Code { "message": { "role": "user", "content": [{ "type": "text", … }] } }
|
|
31
|
+
* Codex { "type": "response_item",
|
|
32
|
+
* "payload": { "type": "message", "role": "user",
|
|
33
|
+
* "content": [{ "type": "input_text", "text": … }] } }
|
|
34
|
+
*
|
|
35
|
+
* Both are read here, by the same backwards walk, because the alternative was a
|
|
36
|
+
* Codex Stop hook that fired on every turn and reported `transcript_no_exchange`
|
|
37
|
+
* every time — a hook that does nothing and says it did nothing. The Codex
|
|
38
|
+
* shape was captured from a real rollout file on 2026-09-05 rather than read
|
|
39
|
+
* off a doc, and the fixture in `hooks.test.ts` keeps the record structure
|
|
40
|
+
* verbatim with only the `text` values replaced (the real ones are somebody's
|
|
41
|
+
* conversation).
|
|
42
|
+
*
|
|
43
|
+
* `assistant` content blocks are `output_text` and `user` blocks are
|
|
44
|
+
* `input_text`; both are accepted alongside Claude's `text`. A `reasoning`,
|
|
45
|
+
* `custom_tool_call` or `event_msg` record contributes nothing, which is the
|
|
46
|
+
* same rule Claude's tool results already follow — a memory made of tool output
|
|
47
|
+
* is the transcript dump this repo refuses.
|
|
22
48
|
*/
|
|
23
49
|
/** How much of the file's end is read. Big enough for a long final turn. */
|
|
24
50
|
export declare const TAIL_BYTES: number;
|
package/dist/hooks/transcript.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The last exchange out of
|
|
2
|
+
* The last exchange out of an agent host's transcript (BLI-3580; Codex added
|
|
3
|
+
* by BLI-3729).
|
|
3
4
|
*
|
|
4
5
|
* The Stop hook is handed `transcript_path`: a JSONL file, one record per line,
|
|
5
6
|
* appended to for the whole session. What the save door wants is the LAST
|
|
@@ -19,6 +20,31 @@
|
|
|
19
20
|
* A record shape that is not recognised is skipped, never guessed at. The
|
|
20
21
|
* reader returns null rather than half an exchange: half a turn saved as a
|
|
21
22
|
* memory is a memory that says something the person did not.
|
|
23
|
+
*
|
|
24
|
+
* ## Two hosts, two record shapes (BLI-3729)
|
|
25
|
+
*
|
|
26
|
+
* `cockpit memory install` now registers the same three hooks with Codex, and
|
|
27
|
+
* Codex hands its Stop hook a `transcript_path` too — pointing at its own
|
|
28
|
+
* rollout JSONL, whose records are shaped differently:
|
|
29
|
+
*
|
|
30
|
+
* Claude Code { "message": { "role": "user", "content": [{ "type": "text", … }] } }
|
|
31
|
+
* Codex { "type": "response_item",
|
|
32
|
+
* "payload": { "type": "message", "role": "user",
|
|
33
|
+
* "content": [{ "type": "input_text", "text": … }] } }
|
|
34
|
+
*
|
|
35
|
+
* Both are read here, by the same backwards walk, because the alternative was a
|
|
36
|
+
* Codex Stop hook that fired on every turn and reported `transcript_no_exchange`
|
|
37
|
+
* every time — a hook that does nothing and says it did nothing. The Codex
|
|
38
|
+
* shape was captured from a real rollout file on 2026-09-05 rather than read
|
|
39
|
+
* off a doc, and the fixture in `hooks.test.ts` keeps the record structure
|
|
40
|
+
* verbatim with only the `text` values replaced (the real ones are somebody's
|
|
41
|
+
* conversation).
|
|
42
|
+
*
|
|
43
|
+
* `assistant` content blocks are `output_text` and `user` blocks are
|
|
44
|
+
* `input_text`; both are accepted alongside Claude's `text`. A `reasoning`,
|
|
45
|
+
* `custom_tool_call` or `event_msg` record contributes nothing, which is the
|
|
46
|
+
* same rule Claude's tool results already follow — a memory made of tool output
|
|
47
|
+
* is the transcript dump this repo refuses.
|
|
22
48
|
*/
|
|
23
49
|
import fs from "node:fs";
|
|
24
50
|
import { STOP_CHUNK_CHARS } from "./contract.js";
|
|
@@ -123,8 +149,8 @@ function parseJsonlTail(raw) {
|
|
|
123
149
|
function roleOf(record) {
|
|
124
150
|
if (!record)
|
|
125
151
|
return "";
|
|
126
|
-
const message = record
|
|
127
|
-
if (message
|
|
152
|
+
const message = messageOf(record);
|
|
153
|
+
if (message) {
|
|
128
154
|
const role = message["role"];
|
|
129
155
|
if (typeof role === "string")
|
|
130
156
|
return role;
|
|
@@ -133,18 +159,32 @@ function roleOf(record) {
|
|
|
133
159
|
return typeof type === "string" ? type : "";
|
|
134
160
|
}
|
|
135
161
|
/**
|
|
136
|
-
* The
|
|
137
|
-
* `
|
|
138
|
-
*
|
|
139
|
-
*
|
|
162
|
+
* The record's message envelope, whichever host wrote it: Claude Code's
|
|
163
|
+
* `message`, Codex's `payload`, or the record itself when it carries `role`
|
|
164
|
+
* directly. Returns null for a record with no envelope at all, which is how a
|
|
165
|
+
* Codex `reasoning` or `event_msg` line drops out without a special case.
|
|
166
|
+
*/
|
|
167
|
+
function messageOf(record) {
|
|
168
|
+
for (const key of ["message", "payload"]) {
|
|
169
|
+
const value = record[key];
|
|
170
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
/** The content block types that carry a person's or the model's own words. */
|
|
177
|
+
const TEXT_BLOCK_TYPES = new Set(["text", "input_text", "output_text"]);
|
|
178
|
+
/**
|
|
179
|
+
* The text of a record, whichever of the shapes it uses: a string `content`, an
|
|
180
|
+
* array of blocks whose `type` is one of `TEXT_BLOCK_TYPES`, or a bare `text`.
|
|
181
|
+
* A tool result or an image block contributes nothing, on purpose — a memory
|
|
182
|
+
* made of tool output is the transcript dump this repo refuses.
|
|
140
183
|
*/
|
|
141
184
|
function textOf(record) {
|
|
142
185
|
if (!record)
|
|
143
186
|
return "";
|
|
144
|
-
const
|
|
145
|
-
const source = message && typeof message === "object" && !Array.isArray(message)
|
|
146
|
-
? message
|
|
147
|
-
: record;
|
|
187
|
+
const source = messageOf(record) ?? record;
|
|
148
188
|
const content = source["content"] ?? source["text"];
|
|
149
189
|
if (typeof content === "string")
|
|
150
190
|
return content.trim();
|
|
@@ -159,7 +199,9 @@ function textOf(record) {
|
|
|
159
199
|
if (!block || typeof block !== "object")
|
|
160
200
|
continue;
|
|
161
201
|
const record_ = block;
|
|
162
|
-
if (record_["type"] !== "
|
|
202
|
+
if (typeof record_["type"] !== "string")
|
|
203
|
+
continue;
|
|
204
|
+
if (!TEXT_BLOCK_TYPES.has(record_["type"]))
|
|
163
205
|
continue;
|
|
164
206
|
const text = record_["text"];
|
|
165
207
|
if (typeof text === "string")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/memory-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "BLI Memory — an MCP server for the memory layer BLI owns (save, search, update, forget).",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"start": "node dist/index.js"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@bli-cockpit/telemetry-core": "0.1.
|
|
31
|
+
"@bli-cockpit/telemetry-core": "0.1.29",
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
33
33
|
"zod": "^4.3.6"
|
|
34
34
|
},
|