@bli-cockpit/cli 0.2.206 → 0.2.207

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.
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
21
21
  }
22
22
 
23
23
  if (command === "--version" || command === "-V" || command === "version") {
24
- writeLine(io?.stdout ?? process.stdout, "0.2.206");
24
+ writeLine(io?.stdout ?? process.stdout, "0.2.207");
25
25
  return 0;
26
26
  }
27
27
 
@@ -1,4 +1,5 @@
1
1
  import { writeLine } from "./cli-io.js";
2
+ import { readDescribedImageKeys } from "../image-description-state.js";
2
3
  import { CLAUDE_CURSOR_FILENAME, readRawEvidenceCursor, } from "../cursors/raw-evidence-cursor.js";
3
4
  import { fetchTranscriptObjects } from "../evidence-transcripts-client.js";
4
5
  import { DEFAULT_DASHBOARD_URL, getCollectorRuntimePaths, readLocalCollectorConfig, readLocalCollectorSessionFile, } from "../local-state.js";
@@ -28,13 +29,14 @@ export async function runSourcesCheck(command, io, now = new Date()) {
28
29
  codex: await readRawEvidenceCursor(paths),
29
30
  claude_code: await readRawEvidenceCursor(paths, { filename: CLAUDE_CURSOR_FILENAME, sessionsOnly: true }),
30
31
  };
32
+ const describedKeys = new Set(await readDescribedImageKeys(paths.state_dir));
31
33
  const report = emptyReport(command, remote, local.newer_count, local.file_read_failed + local.directory_read_failed);
32
34
  for (const file of local.files) {
33
35
  const judged = await judgeSource({
34
36
  file,
35
37
  remotes: remote.files.get(file.key) ?? [],
36
38
  cursorEntry: file.kind === "claude_subagent" ? null : (cursors[file.source].sessions[file.session_id] ?? null),
37
- facts: fileFacts(file),
39
+ facts: fileFacts(file, undefined, { describedKeys }),
38
40
  }).catch((error) => {
39
41
  report.local.unreadable += 1;
40
42
  console.error("[collector sources] a transcript could not be judged; left out of the answer", JSON.stringify({ reason: "file_unreadable", kind: file.kind, byte_size: file.byte_size, error_name: error?.name ?? "Error" }));
@@ -30,6 +30,8 @@ import path from "node:path";
30
30
  import { RAW_EVIDENCE_UPLOAD_MAX_FILE_BYTES, REDACTION_STUB_SHA256, } from "@bli-cockpit/telemetry-core";
31
31
  import { sanitizeTextEvidenceForUpload } from "../adapters/raw-evidence-sanitize.js";
32
32
  import { isSecretLikePath } from "../adapters/raw-evidence-keys.js";
33
+ import { IMAGE_HANDLING_MODE } from "@bli-cockpit/telemetry-core";
34
+ import { uploadFormOfTranscript } from "../image-description-session.js";
33
35
  export async function judgeSource(input) {
34
36
  const { file, remotes, cursorEntry } = input;
35
37
  if (remotes.length === 0)
@@ -79,7 +81,11 @@ export async function judgeSource(input) {
79
81
  * upload does (`raw-evidence-transcript-file.ts`): same sanitizer, same
80
82
  * secret-like-name rule, same byte cap.
81
83
  */
82
- export function fileFacts(file, maxBytes = RAW_EVIDENCE_UPLOAD_MAX_FILE_BYTES) {
84
+ export function fileFacts(file, maxBytes = RAW_EVIDENCE_UPLOAD_MAX_FILE_BYTES,
85
+ /** Tests only set `mode`. Production reads the one switch. `describedKeys` is the collector's described-image state. */
86
+ upload = {}) {
87
+ const mode = upload.mode ?? IMAGE_HANDLING_MODE;
88
+ const describedKeys = upload.describedKeys ?? new Set();
83
89
  let bytes = null;
84
90
  const read = () => {
85
91
  bytes ??= file.byte_size > maxBytes ? Promise.resolve(null) : fs.readFile(file.file_path);
@@ -96,9 +102,11 @@ export function fileFacts(file, maxBytes = RAW_EVIDENCE_UPLOAD_MAX_FILE_BYTES) {
96
102
  sanitized ??= read().then((buffer) => {
97
103
  if (!buffer)
98
104
  return null;
105
+ // Compare against what the uploader sends: in drop mode that is the slim form, not the disk bytes.
106
+ const uploadText = uploadFormOfTranscript(buffer.toString("utf8"), file.source === "codex" ? "codex" : "claude_code", mode, describedKeys);
99
107
  const result = sanitizeTextEvidenceForUpload({
100
- text: buffer.toString("utf8"),
101
- originalBytes: buffer,
108
+ text: uploadText,
109
+ originalBytes: uploadText === buffer.toString("utf8") ? buffer : Buffer.from(uploadText, "utf8"),
102
110
  redactedFields: [`${file.kind === "codex_rollout" ? "codex_jsonl" : file.kind === "claude_main" ? "claude_jsonl" : "claude_jsonl_sidecar"}.body`],
103
111
  secretLikeFileName: isSecretLikePath(path.basename(file.file_path)),
104
112
  });
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import { IMAGE_DESCRIBE_MAX_BYTES, IMAGE_HANDLING_MODE, base64Of, findEmbeddedImages, replaceImagesWithMarkers, } from "@bli-cockpit/telemetry-core";
24
24
  import { postImageToDescribeDoor } from "./image-describe-client.js";
25
- import { slimOrKeep } from "./slim/slim-transcript.js";
25
+ import { slimOrKeep, slimTranscript } from "./slim/slim-transcript.js";
26
26
  import { imageStateKey, readDescribedImageKeys, writeDescribedImageKeys, } from "./image-description-state.js";
27
27
  /** New images sent to the door in one sync (all worktrees together). */
28
28
  export const IMAGE_DESCRIBE_MAX_PER_RUN = 100;
@@ -151,6 +151,19 @@ export async function openImageDescriptions(options) {
151
151
  },
152
152
  };
153
153
  }
154
+ /**
155
+ * What the uploader sends for this transcript text, worked out offline (BLI-17013). Keep mode: the
156
+ * text as it is. Drop mode: every image whose key is in `describedKeys` becomes its marker, then
157
+ * the slim rules run, the same two steps as `prepareTranscript` above, with no describe-door call.
158
+ * `cockpit sources check` uses it so a complete upload is judged against the bytes actually uploaded.
159
+ */
160
+ export function uploadFormOfTranscript(text, source, mode, describedKeys) {
161
+ if (mode !== "describe_and_drop")
162
+ return text;
163
+ const images = findEmbeddedImages(text);
164
+ const replaced = replaceImagesWithMarkers(text, images, (image) => describedKeys.has(imageStateKey(image.sha256)));
165
+ return slimTranscript(replaced.text, source).text;
166
+ }
154
167
  /**
155
168
  * The image step for one build of an envelope. When a sync-wide budget is shared across worktrees
156
169
  * the session lives on it (one count budget and one state file for the whole sync); otherwise this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.206",
3
+ "version": "0.2.207",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {