@bli-cockpit/cli 0.1.29 → 0.1.31
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/README.md +7 -1
- package/dist/adapters/attribution-core.js +70 -1
- package/dist/adapters/claude-attribution.js +6 -5
- package/dist/adapters/codex-attribution.js +6 -5
- package/dist/adapters/common.js +2 -5
- package/dist/adapters/raw-evidence.js +29 -8
- package/dist/backfill-lock.js +108 -0
- package/dist/commands/backfill.js +964 -0
- package/dist/commands/local-args.js +59 -1
- package/dist/commands/local.js +305 -8
- package/dist/commands/session-sync.js +22 -14
- package/dist/cursors/backfill-cursor.js +130 -0
- package/dist/evidence-upload-client.js +16 -0
- package/dist/upload.js +114 -25
- package/package.json +2 -2
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { getCollectorRuntimePaths, startLocalWorkContext, readLocalCollectorConfig } from "../local-state.js";
|
|
9
|
+
import { CODEX_SESSION_ATTRIBUTION_STATE_RANK, } from "@bli-cockpit/telemetry-core";
|
|
9
10
|
import { LocalUploadBlockedError, postCodexSessionReport, syncLocalAmbientEnvelope } from "../upload.js";
|
|
10
|
-
import {
|
|
11
|
+
import { CODEX_ATTRIBUTION_SCAN_WINDOW_SESSION_LIMIT, CODEX_ATTRIBUTION_SCAN_WINDOW_MINUTES, defaultCodexSessionDirs, scanAndAttributeCodexSessions, } from "../adapters/codex-attribution.js";
|
|
11
12
|
import { scanAndAttributeClaudeSessions } from "../adapters/claude-attribution.js";
|
|
12
13
|
import { RAW_EVIDENCE_DEFAULT_BYTE_BUDGET, RAW_EVIDENCE_DEFAULT_OBJECT_BUDGET } from "../adapters/raw-evidence.js";
|
|
13
14
|
import { CLAUDE_CURSOR_FILENAME, countStaleSessions, emptyRawEvidenceCursorState, readRawEvidenceCursor, recordSessionObservation, writeRawEvidenceCursor } from "../cursors/raw-evidence-cursor.js";
|
|
15
|
+
import { normalizeCollectionRoots } from "../root-normalization.js";
|
|
14
16
|
const CLAUDE_FIRST_RUN_BACKFILL_MINUTES = 14 * 24 * 60;
|
|
15
17
|
const CLAUDE_DAMP_GROWTH_BYTES = 256 * 1024;
|
|
16
18
|
const CLAUDE_DAMP_MAX_AGE_MS = 6 * 60 * 60 * 1000;
|
|
@@ -28,13 +30,16 @@ export async function runAttributedWorktreeSync(options) {
|
|
|
28
30
|
const now = new Date();
|
|
29
31
|
const homeDir = options.homeDir ?? os.homedir();
|
|
30
32
|
const paths = getCollectorRuntimePaths(options.homeDir);
|
|
31
|
-
const
|
|
33
|
+
const config = await readLocalCollectorConfig(paths).catch(() => null);
|
|
34
|
+
const claudeEnabled = config?.collect_claude_jsonl !== false;
|
|
35
|
+
const collectionRoots = fallbackCollectionRoots(config?.default_repo_paths ?? [], options.worktrees);
|
|
32
36
|
const codexAttribution = await scanAndAttributeCodexSessions({
|
|
33
37
|
sessionsDirs: defaultCodexSessionDirs(homeDir),
|
|
34
38
|
worktrees: options.worktrees,
|
|
35
39
|
now,
|
|
36
|
-
sinceMinutes:
|
|
37
|
-
limit:
|
|
40
|
+
sinceMinutes: CODEX_ATTRIBUTION_SCAN_WINDOW_MINUTES,
|
|
41
|
+
limit: CODEX_ATTRIBUTION_SCAN_WINDOW_SESSION_LIMIT,
|
|
42
|
+
collectionRoots,
|
|
38
43
|
});
|
|
39
44
|
// First run (D B.4 §8): without a Claude cursor yet, widen the window to 14
|
|
40
45
|
// days so the first sync captures retroactive history instead of only 24h.
|
|
@@ -45,6 +50,7 @@ export async function runAttributedWorktreeSync(options) {
|
|
|
45
50
|
projectsDir: path.join(homeDir, ".claude", "projects"),
|
|
46
51
|
worktrees: options.worktrees,
|
|
47
52
|
now,
|
|
53
|
+
collectionRoots,
|
|
48
54
|
sinceMinutes: firstRunBackfill
|
|
49
55
|
? CLAUDE_FIRST_RUN_BACKFILL_MINUTES
|
|
50
56
|
: undefined,
|
|
@@ -228,12 +234,7 @@ export async function runAttributedWorktreeSync(options) {
|
|
|
228
234
|
});
|
|
229
235
|
return { ok, outcomes, codexAttribution, claudeAttribution, summary };
|
|
230
236
|
}
|
|
231
|
-
const ATTRIBUTION_STATE_RANK =
|
|
232
|
-
attributed: 3,
|
|
233
|
-
ambiguous: 2,
|
|
234
|
-
unattributed: 1,
|
|
235
|
-
skipped: 0,
|
|
236
|
-
};
|
|
237
|
+
export const ATTRIBUTION_STATE_RANK = CODEX_SESSION_ATTRIBUTION_STATE_RANK;
|
|
237
238
|
function normalizeCodexResult(result) {
|
|
238
239
|
return {
|
|
239
240
|
source: "codex",
|
|
@@ -358,7 +359,8 @@ function buildAgentSessionReport(options) {
|
|
|
358
359
|
raw_evidence_pointer_id: priorDurablePointer,
|
|
359
360
|
upload_state: "reused_existing",
|
|
360
361
|
}
|
|
361
|
-
: result.state === "attributed"
|
|
362
|
+
: result.state === "attributed" ||
|
|
363
|
+
result.state === "attributed_fallback"
|
|
362
364
|
? { upload_state: "not_uploaded" }
|
|
363
365
|
: {}),
|
|
364
366
|
};
|
|
@@ -442,6 +444,7 @@ function buildAgentSessionSummary(options) {
|
|
|
442
444
|
const codex = {
|
|
443
445
|
scanned: options.codexAttribution.scanned_file_count,
|
|
444
446
|
attributed: options.codexAttribution.counts.attributed,
|
|
447
|
+
attributed_fallback: options.codexAttribution.counts.attributed_fallback,
|
|
445
448
|
ambiguous: options.codexAttribution.counts.ambiguous,
|
|
446
449
|
unattributed: options.codexAttribution.counts.unattributed,
|
|
447
450
|
skipped: options.codexAttribution.counts.skipped,
|
|
@@ -450,6 +453,7 @@ function buildAgentSessionSummary(options) {
|
|
|
450
453
|
const claude = {
|
|
451
454
|
scanned: options.claudeAttribution.scanned_session_count,
|
|
452
455
|
attributed: options.claudeAttribution.counts.attributed,
|
|
456
|
+
attributed_fallback: options.claudeAttribution.counts.attributed_fallback,
|
|
453
457
|
ambiguous: options.claudeAttribution.counts.ambiguous,
|
|
454
458
|
unattributed: options.claudeAttribution.counts.unattributed,
|
|
455
459
|
skipped: options.claudeAttribution.counts.skipped,
|
|
@@ -470,6 +474,7 @@ function buildAgentSessionSummary(options) {
|
|
|
470
474
|
return {
|
|
471
475
|
scanned: codex.scanned,
|
|
472
476
|
attributed: codex.attributed,
|
|
477
|
+
attributed_fallback: codex.attributed_fallback,
|
|
473
478
|
ambiguous: codex.ambiguous,
|
|
474
479
|
unattributed: codex.unattributed,
|
|
475
480
|
skipped: codex.skipped,
|
|
@@ -501,6 +506,7 @@ function emptyClaudeScan() {
|
|
|
501
506
|
disabled_reason: "claude_collection_disabled_by_config",
|
|
502
507
|
counts: {
|
|
503
508
|
attributed: 0,
|
|
509
|
+
attributed_fallback: 0,
|
|
504
510
|
ambiguous: 0,
|
|
505
511
|
unattributed: 0,
|
|
506
512
|
skipped: 0,
|
|
@@ -528,9 +534,11 @@ function worktreeInventoryForRepo(current, worktrees) {
|
|
|
528
534
|
branch: worktree.branch,
|
|
529
535
|
}));
|
|
530
536
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
537
|
+
function fallbackCollectionRoots(savedRoots, worktrees) {
|
|
538
|
+
return normalizeCollectionRoots([
|
|
539
|
+
...savedRoots,
|
|
540
|
+
...worktrees.map((worktree) => worktree.requested_path || worktree.repo_root),
|
|
541
|
+
]);
|
|
534
542
|
}
|
|
535
543
|
async function fileExists(filePath) {
|
|
536
544
|
const { stat } = await import("node:fs/promises");
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export const BACKFILL_CURSOR_FILENAME = "backfill.json";
|
|
5
|
+
export const BACKFILL_COMPLETION_MARKER_FILENAME = "backfill-complete.json";
|
|
6
|
+
export function emptyBackfillCursorState() {
|
|
7
|
+
return {
|
|
8
|
+
schema_version: "cockpit-backfill-cursor.v1",
|
|
9
|
+
updated_at: null,
|
|
10
|
+
sources: {
|
|
11
|
+
codex: emptySourceCursor(),
|
|
12
|
+
claude_code: emptySourceCursor(),
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export async function readBackfillCursor(paths) {
|
|
17
|
+
try {
|
|
18
|
+
const raw = JSON.parse(await fs.readFile(backfillCursorPath(paths), "utf8"));
|
|
19
|
+
return parseBackfillCursor(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return emptyBackfillCursorState();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function writeBackfillCursor(paths, state) {
|
|
26
|
+
const filePath = backfillCursorPath(paths);
|
|
27
|
+
await writePrivateJson(filePath, state);
|
|
28
|
+
}
|
|
29
|
+
export async function writeBackfillCompletionMarker(paths, marker) {
|
|
30
|
+
await writePrivateJson(backfillCompletionMarkerPath(paths), marker);
|
|
31
|
+
}
|
|
32
|
+
export function recordBackfillCursorObservations(cursor, observations, now) {
|
|
33
|
+
for (const observation of observations) {
|
|
34
|
+
const source = cursor.sources[observation.source] ?? emptySourceCursor();
|
|
35
|
+
const oldest = source.oldest_mtime_ms_processed;
|
|
36
|
+
if (oldest === null ||
|
|
37
|
+
observation.session_file_mtime_ms < oldest) {
|
|
38
|
+
source.oldest_mtime_ms_processed = observation.session_file_mtime_ms;
|
|
39
|
+
source.oldest_mtime_processed = observation.session_file_mtime;
|
|
40
|
+
}
|
|
41
|
+
source.state_counts[observation.state] =
|
|
42
|
+
(source.state_counts[observation.state] ?? 0) + 1;
|
|
43
|
+
source.reason_counts[observation.reason] =
|
|
44
|
+
(source.reason_counts[observation.reason] ?? 0) + 1;
|
|
45
|
+
cursor.sources[observation.source] = source;
|
|
46
|
+
}
|
|
47
|
+
cursor.updated_at = now.toISOString();
|
|
48
|
+
}
|
|
49
|
+
export function backfillCursorPath(paths) {
|
|
50
|
+
return path.join(paths.cursors_dir, BACKFILL_CURSOR_FILENAME);
|
|
51
|
+
}
|
|
52
|
+
export function backfillCompletionMarkerPath(paths) {
|
|
53
|
+
return path.join(paths.cursors_dir, BACKFILL_COMPLETION_MARKER_FILENAME);
|
|
54
|
+
}
|
|
55
|
+
function emptySourceCursor() {
|
|
56
|
+
return {
|
|
57
|
+
oldest_mtime_ms_processed: null,
|
|
58
|
+
oldest_mtime_processed: null,
|
|
59
|
+
state_counts: {},
|
|
60
|
+
reason_counts: {},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function parseBackfillCursor(value) {
|
|
64
|
+
if (!value || typeof value !== "object")
|
|
65
|
+
return emptyBackfillCursorState();
|
|
66
|
+
const record = value;
|
|
67
|
+
const sources = record["sources"];
|
|
68
|
+
const parsed = emptyBackfillCursorState();
|
|
69
|
+
parsed.updated_at = optionalString(record["updated_at"]);
|
|
70
|
+
if (sources && typeof sources === "object") {
|
|
71
|
+
const sourceRecord = sources;
|
|
72
|
+
parsed.sources.codex = parseSourceCursor(sourceRecord["codex"]);
|
|
73
|
+
parsed.sources.claude_code = parseSourceCursor(sourceRecord["claude_code"]);
|
|
74
|
+
}
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
function parseSourceCursor(value) {
|
|
78
|
+
if (!value || typeof value !== "object")
|
|
79
|
+
return emptySourceCursor();
|
|
80
|
+
const record = value;
|
|
81
|
+
const oldest = optionalNumber(record["oldest_mtime_ms_processed"]);
|
|
82
|
+
return {
|
|
83
|
+
oldest_mtime_ms_processed: oldest,
|
|
84
|
+
oldest_mtime_processed: optionalString(record["oldest_mtime_processed"]) ??
|
|
85
|
+
(oldest === null ? null : new Date(oldest).toISOString()),
|
|
86
|
+
state_counts: parseNumberRecord(record["state_counts"]),
|
|
87
|
+
reason_counts: parseNumberRecord(record["reason_counts"]),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function parseNumberRecord(value) {
|
|
91
|
+
if (!value || typeof value !== "object")
|
|
92
|
+
return {};
|
|
93
|
+
const out = {};
|
|
94
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
95
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0) {
|
|
96
|
+
out[key] = Math.floor(raw);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
async function writePrivateJson(filePath, value) {
|
|
102
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
103
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${crypto.randomUUID()}`;
|
|
104
|
+
const serialized = `${JSON.stringify(value, null, 2)}\n`;
|
|
105
|
+
let handle = null;
|
|
106
|
+
try {
|
|
107
|
+
handle = await fs.open(tempPath, "w", 0o600);
|
|
108
|
+
await handle.writeFile(serialized);
|
|
109
|
+
await handle.sync();
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
await handle?.close();
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
await fs.rename(tempPath, filePath);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
await fs.rm(tempPath, { force: true }).catch(() => undefined);
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
if (process.platform !== "win32") {
|
|
122
|
+
await fs.chmod(filePath, 0o600).catch(() => undefined);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function optionalString(value) {
|
|
126
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
127
|
+
}
|
|
128
|
+
function optionalNumber(value) {
|
|
129
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
130
|
+
}
|
|
@@ -12,6 +12,22 @@ export async function uploadRawEvidenceFilesChunked(options) {
|
|
|
12
12
|
const loaded = [];
|
|
13
13
|
const loadedByObjectKey = new Map();
|
|
14
14
|
for (const file of options.files) {
|
|
15
|
+
let stat;
|
|
16
|
+
try {
|
|
17
|
+
stat = await fs.stat(file.local_path);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
outcomes.push(failedOutcome(file, "file_read_failed"));
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (stat.size === 0) {
|
|
24
|
+
outcomes.push(failedOutcome(file, "empty_file"));
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (stat.size > RAW_EVIDENCE_UPLOAD_MAX_FILE_BYTES) {
|
|
28
|
+
outcomes.push(failedOutcome(file, "file_too_large"));
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
15
31
|
let bytes;
|
|
16
32
|
try {
|
|
17
33
|
bytes = await fs.readFile(file.local_path);
|
package/dist/upload.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentImageArtifactReportRequestSchema, EvidenceCompletenessPayloadSchema, TelemetryIngestEnvelopeSchema, TelemetryIngestEventDtoSchema, } from "@bli-cockpit/telemetry-core";
|
|
1
|
+
import { AgentImageArtifactReportRequestSchema, CODEX_SESSION_REPORT_MAX_SESSIONS, CodexSessionAttributionReportResponseSchema, EvidenceCompletenessPayloadSchema, TelemetryIngestEnvelopeSchema, TelemetryIngestEventDtoSchema, } from "@bli-cockpit/telemetry-core";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getCollectorRuntimePaths, LOCAL_COLLECTOR_VERSION, readLocalCollectorConfig, readLocalCollectorSessionFile, readLocalSessionReference, readLocalWorkContextForRepo, } from "./local-state.js";
|
|
4
4
|
import { runLocalSourceCollectors } from "./adapters/local-sources.js";
|
|
@@ -279,7 +279,7 @@ export async function syncLocalAmbientEnvelope(options = {}) {
|
|
|
279
279
|
*/
|
|
280
280
|
export async function postCodexSessionReport(options) {
|
|
281
281
|
if (options.sessions.length === 0) {
|
|
282
|
-
return
|
|
282
|
+
return emptyCodexSessionReportResult("no_sessions_observed");
|
|
283
283
|
}
|
|
284
284
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
285
285
|
if (!fetchImpl) {
|
|
@@ -291,7 +291,7 @@ export async function postCodexSessionReport(options) {
|
|
|
291
291
|
const sessionFile = await readLocalCollectorSessionFile(paths);
|
|
292
292
|
const session = await readLocalSessionReference(paths);
|
|
293
293
|
if (session.session_state !== "valid") {
|
|
294
|
-
return
|
|
294
|
+
return emptyCodexSessionReportResult("collector_not_paired");
|
|
295
295
|
}
|
|
296
296
|
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
297
297
|
const activeContext = await readLocalWorkContextForRepo(paths, repoRoot);
|
|
@@ -316,7 +316,7 @@ export async function postCodexSessionReport(options) {
|
|
|
316
316
|
});
|
|
317
317
|
}
|
|
318
318
|
catch {
|
|
319
|
-
return
|
|
319
|
+
return emptyCodexSessionReportResult("collector_not_ready");
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
/**
|
|
@@ -326,33 +326,122 @@ export async function postCodexSessionReport(options) {
|
|
|
326
326
|
*/
|
|
327
327
|
export async function reportCodexSessionAttributions(options) {
|
|
328
328
|
if (options.sessions.length === 0) {
|
|
329
|
-
return
|
|
329
|
+
return emptyCodexSessionReportResult("no_sessions_observed");
|
|
330
330
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
body: JSON.stringify({
|
|
339
|
-
schema_version: "ambient-codex-session-attributions.v1",
|
|
340
|
-
generated_at: options.generatedAt,
|
|
341
|
-
provenance: options.provenance,
|
|
342
|
-
sessions: options.sessions,
|
|
343
|
-
}),
|
|
331
|
+
const chunks = [];
|
|
332
|
+
const sessionChunks = chunkArray(options.sessions, CODEX_SESSION_REPORT_MAX_SESSIONS);
|
|
333
|
+
for (const [index, sessions] of sessionChunks.entries()) {
|
|
334
|
+
const chunk = await postCodexSessionAttributionChunk({
|
|
335
|
+
...options,
|
|
336
|
+
sessions,
|
|
337
|
+
batchIndex: index + 1,
|
|
344
338
|
});
|
|
345
|
-
|
|
346
|
-
|
|
339
|
+
chunks.push(chunk);
|
|
340
|
+
}
|
|
341
|
+
const failedCount = chunks.filter((chunk) => !chunk.posted).length;
|
|
342
|
+
const recordedCount = chunks.reduce((sum, chunk) => sum + chunk.recorded_count, 0);
|
|
343
|
+
return {
|
|
344
|
+
posted: failedCount === 0,
|
|
345
|
+
reason: failedCount === 0
|
|
346
|
+
? "recorded"
|
|
347
|
+
: chunks.find((chunk) => !chunk.posted)?.reason ?? "report_failed",
|
|
348
|
+
chunk_count: chunks.length,
|
|
349
|
+
recorded_count: recordedCount,
|
|
350
|
+
failed_count: failedCount,
|
|
351
|
+
chunks,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
async function postCodexSessionAttributionChunk(options) {
|
|
355
|
+
const maxAttempts = options.maxAttemptsPerRequest ?? 3;
|
|
356
|
+
const sleep = options.sleep ?? defaultReportSleep;
|
|
357
|
+
let lastStatus = null;
|
|
358
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
359
|
+
try {
|
|
360
|
+
const response = await options.fetchImpl(`${options.dashboardUrl}/api/ambient/codex-sessions`, {
|
|
361
|
+
method: "POST",
|
|
362
|
+
headers: {
|
|
363
|
+
"Authorization": `Bearer ${options.deviceToken}`,
|
|
364
|
+
"Content-Type": "application/json",
|
|
365
|
+
},
|
|
366
|
+
body: JSON.stringify({
|
|
367
|
+
schema_version: "ambient-codex-session-attributions.v1",
|
|
368
|
+
generated_at: options.generatedAt,
|
|
369
|
+
provenance: options.provenance,
|
|
370
|
+
sessions: options.sessions,
|
|
371
|
+
}),
|
|
372
|
+
});
|
|
373
|
+
lastStatus = response.status;
|
|
374
|
+
const body = await readResponseJson(response);
|
|
375
|
+
if (response.status === 404) {
|
|
376
|
+
return codexSessionReportChunkResult(options, {
|
|
377
|
+
posted: false,
|
|
378
|
+
reason: "codex_session_api_unavailable",
|
|
379
|
+
httpStatus: response.status,
|
|
380
|
+
recordedCount: 0,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (response.ok) {
|
|
384
|
+
const parsed = CodexSessionAttributionReportResponseSchema.safeParse(body);
|
|
385
|
+
return codexSessionReportChunkResult(options, {
|
|
386
|
+
posted: true,
|
|
387
|
+
reason: "recorded",
|
|
388
|
+
httpStatus: response.status,
|
|
389
|
+
recordedCount: parsed.success ? parsed.data.recorded_count : 0,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (response.status < 500 && response.status !== 429) {
|
|
393
|
+
return codexSessionReportChunkResult(options, {
|
|
394
|
+
posted: false,
|
|
395
|
+
reason: `report_failed_http_${response.status}`,
|
|
396
|
+
httpStatus: response.status,
|
|
397
|
+
recordedCount: 0,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
347
400
|
}
|
|
348
|
-
|
|
349
|
-
|
|
401
|
+
catch {
|
|
402
|
+
lastStatus = null;
|
|
350
403
|
}
|
|
351
|
-
|
|
404
|
+
if (attempt < maxAttempts)
|
|
405
|
+
await sleep(250 * attempt);
|
|
352
406
|
}
|
|
353
|
-
|
|
354
|
-
|
|
407
|
+
return codexSessionReportChunkResult(options, {
|
|
408
|
+
posted: false,
|
|
409
|
+
reason: lastStatus === null
|
|
410
|
+
? "report_network_error"
|
|
411
|
+
: `report_failed_http_${lastStatus}`,
|
|
412
|
+
httpStatus: lastStatus,
|
|
413
|
+
recordedCount: 0,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
function codexSessionReportChunkResult(options, result) {
|
|
417
|
+
return {
|
|
418
|
+
batch_index: options.batchIndex,
|
|
419
|
+
session_count: options.sessions.length,
|
|
420
|
+
posted: result.posted,
|
|
421
|
+
reason: result.reason,
|
|
422
|
+
http_status: result.httpStatus,
|
|
423
|
+
recorded_count: result.recordedCount,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function emptyCodexSessionReportResult(reason) {
|
|
427
|
+
return {
|
|
428
|
+
posted: false,
|
|
429
|
+
reason,
|
|
430
|
+
chunk_count: 0,
|
|
431
|
+
recorded_count: 0,
|
|
432
|
+
failed_count: 0,
|
|
433
|
+
chunks: [],
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function chunkArray(items, size) {
|
|
437
|
+
const chunks = [];
|
|
438
|
+
for (let offset = 0; offset < items.length; offset += size) {
|
|
439
|
+
chunks.push(items.slice(offset, offset + size));
|
|
355
440
|
}
|
|
441
|
+
return chunks;
|
|
442
|
+
}
|
|
443
|
+
function defaultReportSleep(milliseconds) {
|
|
444
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
356
445
|
}
|
|
357
446
|
export async function reportAgentImageArtifacts(options) {
|
|
358
447
|
const artifacts = agentArtifactsFromEvidence(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"test": "node dist/cli.js --help && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@bli-cockpit/telemetry-core": "0.1.
|
|
29
|
+
"@bli-cockpit/telemetry-core": "0.1.11"
|
|
30
30
|
}
|
|
31
31
|
}
|