@bli-cockpit/telemetry-core 0.1.35 → 0.1.36

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.
@@ -0,0 +1,37 @@
1
+ export type ExperienceStore = "bli" | "supermemory" | "both";
2
+ export type ExperienceVerdict = "win" | "loss" | "noise";
3
+ export interface MemoryExperience {
4
+ id: string;
5
+ store: ExperienceStore;
6
+ verdict: ExperienceVerdict;
7
+ reason: string;
8
+ created_at: string;
9
+ }
10
+ export interface ExperienceReceipt extends MemoryExperience {
11
+ shipped: boolean;
12
+ }
13
+ export type ExperienceSender = (entry: MemoryExperience) => Promise<{
14
+ ok: boolean;
15
+ reason?: string;
16
+ }>;
17
+ export declare function validateExperience(store: string, verdict: string, reason: string): void;
18
+ export declare function experienceLine(entry: MemoryExperience, agent: string, project: string): string;
19
+ export declare function appendExperience(input: {
20
+ store: ExperienceStore;
21
+ verdict: ExperienceVerdict;
22
+ reason: string;
23
+ }, options: {
24
+ homeDir?: string;
25
+ agent: string;
26
+ project: string;
27
+ }): Promise<ExperienceReceipt>;
28
+ export declare function shipExperience(entry: ExperienceReceipt, send: ExperienceSender, homeDir?: string): Promise<{
29
+ shipped: boolean;
30
+ reason: string;
31
+ }>;
32
+ /** Bounded work, no throwing into collection. UUIDs make concurrent drains idempotent. */
33
+ export declare function drainExperiences(send: ExperienceSender, homeDir?: string): Promise<{
34
+ attempted: number;
35
+ shipped: number;
36
+ reason: string;
37
+ }>;
@@ -0,0 +1,85 @@
1
+ /** Append-only experience receipts shared by the CLI and MCP. BLI-3893. */
2
+ import { appendFile, mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
3
+ import { randomUUID } from "node:crypto";
4
+ import { homedir } from "node:os";
5
+ import path from "node:path";
6
+ export function validateExperience(store, verdict, reason) {
7
+ if (!["bli", "supermemory", "both"].includes(store))
8
+ throw new Error("store must be bli, supermemory or both");
9
+ if (!["win", "loss", "noise"].includes(verdict))
10
+ throw new Error("verdict must be win, loss or noise");
11
+ if (!reason.trim() || [...reason].length > 500 || /[\r\n\x00-\x1f\x7f]/u.test(reason)) {
12
+ throw new Error("reason must be one nonempty line of at most 500 characters; opinions only, no prompts, memory bodies or secrets");
13
+ }
14
+ }
15
+ const markers = { bli: "[bli]", supermemory: "◪", both: "[bli]+◪" };
16
+ export function experienceLine(entry, agent, project) {
17
+ const stamp = new Date(Date.parse(entry.created_at) + 7 * 3600000).toISOString().slice(0, 16).replace("T", " ");
18
+ const label = (value) => value.replace(/[\r\n·]/gu, " ");
19
+ return `- ${stamp} ICT · ${label(agent)} · ${label(project)} · ${entry.store} ${markers[entry.store]} · ${entry.verdict}: ${entry.reason}`;
20
+ }
21
+ function paths(home) {
22
+ return { log: path.join(home, ".codex", "AGENT-EXPERIENCE.md"), queue: path.join(home, ".codex", "agent-experience-outbox") };
23
+ }
24
+ async function saveReceipt(home, entry) {
25
+ const target = path.join(paths(home).queue, `${entry.id}.json`);
26
+ const temp = `${target}.${randomUUID()}.tmp`;
27
+ await writeFile(temp, JSON.stringify(entry), { mode: 0o600 });
28
+ await rename(temp, target);
29
+ }
30
+ export async function appendExperience(input, options) {
31
+ validateExperience(input.store, input.verdict, input.reason);
32
+ const home = options.homeDir ?? homedir();
33
+ const entry = { ...input, id: randomUUID(), created_at: new Date().toISOString(), shipped: false };
34
+ await mkdir(paths(home).queue, { recursive: true });
35
+ // Persist the retry before the human receipt so a crash never loses delivery.
36
+ await saveReceipt(home, entry);
37
+ await appendFile(paths(home).log, `${experienceLine(entry, options.agent, options.project)}\n`, { mode: 0o600 });
38
+ return entry;
39
+ }
40
+ export async function shipExperience(entry, send, homeDir = homedir()) {
41
+ try {
42
+ const result = await send(entry);
43
+ if (!result.ok)
44
+ return { shipped: false, reason: result.reason ?? "experience_refused" };
45
+ await saveReceipt(homeDir, { ...entry, shipped: true });
46
+ return { shipped: true, reason: "accepted" };
47
+ }
48
+ catch {
49
+ return { shipped: false, reason: "experience_delivery_failed" };
50
+ }
51
+ }
52
+ /** Bounded work, no throwing into collection. UUIDs make concurrent drains idempotent. */
53
+ export async function drainExperiences(send, homeDir = homedir()) {
54
+ let attempted = 0, shipped = 0;
55
+ try {
56
+ let files;
57
+ try {
58
+ files = await readdir(paths(homeDir).queue);
59
+ }
60
+ catch (error) {
61
+ if (error.code === "ENOENT")
62
+ return { attempted, shipped, reason: "empty" };
63
+ throw error;
64
+ }
65
+ for (const file of files.sort()) {
66
+ if (!/^[0-9a-f-]{36}\.json$/u.test(file))
67
+ continue;
68
+ const entry = JSON.parse(await readFile(path.join(paths(homeDir).queue, file), "utf8"));
69
+ if (entry.shipped)
70
+ continue;
71
+ validateExperience(entry.store, entry.verdict, entry.reason);
72
+ attempted++;
73
+ const result = await shipExperience(entry, send, homeDir);
74
+ if (!result.shipped)
75
+ return { attempted, shipped, reason: result.reason };
76
+ shipped++;
77
+ if (attempted >= 20)
78
+ return { attempted, shipped, reason: "batch_cap" };
79
+ }
80
+ return { attempted, shipped, reason: "exhausted" };
81
+ }
82
+ catch {
83
+ return { attempted, shipped, reason: "experience_outbox_unreadable" };
84
+ }
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/telemetry-core",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,6 +13,10 @@
13
13
  ".": {
14
14
  "types": "./dist/index.d.ts",
15
15
  "import": "./dist/index.js"
16
+ },
17
+ "./memory-experience": {
18
+ "types": "./dist/memory-experience.d.ts",
19
+ "import": "./dist/memory-experience.js"
16
20
  }
17
21
  },
18
22
  "publishConfig": {