@bli-cockpit/cli 0.2.149 → 0.2.154

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.
Files changed (46) hide show
  1. package/dist/adapters/local-sources.js +1 -0
  2. package/dist/adapters/raw-evidence-collection-state.js +1 -0
  3. package/dist/adapters/raw-evidence-facts.js +1 -0
  4. package/dist/adapters/raw-evidence-manifest.js +5 -0
  5. package/dist/adapters/raw-evidence-sanitize.js +82 -20
  6. package/dist/adapters/raw-evidence-snapshot-supersede.js +82 -0
  7. package/dist/adapters/raw-evidence-transcript-file.js +10 -0
  8. package/dist/agent-rules.js +3 -3
  9. package/dist/commands/agent-door.js +3 -0
  10. package/dist/commands/backfill-lock-wait.js +49 -0
  11. package/dist/commands/backfill-outside-roots.js +97 -0
  12. package/dist/commands/backfill-reasons.js +17 -0
  13. package/dist/commands/backfill-scan.js +8 -3
  14. package/dist/commands/backfill-scope.js +14 -1
  15. package/dist/commands/backfill-session-report.js +3 -0
  16. package/dist/commands/backfill-upload.js +6 -0
  17. package/dist/commands/backfill.js +11 -5
  18. package/dist/commands/cal-calendars-write.js +107 -0
  19. package/dist/commands/cal.js +29 -110
  20. package/dist/commands/docs.js +20 -4
  21. package/dist/commands/doctor-budget.js +69 -0
  22. package/dist/commands/doctor-lock-wait.js +12 -3
  23. package/dist/commands/doctor-pipeline-verdicts.js +2 -0
  24. package/dist/commands/doctor-pipeline.js +5 -2
  25. package/dist/commands/doctor.js +26 -4
  26. package/dist/commands/issue-directory.js +7 -0
  27. package/dist/commands/issue.js +23 -4
  28. package/dist/commands/local-arg-values.js +9 -0
  29. package/dist/commands/local-args-collector-setup.js +4 -2
  30. package/dist/commands/local-args-collector-work.js +9 -1
  31. package/dist/commands/local-help-commands.js +4 -1
  32. package/dist/commands/local-help.js +1 -1
  33. package/dist/commands/local.js +14 -0
  34. package/dist/commands/mail.js +20 -4
  35. package/dist/commands/memory-store.js +27 -10
  36. package/dist/commands/msg.js +25 -5
  37. package/dist/commands/public-root.js +1 -1
  38. package/dist/commands/read-cache.js +115 -0
  39. package/dist/commands/settings.js +17 -2
  40. package/dist/commands/tower-command.js +4 -0
  41. package/dist/cursors/raw-evidence-cursor.js +14 -0
  42. package/dist/tower-client.js +31 -3
  43. package/dist/upload-envelope-build.js +9 -0
  44. package/dist/upload-sync.js +10 -22
  45. package/dist/warm-tower-fetch.js +83 -0
  46. package/package.json +5 -5
@@ -1,23 +1,3 @@
1
- /**
2
- * `syncLocalAmbientEnvelope`: one ambient sync, end to end.
3
- *
4
- * 1. Assemble the envelope — or record why this machine cannot upload at all
5
- * and rethrow, so `cockpit status` names the blocker.
6
- * 2. Deliver the raw evidence objects, skipping anything inside its delivery
7
- * backoff window (`upload-evidence-delivery.ts`).
8
- * 3. Reconcile the envelope with what actually became durable, POST it to
9
- * ingest, and refuse to believe a receipt we cannot read
10
- * (`upload-ingest-receipt.ts`).
11
- * 4. Advance the content cursor, report any agent images, and write the ledger
12
- * row — a retry when a later sync could still rescue the gap, a named
13
- * blocker when it never can, nothing extra when the sync was clean.
14
- * 5. On any failure, spool a retry that carries its own reason.
15
- *
16
- * The whole file is built around one rule from the fleet contract: a sync that
17
- * did not collect everything must never read green. Objects held by backoff are
18
- * reported as named failures, permanent rejections are logged and blocked
19
- * rather than silently retried forever, and the ledger row always says why.
20
- */
21
1
  import { getCollectorRuntimePaths } from "./local-state.js";
22
2
  import { uploadRawEvidenceFilesChunked, } from "./evidence-upload-client.js";
23
3
  import { markObjectCommitted, readRawEvidenceCursor, writeRawEvidenceCursor, } from "./cursors/raw-evidence-cursor.js";
@@ -80,7 +60,7 @@ export async function syncLocalAmbientEnvelope(options = {}) {
80
60
  built,
81
61
  envelope: applyRawEvidenceUploadOutcomes(built.envelope, uploadOutcomes),
82
62
  });
83
- advanceRawEvidenceCursor(cursor, uploadOutcomes, attemptedAt);
63
+ advanceRawEvidenceCursor(cursor, uploadOutcomes, attemptedAt, built.raw_evidence_upload_files);
84
64
  await writeRawEvidenceCursor(paths, cursor);
85
65
  await reportAgentImageArtifacts({
86
66
  fetchImpl,
@@ -221,17 +201,25 @@ async function buildEnvelopeOrRecordBlocker(paths, options, cursorObjects, attem
221
201
  * bytes instead of re-uploading them. A failed upload is deliberately absent:
222
202
  * recording it would make the next sync skip a file that never landed.
223
203
  */
224
- function advanceRawEvidenceCursor(cursor, outcomes, attemptedAt) {
204
+ function advanceRawEvidenceCursor(cursor, outcomes, attemptedAt, files = []) {
205
+ // BLI-11088: a transcript's source key and snapshot facts ride into the
206
+ // cursor, so the next sync can prove its snapshot rebuilds this one.
207
+ const snapshotByHash = new Map(files.filter((file) => file.snapshot && file.source_key && file.pointer.content_hash_sha256)
208
+ .map((file) => [file.pointer.content_hash_sha256, file]));
225
209
  for (const outcome of outcomes) {
226
210
  if (outcome.upload_state === "upload_failed")
227
211
  continue;
228
212
  const contentHash = outcome.pointer.content_hash_sha256;
229
213
  if (!contentHash)
230
214
  continue;
215
+ const source = snapshotByHash.get(contentHash);
231
216
  markObjectCommitted(cursor, contentHash, {
232
217
  object_key: outcome.object_key,
233
218
  byte_size: outcome.pointer.byte_size ?? 0,
234
219
  committed_at: attemptedAt,
220
+ ...(source
221
+ ? { source_key: source.source_key, checkpoints: source.snapshot.checkpoints }
222
+ : {}),
235
223
  });
236
224
  }
237
225
  cursor.updated_at = attemptedAt;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * A CLI READ, ROUTED THROUGH THE RESIDENT MEMORY DAEMON'S WARM CONNECTION
3
+ * (BLI-10746).
4
+ *
5
+ * Every `cockpit` process today opens its own TCP+TLS connection to Tower —
6
+ * measured from Vietnam, about 420 ms of the ~1,700 ms `work list` takes is
7
+ * exactly that setup, paid fresh on every single command. `@bli-cockpit/
8
+ * memory-mcp`'s resident daemon already holds one connection open across
9
+ * turns for recall (BLI-10741); this module is "reuse that for every other
10
+ * read too" — a GET (or the one POST read this repo has, `memory search`)
11
+ * asks the daemon to replay it on its own dispatcher instead of opening a new
12
+ * socket.
13
+ *
14
+ * **Fallback is the whole safety argument.** A daemon that is absent, wedged,
15
+ * refuses, times out, or answers something this cannot parse means exactly
16
+ * one thing here: go the way this command always went. `isWarmRoutableRead`
17
+ * decides which requests are even eligible — writes NEVER take this path, so
18
+ * there is no daemon-retry-of-a-write hazard to reason about.
19
+ *
20
+ * **Auth never changes hands.** The caller's own `authorization` header
21
+ * travels inside `init.headers` exactly as `towerRequest` already built it;
22
+ * the daemon forwards it unread (see `@bli-cockpit/memory-mcp`'s
23
+ * `daemon/http-proxy.ts`). This module never reads the device token either.
24
+ */
25
+ import os from "node:os";
26
+ import { memoryDaemonEndpoint } from "@bli-cockpit/telemetry-core";
27
+ import { askMemoryDaemonHttp, } from "@bli-cockpit/telemetry-core/node-daemon-http-client";
28
+ /**
29
+ * The only POST this repo treats as a read for warm-routing purposes
30
+ * (`cockpit memory search`, `POST /api/memory/search`). Every other read is a
31
+ * GET; a caller names itself with the same `label` it already logs under.
32
+ */
33
+ const WARM_ROUTABLE_POST_LABELS = new Set(["memory search"]);
34
+ /** GET is always eligible; a small named allowlist covers the one read that is not. */
35
+ export function isWarmRoutableRead(method, label) {
36
+ return method === "GET" || WARM_ROUTABLE_POST_LABELS.has(label);
37
+ }
38
+ /** The whole local-socket-plus-daemon's-own-fetch budget. Comfortably under any `READ_DEADLINE_MS` a command sets. */
39
+ export const WARM_FETCH_DEADLINE_MS = 10_000;
40
+ /**
41
+ * Tries the daemon; returns `null` on ANY reason it could not, so the caller
42
+ * falls back to a direct request. Never throws.
43
+ */
44
+ export async function tryWarmTowerFetch(url, init, log, deps = {}) {
45
+ const endpoint = memoryDaemonEndpoint({
46
+ homeDir: deps.homeDir ?? os.homedir(),
47
+ ...(deps.platform ? { platform: deps.platform } : {}),
48
+ username: deps.username ?? safeUsername(),
49
+ });
50
+ if (endpoint.kind === "unix" && endpoint.addressTooLong) {
51
+ log(`[tower client] warm route skipped ${JSON.stringify({ reason: "socket_path_too_long" })}`);
52
+ return null;
53
+ }
54
+ const result = await askMemoryDaemonHttp({
55
+ address: endpoint.address,
56
+ deadlineMs: deps.deadlineMs ?? WARM_FETCH_DEADLINE_MS,
57
+ ...(deps.connectImpl ? { connectImpl: deps.connectImpl } : {}),
58
+ ...(deps.now ? { now: deps.now } : {}),
59
+ request: {
60
+ method: init.method,
61
+ url,
62
+ headers: init.headers,
63
+ ...(init.body === undefined ? {} : { body: init.body }),
64
+ },
65
+ });
66
+ if (!result.ok) {
67
+ log(`[tower client] warm route missed ${JSON.stringify({ reason: result.reason, elapsed_ms: result.elapsedMs })}`);
68
+ return null;
69
+ }
70
+ log(`[tower client] warm route used ${JSON.stringify({ elapsed_ms: result.elapsedMs })}`);
71
+ return new Response(result.answer.body, {
72
+ status: result.answer.status,
73
+ headers: result.answer.headers,
74
+ });
75
+ }
76
+ function safeUsername() {
77
+ try {
78
+ return os.userInfo().username;
79
+ }
80
+ catch {
81
+ return undefined;
82
+ }
83
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.149",
3
+ "version": "0.2.154",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,11 +24,11 @@
24
24
  "pretypecheck": "node ../../scripts/build-workspace-dep.mjs @bli-cockpit/cli",
25
25
  "typecheck": "node -e \"await import('./dist/commands/public-root.js')\"",
26
26
  "pretest": "node ../../scripts/build-workspace-dep.mjs @bli-cockpit/cli",
27
- "test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-surface-doors.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
27
+ "test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-surface-doors.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-cli-doctor-json.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
28
28
  },
29
29
  "dependencies": {
30
- "@bli-cockpit/memory-mcp": "0.1.38",
31
- "@bli-cockpit/mcp": "0.1.72",
32
- "@bli-cockpit/telemetry-core": "0.1.50"
30
+ "@bli-cockpit/memory-mcp": "0.1.41",
31
+ "@bli-cockpit/mcp": "0.1.75",
32
+ "@bli-cockpit/telemetry-core": "0.1.53"
33
33
  }
34
34
  }